From 3095544cdfc9d5bc8fe5cc45ae89c8cd315668d8 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 2 Feb 2023 10:31:36 +0800 Subject: [PATCH] chore: commit history cleaning --- .babelrc.js | 26 + .browserslistrc | 10 + .editorconfig | 20 + .eslintignore | 14 + .eslintrc.js | 51 + .fatherrc.js | 5 + .github/ISSUE_TEMPLATE.md | 24 + .github/ISSUE_TEMPLATE/bug_report.yml | 85 + .github/ISSUE_TEMPLATE/bug_report_chinese.yml | 69 + .github/ISSUE_TEMPLATE/config.yml | 5 + .github/PULL_REQUEST_TEMPLATE.md | 21 + .github/workflows/gitleaks.yml | 17 + .github/workflows/preview.yml | 18 + .github/workflows/rebase.yml | 17 + .gitignore | 88 + .gitlab-ci.yml | 19 + .npmignore | 83 + .prettierignore | 10 + .prettierrc.js | 5 + .travis.yml | 30 + .vscode/settings.json | 6 + CHANGELOG.md | 1507 +++++++++++++++ LICENSE | 21 + README.en-US.md | 170 ++ README.md | 177 ++ lerna.json | 29 + package.json | 90 + packages/core/.eslintignore | 22 + packages/core/.fatherrc.js | 5 + packages/core/.npmignore | 83 + packages/core/jest.config.js | 19 + packages/core/package.json | 85 + packages/core/src/index.ts | 5 + packages/core/src/interface/index.ts | 4 + packages/core/src/types/index.ts | 0 packages/core/src/util/index.ts | 2 + packages/core/tests/unit/template-spec.ts | 10 + packages/core/tsconfig.json | 29 + packages/element/.eslintignore | 12 + packages/element/.fatherrc.js | 5 + packages/element/.npmignore | 83 + packages/element/.prettierignore | 8 + packages/element/.prettierrc.js | 5 + packages/element/jest.config.js | 19 + packages/element/package.json | 89 + packages/element/src/edges/index.ts | 0 packages/element/src/index.ts | 2 + packages/element/src/nodes/index.ts | 0 packages/element/tsconfig.json | 29 + packages/g6/.babelrc.js | 26 + packages/g6/.eslintignore | 12 + packages/g6/.fatherrc.js | 5 + packages/g6/.npmignore | 83 + packages/g6/package.json | 90 + packages/g6/src/index.ts | 7 + packages/g6/tsconfig.json | 19 + packages/g6/webpack.config.js | 47 + packages/g6/webpack.dev.config.js | 14 + packages/pc/.babelrc.js | 26 + packages/pc/.eslintignore | 12 + packages/pc/.fatherrc.js | 5 + packages/pc/.npmignore | 83 + packages/pc/global.d.ts | 4 + packages/pc/jest.config.js | 19 + packages/pc/package.json | 103 + packages/pc/src/index.ts | 3 + packages/pc/tests/unit/template-spec.ts | 10 + packages/pc/tsconfig.json | 37 + packages/pc/webpack.config.js | 47 + packages/pc/webpack.dev.config.js | 14 + packages/plugin/.eslintignore | 15 + packages/plugin/.fatherrc.js | 6 + packages/plugin/.npmignore | 49 + packages/plugin/jest.config.js | 25 + packages/plugin/package.json | 53 + packages/plugin/src/index.ts | 5 + packages/plugin/tsconfig.json | 18 + packages/react-node/.editorconfig | 16 + packages/react-node/.fatherrc.ts | 4 + packages/react-node/.gitignore | 21 + packages/react-node/.npmignore | 3 + packages/react-node/.prettierignore | 7 + packages/react-node/.prettierrc | 11 + packages/react-node/.umirc.ts | 18 + packages/react-node/README.md | 90 + packages/react-node/docs/index.en-US.md | 174 ++ packages/react-node/docs/index.md | 170 ++ packages/react-node/package.json | 47 + packages/react-node/src/API/CircleStyle.tsx | 5 + packages/react-node/src/API/EllipseStyle.tsx | 5 + packages/react-node/src/API/Event.tsx | 5 + packages/react-node/src/API/ImageStyle.tsx | 5 + packages/react-node/src/API/MarkerStyle.tsx | 5 + packages/react-node/src/API/PathStyle.tsx | 5 + packages/react-node/src/API/PolygonStyle.tsx | 5 + packages/react-node/src/API/RectStyle.tsx | 5 + packages/react-node/src/API/TextStyle.tsx | 5 + packages/react-node/src/API/circle.en-US.md | 28 + packages/react-node/src/API/circle.md | 29 + packages/react-node/src/API/ellipse.en-US.md | 29 + packages/react-node/src/API/ellipse.md | 29 + packages/react-node/src/API/event.en-US.md | 14 + packages/react-node/src/API/event.md | 14 + packages/react-node/src/API/image.en-US.md | 28 + packages/react-node/src/API/image.md | 28 + packages/react-node/src/API/index.en-US.md | 5 + packages/react-node/src/API/index.md | 5 + packages/react-node/src/API/marker.en-US.md | 31 + packages/react-node/src/API/marker.md | 31 + packages/react-node/src/API/path.en-US.md | 34 + packages/react-node/src/API/path.md | 34 + packages/react-node/src/API/polygon.en-US.md | 34 + packages/react-node/src/API/polygon.md | 34 + packages/react-node/src/API/rect.en-US.md | 45 + packages/react-node/src/API/rect.md | 45 + packages/react-node/src/API/text.en-US.md | 31 + packages/react-node/src/API/text.md | 31 + packages/react-node/src/Animation/animate.ts | 22 + .../react-node/src/Animation/animateFunc.ts | 158 ++ packages/react-node/src/Example/animate.md | 92 + packages/react-node/src/Example/card.en-US.md | 95 + packages/react-node/src/Example/card.md | 94 + .../react-node/src/Example/event.en-US.md | 118 ++ packages/react-node/src/Example/event.md | 118 ++ packages/react-node/src/Layout/LayoutEnums.ts | 64 + .../src/Layout/getPositionsUsingYoga.ts | 282 +++ .../react-node/src/Layout/getShapeSize.ts | 186 ++ packages/react-node/src/Loading/index.tsx | 23 + packages/react-node/src/ReactNode/Group.tsx | 65 + .../react-node/src/ReactNode/Shape/Circle.tsx | 36 + .../src/ReactNode/Shape/Ellipse.tsx | 41 + .../react-node/src/ReactNode/Shape/Image.tsx | 46 + .../react-node/src/ReactNode/Shape/Marker.tsx | 47 + .../react-node/src/ReactNode/Shape/Path.tsx | 68 + .../src/ReactNode/Shape/Polygon.tsx | 36 + .../react-node/src/ReactNode/Shape/Rect.tsx | 46 + .../react-node/src/ReactNode/Shape/Text.tsx | 70 + .../react-node/src/ReactNode/Shape/common.ts | 114 ++ packages/react-node/src/ReactNode/demo.tsx | 78 + packages/react-node/src/ReactNode/index.ts | 11 + packages/react-node/src/Register/event.ts | 64 + .../src/Register/getDataFromReactNode.ts | 54 + packages/react-node/src/Register/register.tsx | 133 ++ packages/react-node/src/index.ts | 1 + packages/react-node/test/react.test.tsx | 15 + packages/react-node/tsconfig.json | 27 + packages/react-node/typings.d.ts | 2 + packages/site/.dumi/global.ts | 9 + packages/site/.dumirc.ts | 594 ++++++ packages/site/.eslintignore | 22 + packages/site/.eslintrc.js | 47 + packages/site/.github/workflows/mirror.yml | 30 + packages/site/.gitignore | 10 + packages/site/CNAME | 1 + packages/site/api-extractor.json | 15 + packages/site/docs/api/Algorithm.en.md | 858 ++++++++ packages/site/docs/api/Algorithm.zh.md | 938 +++++++++ packages/site/docs/api/Behavior.en.md | 202 ++ packages/site/docs/api/Behavior.zh.md | 202 ++ packages/site/docs/api/Event.en.md | 389 ++++ packages/site/docs/api/Event.zh.md | 383 ++++ packages/site/docs/api/Graph.en.md | 258 +++ packages/site/docs/api/Graph.zh.md | 253 +++ packages/site/docs/api/Group.en.md | 263 +++ packages/site/docs/api/Group.zh.md | 264 +++ .../site/docs/api/Items/comboMethods.en.md | 139 ++ .../site/docs/api/Items/comboMethods.zh.md | 139 ++ .../site/docs/api/Items/comboProperties.en.md | 63 + .../site/docs/api/Items/comboProperties.zh.md | 61 + .../site/docs/api/Items/edgeMethods.en.md | 76 + .../site/docs/api/Items/edgeMethods.zh.md | 76 + .../site/docs/api/Items/edgeProperties.en.md | 72 + .../site/docs/api/Items/edgeProperties.zh.md | 70 + .../site/docs/api/Items/itemMethods.en.md | 438 +++++ .../site/docs/api/Items/itemMethods.zh.md | 446 +++++ .../site/docs/api/Items/itemProperties.en.md | 36 + .../site/docs/api/Items/itemProperties.zh.md | 36 + .../site/docs/api/Items/nodeMethods.en.md | 220 +++ .../site/docs/api/Items/nodeMethods.zh.md | 226 +++ .../site/docs/api/Items/nodeProperties.en.md | 68 + .../site/docs/api/Items/nodeProperties.zh.md | 68 + packages/site/docs/api/Plugins.en.md | 1007 ++++++++++ packages/site/docs/api/Plugins.zh.md | 1022 ++++++++++ packages/site/docs/api/TreeGraph.en.md | 68 + packages/site/docs/api/TreeGraph.zh.md | 68 + packages/site/docs/api/Util.en.md | 166 ++ packages/site/docs/api/Util.zh.md | 166 ++ .../site/docs/api/graphFunc/animation.en.md | 22 + .../site/docs/api/graphFunc/animation.zh.md | 22 + .../site/docs/api/graphFunc/behavior.en.md | 92 + .../site/docs/api/graphFunc/behaviors.zh.md | 92 + .../site/docs/api/graphFunc/calculation.en.md | 56 + .../site/docs/api/graphFunc/calculation.zh.md | 64 + packages/site/docs/api/graphFunc/combo.en.md | 248 +++ packages/site/docs/api/graphFunc/combo.zh.md | 158 ++ .../site/docs/api/graphFunc/coordinate.en.md | 138 ++ .../site/docs/api/graphFunc/coordinate.zh.md | 141 ++ packages/site/docs/api/graphFunc/data.en.md | 164 ++ packages/site/docs/api/graphFunc/data.zh.md | 138 ++ .../site/docs/api/graphFunc/destroy.en.md | 24 + .../site/docs/api/graphFunc/destroy.zh.md | 28 + .../site/docs/api/graphFunc/download.en.md | 112 ++ .../site/docs/api/graphFunc/download.zh.md | 111 ++ packages/site/docs/api/graphFunc/find.en.md | 197 ++ packages/site/docs/api/graphFunc/find.zh.md | 197 ++ .../site/docs/api/graphFunc/get_set.en.md | 169 ++ .../site/docs/api/graphFunc/get_set.zh.md | 169 ++ packages/site/docs/api/graphFunc/hull.en.md | 112 ++ packages/site/docs/api/graphFunc/hull.zh.md | 112 ++ packages/site/docs/api/graphFunc/item.en.md | 315 +++ packages/site/docs/api/graphFunc/item.zh.md | 314 +++ packages/site/docs/api/graphFunc/layout.en.md | 126 ++ packages/site/docs/api/graphFunc/layout.zh.md | 126 ++ packages/site/docs/api/graphFunc/mode.en.md | 46 + packages/site/docs/api/graphFunc/mode.zh.md | 48 + packages/site/docs/api/graphFunc/on_off.en.md | 169 ++ packages/site/docs/api/graphFunc/on_off.zh.md | 151 ++ packages/site/docs/api/graphFunc/render.en.md | 68 + packages/site/docs/api/graphFunc/render.zh.md | 74 + packages/site/docs/api/graphFunc/stack.en.md | 45 + packages/site/docs/api/graphFunc/stack.zh.md | 45 + packages/site/docs/api/graphFunc/state.en.md | 73 + packages/site/docs/api/graphFunc/state.zh.md | 71 + .../site/docs/api/graphFunc/transform.en.md | 222 +++ .../site/docs/api/graphFunc/transform.zh.md | 223 +++ .../site/docs/api/graphFunc/watermarker.en.md | 63 + .../site/docs/api/graphFunc/watermarker.zh.md | 63 + .../site/docs/api/graphLayout/circular.en.md | 67 + .../site/docs/api/graphLayout/circular.zh.md | 67 + .../docs/api/graphLayout/comboCombined.en.md | 109 ++ .../docs/api/graphLayout/comboCombined.zh.md | 108 ++ .../docs/api/graphLayout/comboForce.en.md | 178 ++ .../docs/api/graphLayout/comboForce.zh.md | 177 ++ .../docs/api/graphLayout/concentric.en.md | 75 + .../docs/api/graphLayout/concentric.zh.md | 75 + .../site/docs/api/graphLayout/dagre.en.md | 135 ++ .../site/docs/api/graphLayout/dagre.zh.md | 131 ++ .../site/docs/api/graphLayout/force.en.md | 134 ++ .../site/docs/api/graphLayout/force.zh.md | 135 ++ .../site/docs/api/graphLayout/force2.en.md | 196 ++ .../site/docs/api/graphLayout/force2.zh.md | 194 ++ .../docs/api/graphLayout/forceAtlas2.en.md | 93 + .../docs/api/graphLayout/forceAtlas2.zh.md | 94 + .../docs/api/graphLayout/fruchterman.en.md | 62 + .../docs/api/graphLayout/fruchterman.zh.md | 62 + .../site/docs/api/graphLayout/gforce.en.md | 133 ++ .../site/docs/api/graphLayout/gforce.zh.md | 133 ++ packages/site/docs/api/graphLayout/grid.en.md | 64 + packages/site/docs/api/graphLayout/grid.zh.md | 65 + .../site/docs/api/graphLayout/guide.en.md | 114 ++ .../site/docs/api/graphLayout/guide.zh.md | 114 ++ packages/site/docs/api/graphLayout/mds.en.md | 31 + packages/site/docs/api/graphLayout/mds.zh.md | 31 + .../site/docs/api/graphLayout/radial.en.md | 102 + .../site/docs/api/graphLayout/radial.zh.md | 102 + .../site/docs/api/graphLayout/random.en.md | 38 + .../site/docs/api/graphLayout/random.zh.md | 38 + packages/site/docs/api/registerItem.en.md | 305 +++ packages/site/docs/api/registerItem.zh.md | 308 +++ packages/site/docs/api/registerLayout.en.md | 126 ++ packages/site/docs/api/registerLayout.zh.md | 128 ++ packages/site/docs/api/shapeMethods.en.md | 63 + packages/site/docs/api/shapeMethods.zh.md | 65 + packages/site/docs/api/shapeProperties.en.md | 554 ++++++ packages/site/docs/api/shapeProperties.zh.md | 555 ++++++ .../docs/api/treeGraphLayout/compactBox.en.md | 127 ++ .../docs/api/treeGraphLayout/compactBox.zh.md | 126 ++ .../docs/api/treeGraphLayout/dendrogram.en.md | 48 + .../docs/api/treeGraphLayout/dendrogram.zh.md | 50 + .../site/docs/api/treeGraphLayout/guide.en.md | 43 + .../site/docs/api/treeGraphLayout/guide.zh.md | 45 + .../docs/api/treeGraphLayout/indented.en.md | 86 + .../docs/api/treeGraphLayout/indented.zh.md | 88 + .../docs/api/treeGraphLayout/mindmap.en.md | 90 + .../docs/api/treeGraphLayout/mindmap.zh.md | 90 + packages/site/docs/api/treeMethods.en.md | 194 ++ packages/site/docs/api/treeMethods.zh.md | 191 ++ packages/site/docs/design/case.en.md | 8 + packages/site/docs/design/case.zh.md | 24 + .../design/component/componentOverview.en.md | 8 + .../design/component/componentOverview.zh.md | 154 ++ .../site/docs/design/component/timebar.en.md | 8 + .../site/docs/design/component/timebar.zh.md | 99 + .../docs/design/component/viewToolbar.en.md | 8 + .../docs/design/component/viewToolbar.zh.md | 99 + .../site/docs/design/global/Interactive.zh.md | 132 ++ .../site/docs/design/global/interactive.en.md | 8 + packages/site/docs/design/global/style.en.md | 8 + packages/site/docs/design/global/style.zh.md | 180 ++ packages/site/docs/design/overview.en.md | 199 ++ packages/site/docs/design/overview.zh.md | 214 ++ packages/site/docs/design/principles.en.md | 10 + packages/site/docs/design/principles.zh.md | 46 + packages/site/docs/design/template.en.md | 8 + packages/site/docs/design/template.zh.md | 183 ++ .../docs/manual/FAQ/angular-support.en.md | 20 + .../docs/manual/FAQ/angular-support.zh.md | 20 + .../docs/manual/FAQ/build-error-rollup.en.md | 31 + .../docs/manual/FAQ/build-error-rollup.zh.md | 31 + .../site/docs/manual/FAQ/build-error.en.md | 16 + .../site/docs/manual/FAQ/build-error.zh.md | 16 + packages/site/docs/manual/FAQ/faq.en.md | 77 + packages/site/docs/manual/FAQ/faq.zh.md | 76 + .../docs/manual/FAQ/performance-opt.en.md | 217 +++ .../docs/manual/FAQ/performance-opt.zh.md | 206 ++ packages/site/docs/manual/FAQ/supportIE.en.md | 66 + packages/site/docs/manual/FAQ/supportIE.zh.md | 80 + .../site/docs/manual/FAQ/upgradeGuide.en.md | 361 ++++ .../site/docs/manual/FAQ/upgradeGuide.zh.md | 363 ++++ .../site/docs/manual/FAQ/vite-support.en.md | 30 + .../site/docs/manual/FAQ/vite-support.zh.md | 28 + .../docs/manual/advanced/comboTheory.en.md | 72 + .../docs/manual/advanced/comboTheory.zh.md | 70 + .../manual/advanced/coordinate-system.en.md | 229 +++ .../manual/advanced/coordinate-system.zh.md | 225 +++ .../site/docs/manual/advanced/g6InReact.en.md | 110 ++ .../site/docs/manual/advanced/g6InReact.zh.md | 110 ++ .../site/docs/manual/advanced/iconfont.en.md | 346 ++++ .../site/docs/manual/advanced/iconfont.zh.md | 344 ++++ .../advanced/mode-and-custom-behavior.en.md | 212 ++ .../advanced/mode-and-custom-behavior.zh.md | 214 ++ .../site/docs/manual/advanced/state-new.en.md | 266 +++ .../site/docs/manual/advanced/state-new.zh.md | 274 +++ .../site/docs/manual/cases/edgeBundling.en.md | 331 ++++ .../site/docs/manual/cases/edgeBundling.zh.md | 329 ++++ .../site/docs/manual/cases/relations.en.md | 105 + .../site/docs/manual/cases/relations.zh.md | 105 + .../site/docs/manual/cases/sequenceTime.en.md | 88 + .../site/docs/manual/cases/sequenceTime.zh.md | 88 + .../site/docs/manual/getting-started.en.md | 203 ++ .../site/docs/manual/getting-started.zh.md | 203 ++ packages/site/docs/manual/introduction.en.md | 139 ++ packages/site/docs/manual/introduction.zh.md | 148 ++ .../site/docs/manual/middle/animation.en.md | 479 +++++ .../site/docs/manual/middle/animation.zh.md | 492 +++++ .../elements/advanced-style/gradient.en.md | 40 + .../elements/advanced-style/gradient.zh.md | 42 + .../advanced-style/set-label-bg.en.md | 47 + .../advanced-style/set-label-bg.zh.md | 47 + .../elements/advanced-style/texture.en.md | 18 + .../elements/advanced-style/texture.zh.md | 21 + .../elements/advanced-style/updateText.en.md | 79 + .../elements/advanced-style/updateText.zh.md | 79 + .../elements/combos/built-in/circle.en.md | 132 ++ .../elements/combos/built-in/circle.zh.md | 132 ++ .../elements/combos/built-in/rect.en.md | 132 ++ .../elements/combos/built-in/rect.zh.md | 132 ++ .../middle/elements/combos/custom-combo.en.md | 359 ++++ .../middle/elements/combos/custom-combo.zh.md | 350 ++++ .../middle/elements/combos/defaultCombo.en.md | 426 ++++ .../middle/elements/combos/defaultCombo.zh.md | 438 +++++ .../manual/middle/elements/edges/arrow.en.md | 109 ++ .../manual/middle/elements/edges/arrow.zh.md | 108 ++ .../middle/elements/edges/built-in/arc.en.md | 171 ++ .../middle/elements/edges/built-in/arc.zh.md | 171 ++ .../elements/edges/built-in/cubic.en.md | 137 ++ .../elements/edges/built-in/cubic.zh.md | 137 ++ .../middle/elements/edges/built-in/line.en.md | 135 ++ .../middle/elements/edges/built-in/line.zh.md | 135 ++ .../middle/elements/edges/built-in/loop.en.md | 161 ++ .../middle/elements/edges/built-in/loop.zh.md | 161 ++ .../elements/edges/built-in/polyline.en.md | 159 ++ .../elements/edges/built-in/polyline.zh.md | 159 ++ .../elements/edges/built-in/quadratic.en.md | 135 ++ .../elements/edges/built-in/quadratic.zh.md | 135 ++ .../middle/elements/edges/custom-edge.en.md | 352 ++++ .../middle/elements/edges/custom-edge.zh.md | 362 ++++ .../middle/elements/edges/defaultEdge.en.md | 299 +++ .../middle/elements/edges/defaultEdge.zh.md | 300 +++ .../middle/elements/methods/edgeVisible.en.md | 67 + .../middle/elements/methods/edgeVisible.zh.md | 67 + .../elements/methods/elementIndex.en.md | 235 +++ .../elements/methods/elementIndex.zh.md | 233 +++ .../middle/elements/methods/lock-node.en.md | 215 +++ .../middle/elements/methods/lock-node.zh.md | 215 +++ .../middle/elements/methods/multi-line.en.md | 141 ++ .../middle/elements/methods/multi-line.zh.md | 143 ++ .../elements/methods/updateElement.en.md | 66 + .../elements/methods/updateElement.zh.md | 66 + .../middle/elements/nodes/anchorpoint.en.md | 65 + .../middle/elements/nodes/anchorpoint.zh.md | 67 + .../elements/nodes/built-in/circle.en.md | 194 ++ .../elements/nodes/built-in/circle.zh.md | 194 ++ .../elements/nodes/built-in/diamond.en.md | 197 ++ .../elements/nodes/built-in/diamond.zh.md | 196 ++ .../elements/nodes/built-in/donut.en.md | 234 +++ .../elements/nodes/built-in/donut.zh.md | 235 +++ .../elements/nodes/built-in/ellipse.en.md | 198 ++ .../elements/nodes/built-in/ellipse.zh.md | 196 ++ .../elements/nodes/built-in/image.en.md | 194 ++ .../elements/nodes/built-in/image.zh.md | 196 ++ .../elements/nodes/built-in/modelRect.en.md | 295 +++ .../elements/nodes/built-in/modelRect.zh.md | 295 +++ .../middle/elements/nodes/built-in/rect.en.md | 171 ++ .../middle/elements/nodes/built-in/rect.zh.md | 170 ++ .../middle/elements/nodes/built-in/star.en.md | 197 ++ .../middle/elements/nodes/built-in/star.zh.md | 196 ++ .../elements/nodes/built-in/triangle.en.md | 223 +++ .../elements/nodes/built-in/triangle.zh.md | 220 +++ .../middle/elements/nodes/custom-node.en.md | 572 ++++++ .../middle/elements/nodes/custom-node.zh.md | 575 ++++++ .../middle/elements/nodes/defaultNode.en.md | 328 ++++ .../middle/elements/nodes/defaultNode.zh.md | 331 ++++ .../middle/elements/nodes/jsx-node.en.md | 150 ++ .../middle/elements/nodes/jsx-node.zh.md | 150 ++ .../middle/elements/nodes/react-node.en.md | 177 ++ .../middle/elements/nodes/react-node.zh.md | 178 ++ .../manual/middle/elements/overview.en.md | 15 + .../manual/middle/elements/overview.zh.md | 15 + .../elements/shape/graphics-group.en.md | 76 + .../elements/shape/graphics-group.zh.md | 72 + .../elements/shape/shape-and-properties.en.md | 369 ++++ .../elements/shape/shape-and-properties.zh.md | 369 ++++ .../elements/shape/shape-keyshape.en.md | 127 ++ .../elements/shape/shape-keyshape.zh.md | 129 ++ .../middle/elements/shape/transform.en.md | 140 ++ .../middle/elements/shape/transform.zh.md | 141 ++ packages/site/docs/manual/middle/graph.en.md | 185 ++ packages/site/docs/manual/middle/graph.zh.md | 183 ++ .../docs/manual/middle/layout/ai-layout.en.md | 52 + .../docs/manual/middle/layout/ai-layout.zh.md | 52 + .../manual/middle/layout/custom-layout.en.md | 222 +++ .../manual/middle/layout/custom-layout.zh.md | 224 +++ .../manual/middle/layout/graph-layout.en.md | 370 ++++ .../manual/middle/layout/graph-layout.zh.md | 371 ++++ .../middle/layout/layout-mechanism.en.md | 125 ++ .../middle/layout/layout-mechanism.zh.md | 125 ++ .../middle/layout/sub-layout-pipe.en.md | 53 + .../middle/layout/sub-layout-pipe.zh.md | 54 + .../manual/middle/layout/sub-layout.en.md | 31 + .../manual/middle/layout/sub-layout.zh.md | 31 + .../middle/layout/tree-graph-layout.en.md | 101 + .../middle/layout/tree-graph-layout.zh.md | 101 + .../docs/manual/middle/layout/webworker.en.md | 22 + .../docs/manual/middle/layout/webworker.zh.md | 23 + .../site/docs/manual/middle/overview.en.md | 62 + .../site/docs/manual/middle/overview.zh.md | 63 + .../docs/manual/middle/plugins/Plugins.en.md | 924 +++++++++ .../docs/manual/middle/plugins/Plugins.zh.md | 994 ++++++++++ .../middle/plugins/autoZoomTooltip.en.md | 29 + .../middle/plugins/autoZoomTooltip.zh.md | 29 + .../docs/manual/middle/states/bindEvent.en.md | 103 + .../docs/manual/middle/states/bindEvent.zh.md | 103 + .../middle/states/custom-behavior.en.md | 105 + .../middle/states/custom-behavior.zh.md | 105 + .../middle/states/defaultBehavior.en.md | 801 ++++++++ .../middle/states/defaultBehavior.zh.md | 751 +++++++ .../site/docs/manual/middle/states/mode.en.md | 96 + .../site/docs/manual/middle/states/mode.zh.md | 96 + .../docs/manual/middle/states/state.en.md | 314 +++ .../docs/manual/middle/states/state.zh.md | 344 ++++ .../site/docs/manual/tutorial/animation.en.md | 36 + .../site/docs/manual/tutorial/animation.zh.md | 36 + .../site/docs/manual/tutorial/behavior.en.md | 302 +++ .../site/docs/manual/tutorial/behavior.zh.md | 305 +++ .../site/docs/manual/tutorial/elements.en.md | 306 +++ .../site/docs/manual/tutorial/elements.zh.md | 306 +++ .../site/docs/manual/tutorial/epilog.en.md | 19 + .../site/docs/manual/tutorial/epilog.zh.md | 19 + .../site/docs/manual/tutorial/example.en.md | 216 +++ .../site/docs/manual/tutorial/example.zh.md | 220 +++ .../site/docs/manual/tutorial/layout.en.md | 182 ++ .../site/docs/manual/tutorial/layout.zh.md | 183 ++ .../site/docs/manual/tutorial/plugins.en.md | 199 ++ .../site/docs/manual/tutorial/plugins.zh.md | 199 ++ .../site/docs/manual/tutorial/preface.en.md | 63 + .../site/docs/manual/tutorial/preface.zh.md | 64 + .../examples/algorithm/algoDemos/API.en.md | 5 + .../examples/algorithm/algoDemos/API.zh.md | 5 + .../algorithm/algoDemos/demo/colorSets.js | 138 ++ .../algorithm/algoDemos/demo/gaddi.js | 231 +++ .../algoDemos/demo/labelPropagation.js | 67 + .../algorithm/algoDemos/demo/louvain.js | 67 + .../algorithm/algoDemos/demo/meta.json | 48 + .../algorithm/algoDemos/demo/shortestPath.js | 92 + .../examples/algorithm/algoDemos/index.en.md | 10 + .../examples/algorithm/algoDemos/index.zh.md | 10 + .../case/graphDemos/demo/christmasBubbles.js | 681 +++++++ .../case/graphDemos/demo/customFlow.js | 302 +++ .../case/graphDemos/demo/decisionBubbles.js | 933 +++++++++ .../case/graphDemos/demo/donutTransfer.js | 201 ++ .../case/graphDemos/demo/edgeBundling.js | 176 ++ .../case/graphDemos/demo/graphinsight.js | 23 + .../case/graphDemos/demo/largeGraph.js | 1718 +++++++++++++++++ .../examples/case/graphDemos/demo/meta.json | 80 + .../examples/case/graphDemos/demo/sequence.js | 252 +++ .../case/graphDemos/demo/simplifyCluster.js | 356 ++++ .../site/examples/case/graphDemos/index.en.md | 93 + .../site/examples/case/graphDemos/index.zh.md | 90 + .../site/examples/case/others/demo/meta.json | 16 + .../examples/case/others/demo/metroLines.js | 270 +++ .../site/examples/case/others/index.en.md | 9 + .../site/examples/case/others/index.zh.md | 9 + .../site/examples/case/treeDemos/API.en.md | 0 .../site/examples/case/treeDemos/API.zh.md | 0 .../case/treeDemos/demo/australiaFire.js | 457 +++++ .../case/treeDemos/demo/customFlow.js | 360 ++++ .../case/treeDemos/demo/decisionTree.js | 687 +++++++ .../case/treeDemos/demo/indentedTree.js | 1572 +++++++++++++++ .../case/treeDemos/demo/knowledgeTreeGraph.js | 458 +++++ .../examples/case/treeDemos/demo/meta.json | 56 + .../examples/case/treeDemos/demo/mindmap.js | 502 +++++ .../site/examples/case/treeDemos/index.en.md | 61 + .../site/examples/case/treeDemos/index.zh.md | 60 + .../site/examples/interaction/combo/API.en.md | 11 + .../site/examples/interaction/combo/API.zh.md | 11 + .../interaction/combo/demo/cCircle.js | 180 ++ .../examples/interaction/combo/demo/cRect.js | 178 ++ .../examples/interaction/combo/demo/circle.js | 91 + .../examples/interaction/combo/demo/meta.json | 28 + .../examples/interaction/combo/demo/rect.js | 94 + .../examples/interaction/combo/index.en.md | 12 + .../examples/interaction/combo/index.zh.md | 10 + .../examples/interaction/createEdge/API.en.md | 11 + .../examples/interaction/createEdge/API.zh.md | 11 + .../createEdge/demo/click-and-key.js | 58 + .../createEdge/demo/click-link-point.js | 259 +++ .../interaction/createEdge/demo/click.js | 53 + .../createEdge/demo/drag-link-point.js | 273 +++ .../interaction/createEdge/demo/drag.js | 58 + .../interaction/createEdge/demo/meta.json | 33 + .../interaction/createEdge/index.en.md | 10 + .../interaction/createEdge/index.zh.md | 10 + .../interaction/customBehavior/API.en.md | 7 + .../interaction/customBehavior/API.zh.md | 7 + .../demo/dragCanvasTwoFingers.js | 67 + .../interaction/customBehavior/demo/meta.json | 16 + .../interaction/customBehavior/index.en.md | 10 + .../interaction/customBehavior/index.zh.md | 10 + .../interaction/dragCanvasHideItem/API.en.md | 11 + .../interaction/dragCanvasHideItem/API.zh.md | 11 + .../dragCanvasHideItem/demo/hideItem.js | 101 + .../dragCanvasHideItem/demo/meta.json | 16 + .../dragCanvasHideItem/index.en.md | 10 + .../dragCanvasHideItem/index.zh.md | 10 + .../examples/interaction/fitView/API.en.md | 11 + .../examples/interaction/fitView/API.zh.md | 11 + .../interaction/fitView/demo/fitView.js | 34 + .../interaction/fitView/demo/meta.json | 18 + .../interaction/fitView/demo/moveTo.js | 41 + .../examples/interaction/fitView/index.en.md | 12 + .../examples/interaction/fitView/index.zh.md | 10 + .../examples/interaction/highlight/API.en.md | 11 + .../examples/interaction/highlight/API.zh.md | 11 + .../highlight/demo/activateRelations.js | 101 + .../highlight/demo/highlightDark.js | 147 ++ .../interaction/highlight/demo/meta.json | 24 + .../interaction/highlight/index.en.md | 13 + .../interaction/highlight/index.zh.md | 10 + .../site/examples/interaction/hull/API.en.md | 5 + .../site/examples/interaction/hull/API.zh.md | 5 + .../interaction/hull/demo/changeMembers.js | 212 ++ .../examples/interaction/hull/demo/hull.js | 134 ++ .../examples/interaction/hull/demo/meta.json | 24 + .../examples/interaction/hull/index.en.md | 10 + .../examples/interaction/hull/index.zh.md | 10 + .../site/examples/interaction/label/API.en.md | 5 + .../site/examples/interaction/label/API.zh.md | 5 + .../interaction/label/demo/changeImg.js | 114 ++ .../examples/interaction/label/demo/meta.json | 24 + .../examples/interaction/label/demo/update.js | 110 ++ .../examples/interaction/label/index.en.md | 12 + .../examples/interaction/label/index.zh.md | 12 + .../pagination/demo/dagrePagination.js | 515 +++++ .../interaction/pagination/demo/meta.json | 24 + .../pagination/demo/treePagination.js | 320 +++ .../interaction/pagination/index.en.md | 13 + .../interaction/pagination/index.zh.md | 13 + .../interaction/partialResponse/API.en.md | 5 + .../interaction/partialResponse/API.zh.md | 5 + .../partialResponse/demo/meta.json | 16 + .../partialResponse/demo/partialNode.js | 136 ++ .../interaction/partialResponse/index.en.md | 10 + .../interaction/partialResponse/index.zh.md | 10 + .../examples/interaction/position/API.en.md | 5 + .../examples/interaction/position/API.zh.md | 5 + .../interaction/position/demo/meta.json | 24 + .../interaction/position/demo/move.js | 80 + .../interaction/position/demo/moveAnimate.js | 90 + .../examples/interaction/position/index.en.md | 13 + .../examples/interaction/position/index.zh.md | 13 + .../examples/interaction/select/API.en.md | 11 + .../examples/interaction/select/API.zh.md | 11 + .../examples/interaction/select/demo/brush.js | 91 + .../examples/interaction/select/demo/click.js | 91 + .../examples/interaction/select/demo/lasso.js | 89 + .../interaction/select/demo/meta.json | 23 + .../examples/interaction/select/index.en.md | 14 + .../examples/interaction/select/index.zh.md | 12 + .../examples/interaction/setMode/API.en.md | 5 + .../examples/interaction/setMode/API.zh.md | 5 + .../interaction/setMode/demo/meta.json | 16 + .../interaction/setMode/demo/setMode.js | 173 ++ .../examples/interaction/setMode/index.en.md | 14 + .../examples/interaction/setMode/index.zh.md | 14 + .../interaction/treeBehavior/API.en.md | 5 + .../interaction/treeBehavior/API.zh.md | 5 + .../treeBehavior/demo/changeData.js | 185 ++ .../treeBehavior/demo/collapseSlibing.js | 226 +++ .../treeBehavior/demo/dragSubtree.js | 149 ++ .../treeBehavior/demo/loadTreeData.js | 162 ++ .../interaction/treeBehavior/demo/meta.json | 40 + .../interaction/treeBehavior/index.en.md | 20 + .../interaction/treeBehavior/index.zh.md | 21 + .../interaction/zoomCanvasFixItem/API.en.md | 11 + .../interaction/zoomCanvasFixItem/API.zh.md | 11 + .../zoomCanvasFixItem/demo/fixItem.js | 145 ++ .../zoomCanvasFixItem/demo/meta.json | 16 + .../interaction/zoomCanvasFixItem/index.en.md | 10 + .../interaction/zoomCanvasFixItem/index.zh.md | 10 + packages/site/examples/item/arrows/API.en.md | 5 + packages/site/examples/item/arrows/API.zh.md | 5 + .../item/arrows/demo/built-in-arrows.js | 220 +++ .../item/arrows/demo/custom-arrows.js | 92 + .../site/examples/item/arrows/demo/meta.json | 24 + .../site/examples/item/arrows/index.en.md | 10 + .../site/examples/item/arrows/index.zh.md | 10 + .../site/examples/item/customCombo/API.en.md | 5 + .../site/examples/item/customCombo/API.zh.md | 5 + .../examples/item/customCombo/demo/cCircle.js | 146 ++ .../examples/item/customCombo/demo/cRect.js | 144 ++ .../examples/item/customCombo/demo/meta.json | 18 + .../examples/item/customCombo/index.en.md | 10 + .../examples/item/customCombo/index.zh.md | 10 + .../site/examples/item/customEdge/API.en.md | 5 + .../site/examples/item/customEdge/API.zh.md | 5 + .../item/customEdge/demo/customPolyline.js | 136 ++ .../item/customEdge/demo/customPolyline2.js | 128 ++ .../item/customEdge/demo/edgeMulLabel.js | 137 ++ .../item/customEdge/demo/extraShape.js | 115 ++ .../examples/item/customEdge/demo/meta.json | 40 + .../site/examples/item/customEdge/index.en.md | 19 + .../site/examples/item/customEdge/index.zh.md | 19 + .../site/examples/item/customNode/API.en.md | 5 + .../site/examples/item/customNode/API.zh.md | 5 + .../item/customNode/demo/areaChart.js | 213 ++ .../examples/item/customNode/demo/barChart.js | 186 ++ .../examples/item/customNode/demo/card.js | 490 +++++ .../examples/item/customNode/demo/cardNode.js | 196 ++ .../item/customNode/demo/intervalChartNode.js | 193 ++ .../examples/item/customNode/demo/jsxNode.js | 104 + .../customNode/demo/jsxNodeWithAnimate.js | 108 ++ .../item/customNode/demo/lineChart.js | 209 ++ .../item/customNode/demo/lineChartNode.js | 160 ++ .../examples/item/customNode/demo/list.js | 232 +++ .../examples/item/customNode/demo/meta.json | 144 ++ .../examples/item/customNode/demo/pieChart.js | 108 ++ .../item/customNode/demo/pieChartNode.js | 161 ++ .../item/customNode/demo/pointChart.js | 182 ++ .../item/customNode/demo/pointChartNode.js | 590 ++++++ .../item/customNode/demo/scrollNode.js | 668 +++++++ .../item/customNode/demo/stackChart.js | 181 ++ .../examples/item/customNode/demo/svgDom.js | 139 ++ .../site/examples/item/customNode/index.en.md | 14 + .../site/examples/item/customNode/index.zh.md | 14 + .../examples/item/defaultCombos/API.en.md | 19 + .../examples/item/defaultCombos/API.zh.md | 19 + .../item/defaultCombos/demo/circle.js | 109 ++ .../item/defaultCombos/demo/meta.json | 18 + .../examples/item/defaultCombos/demo/rect.js | 111 ++ .../examples/item/defaultCombos/index.en.md | 12 + .../examples/item/defaultCombos/index.zh.md | 10 + .../site/examples/item/defaultEdges/API.en.md | 15 + .../site/examples/item/defaultEdges/API.zh.md | 15 + .../examples/item/defaultEdges/demo/arc.js | 107 + .../examples/item/defaultEdges/demo/cubic1.js | 131 ++ .../examples/item/defaultEdges/demo/cubic2.js | 107 + .../examples/item/defaultEdges/demo/loop.js | 96 + .../examples/item/defaultEdges/demo/meta.json | 64 + .../item/defaultEdges/demo/polyline1.js | 87 + .../item/defaultEdges/demo/polyline2.js | 88 + .../item/defaultEdges/demo/polyline3.js | 109 ++ .../examples/item/defaultEdges/index.en.md | 12 + .../examples/item/defaultEdges/index.zh.md | 12 + .../site/examples/item/defaultNodes/API.en.md | 15 + .../site/examples/item/defaultNodes/API.zh.md | 15 + .../examples/item/defaultNodes/demo/circle.js | 106 + .../item/defaultNodes/demo/diamond.js | 106 + .../examples/item/defaultNodes/demo/donut.js | 118 ++ .../item/defaultNodes/demo/ellipse.js | 105 + .../examples/item/defaultNodes/demo/image.js | 43 + .../examples/item/defaultNodes/demo/meta.json | 80 + .../item/defaultNodes/demo/modelRect.js | 112 ++ .../examples/item/defaultNodes/demo/rect.js | 108 ++ .../examples/item/defaultNodes/demo/star.js | 106 + .../item/defaultNodes/demo/triangle.js | 106 + .../examples/item/defaultNodes/index.en.md | 12 + .../examples/item/defaultNodes/index.zh.md | 10 + .../examples/item/label/demo/copyLabel.js | 290 +++ .../site/examples/item/label/demo/labelLen.js | 125 ++ .../examples/item/label/demo/labelLen1.js | 100 + .../site/examples/item/label/demo/meta.json | 32 + packages/site/examples/item/label/index.en.md | 10 + packages/site/examples/item/label/index.zh.md | 10 + .../site/examples/item/labelBg/demo/edgeBg.js | 88 + .../site/examples/item/labelBg/demo/meta.json | 24 + .../site/examples/item/labelBg/demo/nodeBg.js | 83 + .../site/examples/item/labelBg/index.en.md | 47 + .../site/examples/item/labelBg/index.zh.md | 49 + .../examples/item/multiEdge/demo/meta.json | 16 + .../item/multiEdge/demo/multiEdges.js | 91 + .../site/examples/item/multiEdge/index.en.md | 6 + .../site/examples/item/multiEdge/index.zh.md | 6 + packages/site/examples/net/aiLayout/API.en.md | 5 + packages/site/examples/net/aiLayout/API.zh.md | 5 + .../net/aiLayout/demo/layoutPrediction.js | 159 ++ .../site/examples/net/aiLayout/demo/meta.json | 16 + .../site/examples/net/aiLayout/index.en.md | 26 + .../site/examples/net/aiLayout/index.zh.md | 26 + .../net/arcDiagram/demo/basicArcDiagram.js | 156 ++ .../net/arcDiagram/demo/circularArcDiagram.js | 145 ++ .../examples/net/arcDiagram/demo/meta.json | 24 + .../site/examples/net/arcDiagram/index.en.md | 11 + .../site/examples/net/arcDiagram/index.zh.md | 11 + packages/site/examples/net/circular/API.en.md | 5 + packages/site/examples/net/circular/API.zh.md | 5 + .../net/circular/demo/basicCircular.js | 412 ++++ .../demo/circularConfigurationTranslate.js | 500 +++++ .../net/circular/demo/degreeCircular.js | 421 ++++ .../net/circular/demo/divisionCircular.js | 424 ++++ .../site/examples/net/circular/demo/meta.json | 48 + .../net/circular/demo/spiralCircular.js | 422 ++++ .../site/examples/net/circular/index.en.md | 16 + .../site/examples/net/circular/index.zh.md | 16 + .../site/examples/net/comboLayout/API.en.md | 11 + .../site/examples/net/comboLayout/API.zh.md | 11 + .../net/comboLayout/demo/basicComboForce.js | 473 +++++ .../net/comboLayout/demo/comboCombined.js | 641 ++++++ .../examples/net/comboLayout/demo/meta.json | 24 + .../site/examples/net/comboLayout/index.en.md | 12 + .../site/examples/net/comboLayout/index.zh.md | 12 + .../examples/net/concentricLayout/API.en.md | 5 + .../examples/net/concentricLayout/API.zh.md | 5 + .../concentricLayout/demo/basicConcentric.js | 36 + .../net/concentricLayout/demo/meta.json | 16 + .../examples/net/concentricLayout/index.en.md | 10 + .../examples/net/concentricLayout/index.zh.md | 10 + .../site/examples/net/dagreFlow/API.en.md | 5 + .../site/examples/net/dagreFlow/API.zh.md | 5 + .../examples/net/dagreFlow/demo/basicDagre.js | 310 +++ .../examples/net/dagreFlow/demo/dagreCombo.js | 233 +++ .../demo/dagreConfigurationTranslate.js | 273 +++ .../examples/net/dagreFlow/demo/lrDagre.js | 215 +++ .../examples/net/dagreFlow/demo/lrDagreUL.js | 146 ++ .../examples/net/dagreFlow/demo/meta.json | 48 + .../site/examples/net/dagreFlow/index.en.md | 16 + .../site/examples/net/dagreFlow/index.zh.md | 16 + .../site/examples/net/forceDirected/API.en.md | 5 + .../site/examples/net/forceDirected/API.zh.md | 11 + .../net/forceDirected/demo/basicFA2.js | 42 + .../net/forceDirected/demo/basicForce2.js | 505 +++++ .../forceDirected/demo/basicForceDirected.js | 56 + .../demo/basicForceDirectedDragFix.js | 136 ++ .../net/forceDirected/demo/forceBubbles.js | 255 +++ .../net/forceDirected/demo/forceClustering.js | 54 + .../demo/forceConstrainedInRect.js | 90 + .../forceDirectedConfigurationTranslate.js | 442 +++++ .../demo/forceDirectedFunctionalParams.js | 112 ++ .../demo/forceDirectedPreventOverlap.js | 62 + .../net/forceDirected/demo/gForceFix.js | 61 + .../examples/net/forceDirected/demo/meta.json | 96 + .../examples/net/forceDirected/index.en.md | 18 + .../examples/net/forceDirected/index.zh.md | 18 + .../examples/net/furchtermanLayout/API.en.md | 5 + .../examples/net/furchtermanLayout/API.zh.md | 5 + .../demo/basicFruchterman.js | 452 +++++ .../demo/fruchtermanClustering.js | 499 +++++ .../demo/fruchtermanConfigurationTranslate.js | 542 ++++++ .../furchtermanLayout/demo/fruchtermanFix.js | 61 + .../demo/fruchtermanWebWorker.js | 50 + .../net/furchtermanLayout/demo/meta.json | 48 + .../net/furchtermanLayout/index.en.md | 15 + .../net/furchtermanLayout/index.zh.md | 15 + .../site/examples/net/gpuLayout/API.en.md | 5 + .../site/examples/net/gpuLayout/API.zh.md | 12 + .../net/gpuLayout/demo/basicFruchterman.js | 464 +++++ .../net/gpuLayout/demo/basicGForce.js | 179 ++ .../net/gpuLayout/demo/frComplexDataWorker.js | 61 + .../gpuLayout/demo/fruchtermanClustering.js | 503 +++++ .../gpuLayout/demo/fruchtermanComplexData.js | 50 + .../demo/fruchtermanConfigurationTranslate.js | 546 ++++++ .../examples/net/gpuLayout/demo/gForceCPU.js | 166 ++ .../net/gpuLayout/demo/gForceComplexData.js | 51 + .../examples/net/gpuLayout/demo/meta.json | 72 + .../site/examples/net/gpuLayout/index.en.md | 13 + .../site/examples/net/gpuLayout/index.zh.md | 13 + .../site/examples/net/gridLayout/API.en.md | 5 + .../site/examples/net/gridLayout/API.zh.md | 5 + .../examples/net/gridLayout/demo/basicGrid.js | 415 ++++ .../net/gridLayout/demo/clusterGrid.js | 490 +++++ .../examples/net/gridLayout/demo/meta.json | 24 + .../site/examples/net/gridLayout/index.en.md | 13 + .../site/examples/net/gridLayout/index.zh.md | 13 + .../examples/net/layoutMechanism/API.en.md | 5 + .../examples/net/layoutMechanism/API.zh.md | 5 + .../net/layoutMechanism/demo/customBigraph.js | 249 +++ .../net/layoutMechanism/demo/dataChange.js | 482 +++++ .../net/layoutMechanism/demo/layoutTiming.js | 133 ++ .../layoutMechanism/demo/layoutTranslate.js | 78 + .../net/layoutMechanism/demo/meta.json | 56 + .../layoutMechanism/demo/subgraphLayout.js | 475 +++++ .../layoutMechanism/demo/sublayoutPipes.js | 161 ++ .../examples/net/layoutMechanism/index.en.md | 14 + .../examples/net/layoutMechanism/index.zh.md | 14 + .../site/examples/net/mdsLayout/API.en.md | 5 + .../site/examples/net/mdsLayout/API.zh.md | 5 + .../examples/net/mdsLayout/demo/basicMDS.js | 413 ++++ .../examples/net/mdsLayout/demo/meta.json | 16 + .../site/examples/net/mdsLayout/index.en.md | 10 + .../site/examples/net/mdsLayout/index.zh.md | 10 + .../site/examples/net/radialLayout/API.en.md | 5 + .../site/examples/net/radialLayout/API.zh.md | 5 + .../net/radialLayout/demo/basicRadial.js | 412 ++++ .../net/radialLayout/demo/interactRadial.js | 791 ++++++++ .../examples/net/radialLayout/demo/meta.json | 56 + .../radialLayout/demo/preventOverlapRadial.js | 425 ++++ .../demo/preventOverlapUnstrictRadial.js | 424 ++++ .../demo/radialConfigurationTranslate.js | 478 +++++ .../net/radialLayout/demo/sortRadial.js | 469 +++++ .../examples/net/radialLayout/index.en.md | 17 + .../examples/net/radialLayout/index.zh.md | 17 + .../examples/performance/perf/demo/eva.js | 120 ++ .../examples/performance/perf/demo/meta.json | 32 + .../performance/perf/demo/moreData.js | 76 + .../performance/perf/demo/netscience.js | 101 + .../examples/performance/perf/index.en.md | 6 + .../examples/performance/perf/index.zh.md | 6 + .../scatter/changePosition/demo/default.js | 64 + .../scatter/changePosition/demo/meta.json | 16 + .../scatter/changePosition/index.en.md | 10 + .../scatter/changePosition/index.zh.md | 12 + .../scatter/customAnimate/demo/meta.json | 16 + .../scatter/customAnimate/demo/position.js | 67 + .../scatter/customAnimate/index.en.md | 12 + .../scatter/customAnimate/index.zh.md | 12 + packages/site/examples/scatter/edge/API.en.md | 7 + packages/site/examples/scatter/edge/API.zh.md | 7 + .../scatter/edge/demo/arrowAnimate.js | 116 ++ .../site/examples/scatter/edge/demo/edge.js | 89 + .../examples/scatter/edge/demo/lineGrowth.js | 83 + .../site/examples/scatter/edge/demo/meta.json | 40 + .../examples/scatter/edge/demo/pointInLine.js | 99 + .../site/examples/scatter/edge/index.en.md | 16 + .../site/examples/scatter/edge/index.zh.md | 16 + packages/site/examples/scatter/node/API.en.md | 7 + packages/site/examples/scatter/node/API.zh.md | 7 + .../site/examples/scatter/node/demo/meta.json | 16 + .../site/examples/scatter/node/demo/node.js | 249 +++ .../site/examples/scatter/node/index.en.md | 16 + .../site/examples/scatter/node/index.zh.md | 16 + .../examples/scatter/stateChange/API.en.md | 11 + .../examples/scatter/stateChange/API.zh.md | 11 + .../scatter/stateChange/demo/hover.js | 183 ++ .../scatter/stateChange/demo/meta.json | 16 + .../examples/scatter/stateChange/index.en.md | 12 + .../examples/scatter/stateChange/index.zh.md | 12 + .../site/examples/scatter/viewport/API.en.md | 5 + .../site/examples/scatter/viewport/API.zh.md | 5 + .../examples/scatter/viewport/demo/default.js | 115 ++ .../examples/scatter/viewport/demo/meta.json | 16 + .../examples/scatter/viewport/index.en.md | 10 + .../examples/scatter/viewport/index.zh.md | 10 + .../site/examples/tool/contextMenu/API.en.md | 5 + .../site/examples/tool/contextMenu/API.zh.md | 5 + .../tool/contextMenu/demo/contextMenu.js | 150 ++ .../examples/tool/contextMenu/demo/meta.json | 16 + .../examples/tool/contextMenu/index.en.md | 27 + .../examples/tool/contextMenu/index.zh.md | 26 + .../site/examples/tool/edgeBundling/API.en.md | 5 + .../site/examples/tool/edgeBundling/API.zh.md | 5 + .../tool/edgeBundling/demo/edgeBundling.js | 427 ++++ .../examples/tool/edgeBundling/demo/meta.json | 16 + .../examples/tool/edgeBundling/index.en.md | 10 + .../examples/tool/edgeBundling/index.zh.md | 10 + .../examples/tool/edgeFilterLens/API.en.md | 5 + .../examples/tool/edgeFilterLens/API.zh.md | 5 + .../tool/edgeFilterLens/demo/edgeFilter.js | 178 ++ .../edgeFilterLens/demo/edgeFilterConfig.js | 188 ++ .../tool/edgeFilterLens/demo/meta.json | 24 + .../examples/tool/edgeFilterLens/index.en.md | 10 + .../examples/tool/edgeFilterLens/index.zh.md | 10 + packages/site/examples/tool/fisheye/API.en.md | 5 + packages/site/examples/tool/fisheye/API.zh.md | 5 + .../examples/tool/fisheye/demo/fisheye.js | 193 ++ .../tool/fisheye/demo/fisheyeStyle.js | 105 + .../site/examples/tool/fisheye/demo/meta.json | 24 + .../site/examples/tool/fisheye/index.en.md | 10 + .../site/examples/tool/fisheye/index.zh.md | 10 + packages/site/examples/tool/legend/API.en.md | 5 + packages/site/examples/tool/legend/API.zh.md | 5 + .../site/examples/tool/legend/demo/legend.js | 245 +++ .../examples/tool/legend/demo/legendClick.js | 609 ++++++ .../tool/legend/demo/legendMouseenter.js | 244 +++ .../site/examples/tool/legend/demo/meta.json | 32 + .../site/examples/tool/legend/index.en.md | 27 + .../site/examples/tool/legend/index.zh.md | 26 + packages/site/examples/tool/minimap/API.en.md | 5 + packages/site/examples/tool/minimap/API.zh.md | 5 + .../tool/minimap/demo/imageMinimap.js | 79 + .../site/examples/tool/minimap/demo/meta.json | 24 + .../examples/tool/minimap/demo/minimap.js | 224 +++ .../site/examples/tool/minimap/index.en.md | 19 + .../site/examples/tool/minimap/index.zh.md | 19 + .../site/examples/tool/snapline/API.en.md | 5 + .../site/examples/tool/snapline/API.zh.md | 5 + .../examples/tool/snapline/demo/custom.js | 202 ++ .../examples/tool/snapline/demo/default.js | 196 ++ .../examples/tool/snapline/demo/meta.json | 24 + .../site/examples/tool/snapline/index.en.md | 6 + .../site/examples/tool/snapline/index.zh.md | 6 + packages/site/examples/tool/timebar/API.en.md | 5 + packages/site/examples/tool/timebar/API.zh.md | 5 + .../site/examples/tool/timebar/demo/meta.json | 32 + .../tool/timebar/demo/simple-timebar.js | 110 ++ .../tool/timebar/demo/slice-timebar.js | 131 ++ .../examples/tool/timebar/demo/timebar.js | 110 ++ .../site/examples/tool/timebar/index.en.md | 10 + .../site/examples/tool/timebar/index.zh.md | 52 + packages/site/examples/tool/toolbar/API.en.md | 5 + packages/site/examples/tool/toolbar/API.zh.md | 5 + .../site/examples/tool/toolbar/demo/meta.json | 24 + .../tool/toolbar/demo/self-toolbar.js | 143 ++ .../examples/tool/toolbar/demo/toolbar.js | 109 ++ .../site/examples/tool/toolbar/index.en.md | 16 + .../site/examples/tool/toolbar/index.zh.md | 16 + packages/site/examples/tool/tooltip/API.en.md | 5 + packages/site/examples/tool/tooltip/API.zh.md | 5 + .../site/examples/tool/tooltip/demo/meta.json | 48 + .../tool/tooltip/demo/tooltipClick.js | 131 ++ .../examples/tool/tooltip/demo/tooltipFix.js | 131 ++ .../tool/tooltip/demo/tooltipLocalCustom.js | 174 ++ .../tool/tooltip/demo/tooltipPlugin.js | 136 ++ .../tool/tooltip/demo/tooltipPluginLocal.js | 151 ++ .../site/examples/tool/tooltip/index.en.md | 25 + .../site/examples/tool/tooltip/index.zh.md | 25 + .../site/examples/tree/compactBox/API.en.md | 5 + .../site/examples/tree/compactBox/API.zh.md | 5 + .../tree/compactBox/demo/basicCompactBox.js | 78 + .../compactBox/demo/compactBoxLeftAlign.js | 130 ++ .../examples/tree/compactBox/demo/meta.json | 32 + .../tree/compactBox/demo/tbCompactBox.js | 89 + .../site/examples/tree/compactBox/index.en.md | 14 + .../site/examples/tree/compactBox/index.zh.md | 14 + .../examples/tree/customItemTree/API.en.md | 5 + .../examples/tree/customItemTree/API.zh.md | 5 + .../customItemTree/demo/customEdgeTree.js | 360 ++++ .../tree/customItemTree/demo/customTree.js | 142 ++ .../tree/customItemTree/demo/meta.json | 32 + .../tree/customItemTree/demo/treeEdgeLabel.js | 165 ++ .../examples/tree/customItemTree/index.en.md | 14 + .../examples/tree/customItemTree/index.zh.md | 14 + .../site/examples/tree/dendrogram/API.en.md | 5 + .../site/examples/tree/dendrogram/API.zh.md | 5 + .../tree/dendrogram/demo/basicDendrogram.js | 65 + .../examples/tree/dendrogram/demo/meta.json | 24 + .../tree/dendrogram/demo/tbDendrogram.js | 76 + .../site/examples/tree/dendrogram/index.en.md | 12 + .../site/examples/tree/dendrogram/index.zh.md | 10 + .../site/examples/tree/indented/API.en.md | 5 + .../site/examples/tree/indented/API.zh.md | 5 + .../examples/tree/indented/demo/filesystem.js | 197 ++ .../examples/tree/indented/demo/hIntended.js | 72 + .../tree/indented/demo/intendAlignTop.js | 159 ++ .../examples/tree/indented/demo/meta.json | 32 + .../site/examples/tree/indented/index.en.md | 12 + .../site/examples/tree/indented/index.zh.md | 12 + packages/site/examples/tree/mindmap/API.en.md | 5 + packages/site/examples/tree/mindmap/API.zh.md | 5 + .../tree/mindmap/demo/hCustomSideMindmap.js | 91 + .../tree/mindmap/demo/hLeftMindmap.js | 88 + .../examples/tree/mindmap/demo/hMindmap.js | 85 + .../tree/mindmap/demo/hRightMindmap.js | 88 + .../site/examples/tree/mindmap/demo/meta.json | 40 + .../site/examples/tree/mindmap/index.en.md | 12 + .../site/examples/tree/mindmap/index.zh.md | 24 + .../site/examples/tree/radialtree/API.en.md | 11 + .../site/examples/tree/radialtree/API.zh.md | 11 + .../examples/tree/radialtree/demo/meta.json | 24 + .../tree/radialtree/demo/radialCompactBox.js | 69 + .../tree/radialtree/demo/radialDendrogram.js | 56 + .../site/examples/tree/radialtree/index.en.md | 12 + .../site/examples/tree/radialtree/index.zh.md | 12 + packages/site/package.json | 54 + 983 files changed, 112400 insertions(+) create mode 100644 .babelrc.js create mode 100644 .browserslistrc create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 .fatherrc.js create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report_chinese.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/gitleaks.yml create mode 100644 .github/workflows/preview.yml create mode 100644 .github/workflows/rebase.yml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .npmignore create mode 100644 .prettierignore create mode 100644 .prettierrc.js create mode 100644 .travis.yml create mode 100644 .vscode/settings.json create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.en-US.md create mode 100644 README.md create mode 100644 lerna.json create mode 100644 package.json create mode 100644 packages/core/.eslintignore create mode 100644 packages/core/.fatherrc.js create mode 100644 packages/core/.npmignore create mode 100644 packages/core/jest.config.js create mode 100644 packages/core/package.json create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/src/interface/index.ts create mode 100644 packages/core/src/types/index.ts create mode 100644 packages/core/src/util/index.ts create mode 100644 packages/core/tests/unit/template-spec.ts create mode 100644 packages/core/tsconfig.json create mode 100644 packages/element/.eslintignore create mode 100644 packages/element/.fatherrc.js create mode 100644 packages/element/.npmignore create mode 100644 packages/element/.prettierignore create mode 100644 packages/element/.prettierrc.js create mode 100644 packages/element/jest.config.js create mode 100644 packages/element/package.json create mode 100644 packages/element/src/edges/index.ts create mode 100644 packages/element/src/index.ts create mode 100644 packages/element/src/nodes/index.ts create mode 100644 packages/element/tsconfig.json create mode 100644 packages/g6/.babelrc.js create mode 100644 packages/g6/.eslintignore create mode 100644 packages/g6/.fatherrc.js create mode 100644 packages/g6/.npmignore create mode 100644 packages/g6/package.json create mode 100644 packages/g6/src/index.ts create mode 100644 packages/g6/tsconfig.json create mode 100644 packages/g6/webpack.config.js create mode 100644 packages/g6/webpack.dev.config.js create mode 100644 packages/pc/.babelrc.js create mode 100644 packages/pc/.eslintignore create mode 100644 packages/pc/.fatherrc.js create mode 100644 packages/pc/.npmignore create mode 100644 packages/pc/global.d.ts create mode 100644 packages/pc/jest.config.js create mode 100644 packages/pc/package.json create mode 100644 packages/pc/src/index.ts create mode 100644 packages/pc/tests/unit/template-spec.ts create mode 100644 packages/pc/tsconfig.json create mode 100644 packages/pc/webpack.config.js create mode 100644 packages/pc/webpack.dev.config.js create mode 100644 packages/plugin/.eslintignore create mode 100644 packages/plugin/.fatherrc.js create mode 100644 packages/plugin/.npmignore create mode 100644 packages/plugin/jest.config.js create mode 100644 packages/plugin/package.json create mode 100644 packages/plugin/src/index.ts create mode 100644 packages/plugin/tsconfig.json create mode 100755 packages/react-node/.editorconfig create mode 100644 packages/react-node/.fatherrc.ts create mode 100644 packages/react-node/.gitignore create mode 100644 packages/react-node/.npmignore create mode 100644 packages/react-node/.prettierignore create mode 100644 packages/react-node/.prettierrc create mode 100644 packages/react-node/.umirc.ts create mode 100644 packages/react-node/README.md create mode 100644 packages/react-node/docs/index.en-US.md create mode 100644 packages/react-node/docs/index.md create mode 100644 packages/react-node/package.json create mode 100644 packages/react-node/src/API/CircleStyle.tsx create mode 100644 packages/react-node/src/API/EllipseStyle.tsx create mode 100644 packages/react-node/src/API/Event.tsx create mode 100644 packages/react-node/src/API/ImageStyle.tsx create mode 100644 packages/react-node/src/API/MarkerStyle.tsx create mode 100644 packages/react-node/src/API/PathStyle.tsx create mode 100644 packages/react-node/src/API/PolygonStyle.tsx create mode 100644 packages/react-node/src/API/RectStyle.tsx create mode 100644 packages/react-node/src/API/TextStyle.tsx create mode 100644 packages/react-node/src/API/circle.en-US.md create mode 100644 packages/react-node/src/API/circle.md create mode 100644 packages/react-node/src/API/ellipse.en-US.md create mode 100644 packages/react-node/src/API/ellipse.md create mode 100644 packages/react-node/src/API/event.en-US.md create mode 100644 packages/react-node/src/API/event.md create mode 100644 packages/react-node/src/API/image.en-US.md create mode 100644 packages/react-node/src/API/image.md create mode 100644 packages/react-node/src/API/index.en-US.md create mode 100644 packages/react-node/src/API/index.md create mode 100644 packages/react-node/src/API/marker.en-US.md create mode 100644 packages/react-node/src/API/marker.md create mode 100644 packages/react-node/src/API/path.en-US.md create mode 100644 packages/react-node/src/API/path.md create mode 100644 packages/react-node/src/API/polygon.en-US.md create mode 100644 packages/react-node/src/API/polygon.md create mode 100644 packages/react-node/src/API/rect.en-US.md create mode 100644 packages/react-node/src/API/rect.md create mode 100644 packages/react-node/src/API/text.en-US.md create mode 100644 packages/react-node/src/API/text.md create mode 100644 packages/react-node/src/Animation/animate.ts create mode 100644 packages/react-node/src/Animation/animateFunc.ts create mode 100644 packages/react-node/src/Example/animate.md create mode 100644 packages/react-node/src/Example/card.en-US.md create mode 100644 packages/react-node/src/Example/card.md create mode 100644 packages/react-node/src/Example/event.en-US.md create mode 100644 packages/react-node/src/Example/event.md create mode 100644 packages/react-node/src/Layout/LayoutEnums.ts create mode 100644 packages/react-node/src/Layout/getPositionsUsingYoga.ts create mode 100644 packages/react-node/src/Layout/getShapeSize.ts create mode 100644 packages/react-node/src/Loading/index.tsx create mode 100644 packages/react-node/src/ReactNode/Group.tsx create mode 100644 packages/react-node/src/ReactNode/Shape/Circle.tsx create mode 100644 packages/react-node/src/ReactNode/Shape/Ellipse.tsx create mode 100644 packages/react-node/src/ReactNode/Shape/Image.tsx create mode 100644 packages/react-node/src/ReactNode/Shape/Marker.tsx create mode 100644 packages/react-node/src/ReactNode/Shape/Path.tsx create mode 100644 packages/react-node/src/ReactNode/Shape/Polygon.tsx create mode 100644 packages/react-node/src/ReactNode/Shape/Rect.tsx create mode 100644 packages/react-node/src/ReactNode/Shape/Text.tsx create mode 100644 packages/react-node/src/ReactNode/Shape/common.ts create mode 100644 packages/react-node/src/ReactNode/demo.tsx create mode 100644 packages/react-node/src/ReactNode/index.ts create mode 100644 packages/react-node/src/Register/event.ts create mode 100644 packages/react-node/src/Register/getDataFromReactNode.ts create mode 100644 packages/react-node/src/Register/register.tsx create mode 100644 packages/react-node/src/index.ts create mode 100644 packages/react-node/test/react.test.tsx create mode 100644 packages/react-node/tsconfig.json create mode 100644 packages/react-node/typings.d.ts create mode 100644 packages/site/.dumi/global.ts create mode 100644 packages/site/.dumirc.ts create mode 100644 packages/site/.eslintignore create mode 100644 packages/site/.eslintrc.js create mode 100644 packages/site/.github/workflows/mirror.yml create mode 100644 packages/site/.gitignore create mode 100644 packages/site/CNAME create mode 100644 packages/site/api-extractor.json create mode 100644 packages/site/docs/api/Algorithm.en.md create mode 100644 packages/site/docs/api/Algorithm.zh.md create mode 100644 packages/site/docs/api/Behavior.en.md create mode 100644 packages/site/docs/api/Behavior.zh.md create mode 100644 packages/site/docs/api/Event.en.md create mode 100644 packages/site/docs/api/Event.zh.md create mode 100644 packages/site/docs/api/Graph.en.md create mode 100644 packages/site/docs/api/Graph.zh.md create mode 100644 packages/site/docs/api/Group.en.md create mode 100644 packages/site/docs/api/Group.zh.md create mode 100644 packages/site/docs/api/Items/comboMethods.en.md create mode 100644 packages/site/docs/api/Items/comboMethods.zh.md create mode 100644 packages/site/docs/api/Items/comboProperties.en.md create mode 100644 packages/site/docs/api/Items/comboProperties.zh.md create mode 100644 packages/site/docs/api/Items/edgeMethods.en.md create mode 100644 packages/site/docs/api/Items/edgeMethods.zh.md create mode 100644 packages/site/docs/api/Items/edgeProperties.en.md create mode 100644 packages/site/docs/api/Items/edgeProperties.zh.md create mode 100644 packages/site/docs/api/Items/itemMethods.en.md create mode 100644 packages/site/docs/api/Items/itemMethods.zh.md create mode 100644 packages/site/docs/api/Items/itemProperties.en.md create mode 100644 packages/site/docs/api/Items/itemProperties.zh.md create mode 100644 packages/site/docs/api/Items/nodeMethods.en.md create mode 100644 packages/site/docs/api/Items/nodeMethods.zh.md create mode 100644 packages/site/docs/api/Items/nodeProperties.en.md create mode 100644 packages/site/docs/api/Items/nodeProperties.zh.md create mode 100644 packages/site/docs/api/Plugins.en.md create mode 100644 packages/site/docs/api/Plugins.zh.md create mode 100644 packages/site/docs/api/TreeGraph.en.md create mode 100644 packages/site/docs/api/TreeGraph.zh.md create mode 100644 packages/site/docs/api/Util.en.md create mode 100644 packages/site/docs/api/Util.zh.md create mode 100644 packages/site/docs/api/graphFunc/animation.en.md create mode 100644 packages/site/docs/api/graphFunc/animation.zh.md create mode 100644 packages/site/docs/api/graphFunc/behavior.en.md create mode 100644 packages/site/docs/api/graphFunc/behaviors.zh.md create mode 100644 packages/site/docs/api/graphFunc/calculation.en.md create mode 100644 packages/site/docs/api/graphFunc/calculation.zh.md create mode 100644 packages/site/docs/api/graphFunc/combo.en.md create mode 100644 packages/site/docs/api/graphFunc/combo.zh.md create mode 100644 packages/site/docs/api/graphFunc/coordinate.en.md create mode 100644 packages/site/docs/api/graphFunc/coordinate.zh.md create mode 100644 packages/site/docs/api/graphFunc/data.en.md create mode 100644 packages/site/docs/api/graphFunc/data.zh.md create mode 100644 packages/site/docs/api/graphFunc/destroy.en.md create mode 100644 packages/site/docs/api/graphFunc/destroy.zh.md create mode 100644 packages/site/docs/api/graphFunc/download.en.md create mode 100644 packages/site/docs/api/graphFunc/download.zh.md create mode 100644 packages/site/docs/api/graphFunc/find.en.md create mode 100644 packages/site/docs/api/graphFunc/find.zh.md create mode 100644 packages/site/docs/api/graphFunc/get_set.en.md create mode 100644 packages/site/docs/api/graphFunc/get_set.zh.md create mode 100644 packages/site/docs/api/graphFunc/hull.en.md create mode 100644 packages/site/docs/api/graphFunc/hull.zh.md create mode 100644 packages/site/docs/api/graphFunc/item.en.md create mode 100644 packages/site/docs/api/graphFunc/item.zh.md create mode 100644 packages/site/docs/api/graphFunc/layout.en.md create mode 100644 packages/site/docs/api/graphFunc/layout.zh.md create mode 100644 packages/site/docs/api/graphFunc/mode.en.md create mode 100644 packages/site/docs/api/graphFunc/mode.zh.md create mode 100644 packages/site/docs/api/graphFunc/on_off.en.md create mode 100644 packages/site/docs/api/graphFunc/on_off.zh.md create mode 100644 packages/site/docs/api/graphFunc/render.en.md create mode 100644 packages/site/docs/api/graphFunc/render.zh.md create mode 100644 packages/site/docs/api/graphFunc/stack.en.md create mode 100644 packages/site/docs/api/graphFunc/stack.zh.md create mode 100644 packages/site/docs/api/graphFunc/state.en.md create mode 100644 packages/site/docs/api/graphFunc/state.zh.md create mode 100644 packages/site/docs/api/graphFunc/transform.en.md create mode 100644 packages/site/docs/api/graphFunc/transform.zh.md create mode 100644 packages/site/docs/api/graphFunc/watermarker.en.md create mode 100644 packages/site/docs/api/graphFunc/watermarker.zh.md create mode 100644 packages/site/docs/api/graphLayout/circular.en.md create mode 100644 packages/site/docs/api/graphLayout/circular.zh.md create mode 100644 packages/site/docs/api/graphLayout/comboCombined.en.md create mode 100644 packages/site/docs/api/graphLayout/comboCombined.zh.md create mode 100644 packages/site/docs/api/graphLayout/comboForce.en.md create mode 100644 packages/site/docs/api/graphLayout/comboForce.zh.md create mode 100644 packages/site/docs/api/graphLayout/concentric.en.md create mode 100644 packages/site/docs/api/graphLayout/concentric.zh.md create mode 100644 packages/site/docs/api/graphLayout/dagre.en.md create mode 100644 packages/site/docs/api/graphLayout/dagre.zh.md create mode 100644 packages/site/docs/api/graphLayout/force.en.md create mode 100644 packages/site/docs/api/graphLayout/force.zh.md create mode 100644 packages/site/docs/api/graphLayout/force2.en.md create mode 100644 packages/site/docs/api/graphLayout/force2.zh.md create mode 100644 packages/site/docs/api/graphLayout/forceAtlas2.en.md create mode 100644 packages/site/docs/api/graphLayout/forceAtlas2.zh.md create mode 100644 packages/site/docs/api/graphLayout/fruchterman.en.md create mode 100644 packages/site/docs/api/graphLayout/fruchterman.zh.md create mode 100644 packages/site/docs/api/graphLayout/gforce.en.md create mode 100644 packages/site/docs/api/graphLayout/gforce.zh.md create mode 100644 packages/site/docs/api/graphLayout/grid.en.md create mode 100644 packages/site/docs/api/graphLayout/grid.zh.md create mode 100644 packages/site/docs/api/graphLayout/guide.en.md create mode 100644 packages/site/docs/api/graphLayout/guide.zh.md create mode 100644 packages/site/docs/api/graphLayout/mds.en.md create mode 100644 packages/site/docs/api/graphLayout/mds.zh.md create mode 100644 packages/site/docs/api/graphLayout/radial.en.md create mode 100644 packages/site/docs/api/graphLayout/radial.zh.md create mode 100644 packages/site/docs/api/graphLayout/random.en.md create mode 100644 packages/site/docs/api/graphLayout/random.zh.md create mode 100644 packages/site/docs/api/registerItem.en.md create mode 100644 packages/site/docs/api/registerItem.zh.md create mode 100644 packages/site/docs/api/registerLayout.en.md create mode 100644 packages/site/docs/api/registerLayout.zh.md create mode 100644 packages/site/docs/api/shapeMethods.en.md create mode 100644 packages/site/docs/api/shapeMethods.zh.md create mode 100644 packages/site/docs/api/shapeProperties.en.md create mode 100644 packages/site/docs/api/shapeProperties.zh.md create mode 100644 packages/site/docs/api/treeGraphLayout/compactBox.en.md create mode 100644 packages/site/docs/api/treeGraphLayout/compactBox.zh.md create mode 100644 packages/site/docs/api/treeGraphLayout/dendrogram.en.md create mode 100644 packages/site/docs/api/treeGraphLayout/dendrogram.zh.md create mode 100644 packages/site/docs/api/treeGraphLayout/guide.en.md create mode 100644 packages/site/docs/api/treeGraphLayout/guide.zh.md create mode 100644 packages/site/docs/api/treeGraphLayout/indented.en.md create mode 100644 packages/site/docs/api/treeGraphLayout/indented.zh.md create mode 100644 packages/site/docs/api/treeGraphLayout/mindmap.en.md create mode 100644 packages/site/docs/api/treeGraphLayout/mindmap.zh.md create mode 100644 packages/site/docs/api/treeMethods.en.md create mode 100644 packages/site/docs/api/treeMethods.zh.md create mode 100644 packages/site/docs/design/case.en.md create mode 100644 packages/site/docs/design/case.zh.md create mode 100644 packages/site/docs/design/component/componentOverview.en.md create mode 100644 packages/site/docs/design/component/componentOverview.zh.md create mode 100644 packages/site/docs/design/component/timebar.en.md create mode 100644 packages/site/docs/design/component/timebar.zh.md create mode 100644 packages/site/docs/design/component/viewToolbar.en.md create mode 100644 packages/site/docs/design/component/viewToolbar.zh.md create mode 100644 packages/site/docs/design/global/Interactive.zh.md create mode 100644 packages/site/docs/design/global/interactive.en.md create mode 100644 packages/site/docs/design/global/style.en.md create mode 100644 packages/site/docs/design/global/style.zh.md create mode 100644 packages/site/docs/design/overview.en.md create mode 100644 packages/site/docs/design/overview.zh.md create mode 100644 packages/site/docs/design/principles.en.md create mode 100644 packages/site/docs/design/principles.zh.md create mode 100644 packages/site/docs/design/template.en.md create mode 100644 packages/site/docs/design/template.zh.md create mode 100644 packages/site/docs/manual/FAQ/angular-support.en.md create mode 100644 packages/site/docs/manual/FAQ/angular-support.zh.md create mode 100644 packages/site/docs/manual/FAQ/build-error-rollup.en.md create mode 100644 packages/site/docs/manual/FAQ/build-error-rollup.zh.md create mode 100644 packages/site/docs/manual/FAQ/build-error.en.md create mode 100644 packages/site/docs/manual/FAQ/build-error.zh.md create mode 100644 packages/site/docs/manual/FAQ/faq.en.md create mode 100644 packages/site/docs/manual/FAQ/faq.zh.md create mode 100644 packages/site/docs/manual/FAQ/performance-opt.en.md create mode 100644 packages/site/docs/manual/FAQ/performance-opt.zh.md create mode 100644 packages/site/docs/manual/FAQ/supportIE.en.md create mode 100644 packages/site/docs/manual/FAQ/supportIE.zh.md create mode 100644 packages/site/docs/manual/FAQ/upgradeGuide.en.md create mode 100644 packages/site/docs/manual/FAQ/upgradeGuide.zh.md create mode 100644 packages/site/docs/manual/FAQ/vite-support.en.md create mode 100644 packages/site/docs/manual/FAQ/vite-support.zh.md create mode 100644 packages/site/docs/manual/advanced/comboTheory.en.md create mode 100644 packages/site/docs/manual/advanced/comboTheory.zh.md create mode 100644 packages/site/docs/manual/advanced/coordinate-system.en.md create mode 100644 packages/site/docs/manual/advanced/coordinate-system.zh.md create mode 100644 packages/site/docs/manual/advanced/g6InReact.en.md create mode 100644 packages/site/docs/manual/advanced/g6InReact.zh.md create mode 100644 packages/site/docs/manual/advanced/iconfont.en.md create mode 100644 packages/site/docs/manual/advanced/iconfont.zh.md create mode 100644 packages/site/docs/manual/advanced/mode-and-custom-behavior.en.md create mode 100644 packages/site/docs/manual/advanced/mode-and-custom-behavior.zh.md create mode 100644 packages/site/docs/manual/advanced/state-new.en.md create mode 100644 packages/site/docs/manual/advanced/state-new.zh.md create mode 100644 packages/site/docs/manual/cases/edgeBundling.en.md create mode 100644 packages/site/docs/manual/cases/edgeBundling.zh.md create mode 100644 packages/site/docs/manual/cases/relations.en.md create mode 100644 packages/site/docs/manual/cases/relations.zh.md create mode 100644 packages/site/docs/manual/cases/sequenceTime.en.md create mode 100644 packages/site/docs/manual/cases/sequenceTime.zh.md create mode 100644 packages/site/docs/manual/getting-started.en.md create mode 100644 packages/site/docs/manual/getting-started.zh.md create mode 100644 packages/site/docs/manual/introduction.en.md create mode 100644 packages/site/docs/manual/introduction.zh.md create mode 100644 packages/site/docs/manual/middle/animation.en.md create mode 100644 packages/site/docs/manual/middle/animation.zh.md create mode 100644 packages/site/docs/manual/middle/elements/advanced-style/gradient.en.md create mode 100644 packages/site/docs/manual/middle/elements/advanced-style/gradient.zh.md create mode 100644 packages/site/docs/manual/middle/elements/advanced-style/set-label-bg.en.md create mode 100644 packages/site/docs/manual/middle/elements/advanced-style/set-label-bg.zh.md create mode 100644 packages/site/docs/manual/middle/elements/advanced-style/texture.en.md create mode 100644 packages/site/docs/manual/middle/elements/advanced-style/texture.zh.md create mode 100644 packages/site/docs/manual/middle/elements/advanced-style/updateText.en.md create mode 100644 packages/site/docs/manual/middle/elements/advanced-style/updateText.zh.md create mode 100644 packages/site/docs/manual/middle/elements/combos/built-in/circle.en.md create mode 100644 packages/site/docs/manual/middle/elements/combos/built-in/circle.zh.md create mode 100644 packages/site/docs/manual/middle/elements/combos/built-in/rect.en.md create mode 100644 packages/site/docs/manual/middle/elements/combos/built-in/rect.zh.md create mode 100644 packages/site/docs/manual/middle/elements/combos/custom-combo.en.md create mode 100644 packages/site/docs/manual/middle/elements/combos/custom-combo.zh.md create mode 100644 packages/site/docs/manual/middle/elements/combos/defaultCombo.en.md create mode 100644 packages/site/docs/manual/middle/elements/combos/defaultCombo.zh.md create mode 100644 packages/site/docs/manual/middle/elements/edges/arrow.en.md create mode 100644 packages/site/docs/manual/middle/elements/edges/arrow.zh.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/arc.en.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/arc.zh.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/cubic.en.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/cubic.zh.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/line.en.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/line.zh.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/loop.en.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/loop.zh.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/polyline.en.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/polyline.zh.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/quadratic.en.md create mode 100644 packages/site/docs/manual/middle/elements/edges/built-in/quadratic.zh.md create mode 100644 packages/site/docs/manual/middle/elements/edges/custom-edge.en.md create mode 100644 packages/site/docs/manual/middle/elements/edges/custom-edge.zh.md create mode 100644 packages/site/docs/manual/middle/elements/edges/defaultEdge.en.md create mode 100644 packages/site/docs/manual/middle/elements/edges/defaultEdge.zh.md create mode 100644 packages/site/docs/manual/middle/elements/methods/edgeVisible.en.md create mode 100644 packages/site/docs/manual/middle/elements/methods/edgeVisible.zh.md create mode 100644 packages/site/docs/manual/middle/elements/methods/elementIndex.en.md create mode 100644 packages/site/docs/manual/middle/elements/methods/elementIndex.zh.md create mode 100644 packages/site/docs/manual/middle/elements/methods/lock-node.en.md create mode 100644 packages/site/docs/manual/middle/elements/methods/lock-node.zh.md create mode 100644 packages/site/docs/manual/middle/elements/methods/multi-line.en.md create mode 100644 packages/site/docs/manual/middle/elements/methods/multi-line.zh.md create mode 100644 packages/site/docs/manual/middle/elements/methods/updateElement.en.md create mode 100644 packages/site/docs/manual/middle/elements/methods/updateElement.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/anchorpoint.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/anchorpoint.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/circle.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/circle.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/diamond.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/diamond.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/donut.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/donut.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/ellipse.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/ellipse.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/image.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/image.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/modelRect.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/modelRect.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/rect.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/rect.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/star.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/star.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/triangle.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/built-in/triangle.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/custom-node.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/custom-node.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/defaultNode.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/defaultNode.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/jsx-node.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/jsx-node.zh.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/react-node.en.md create mode 100644 packages/site/docs/manual/middle/elements/nodes/react-node.zh.md create mode 100644 packages/site/docs/manual/middle/elements/overview.en.md create mode 100644 packages/site/docs/manual/middle/elements/overview.zh.md create mode 100644 packages/site/docs/manual/middle/elements/shape/graphics-group.en.md create mode 100644 packages/site/docs/manual/middle/elements/shape/graphics-group.zh.md create mode 100644 packages/site/docs/manual/middle/elements/shape/shape-and-properties.en.md create mode 100644 packages/site/docs/manual/middle/elements/shape/shape-and-properties.zh.md create mode 100644 packages/site/docs/manual/middle/elements/shape/shape-keyshape.en.md create mode 100644 packages/site/docs/manual/middle/elements/shape/shape-keyshape.zh.md create mode 100644 packages/site/docs/manual/middle/elements/shape/transform.en.md create mode 100644 packages/site/docs/manual/middle/elements/shape/transform.zh.md create mode 100644 packages/site/docs/manual/middle/graph.en.md create mode 100644 packages/site/docs/manual/middle/graph.zh.md create mode 100644 packages/site/docs/manual/middle/layout/ai-layout.en.md create mode 100644 packages/site/docs/manual/middle/layout/ai-layout.zh.md create mode 100644 packages/site/docs/manual/middle/layout/custom-layout.en.md create mode 100644 packages/site/docs/manual/middle/layout/custom-layout.zh.md create mode 100644 packages/site/docs/manual/middle/layout/graph-layout.en.md create mode 100644 packages/site/docs/manual/middle/layout/graph-layout.zh.md create mode 100644 packages/site/docs/manual/middle/layout/layout-mechanism.en.md create mode 100644 packages/site/docs/manual/middle/layout/layout-mechanism.zh.md create mode 100644 packages/site/docs/manual/middle/layout/sub-layout-pipe.en.md create mode 100644 packages/site/docs/manual/middle/layout/sub-layout-pipe.zh.md create mode 100644 packages/site/docs/manual/middle/layout/sub-layout.en.md create mode 100644 packages/site/docs/manual/middle/layout/sub-layout.zh.md create mode 100644 packages/site/docs/manual/middle/layout/tree-graph-layout.en.md create mode 100644 packages/site/docs/manual/middle/layout/tree-graph-layout.zh.md create mode 100644 packages/site/docs/manual/middle/layout/webworker.en.md create mode 100644 packages/site/docs/manual/middle/layout/webworker.zh.md create mode 100644 packages/site/docs/manual/middle/overview.en.md create mode 100644 packages/site/docs/manual/middle/overview.zh.md create mode 100644 packages/site/docs/manual/middle/plugins/Plugins.en.md create mode 100644 packages/site/docs/manual/middle/plugins/Plugins.zh.md create mode 100644 packages/site/docs/manual/middle/plugins/autoZoomTooltip.en.md create mode 100644 packages/site/docs/manual/middle/plugins/autoZoomTooltip.zh.md create mode 100644 packages/site/docs/manual/middle/states/bindEvent.en.md create mode 100644 packages/site/docs/manual/middle/states/bindEvent.zh.md create mode 100644 packages/site/docs/manual/middle/states/custom-behavior.en.md create mode 100644 packages/site/docs/manual/middle/states/custom-behavior.zh.md create mode 100644 packages/site/docs/manual/middle/states/defaultBehavior.en.md create mode 100644 packages/site/docs/manual/middle/states/defaultBehavior.zh.md create mode 100644 packages/site/docs/manual/middle/states/mode.en.md create mode 100644 packages/site/docs/manual/middle/states/mode.zh.md create mode 100644 packages/site/docs/manual/middle/states/state.en.md create mode 100644 packages/site/docs/manual/middle/states/state.zh.md create mode 100644 packages/site/docs/manual/tutorial/animation.en.md create mode 100644 packages/site/docs/manual/tutorial/animation.zh.md create mode 100644 packages/site/docs/manual/tutorial/behavior.en.md create mode 100644 packages/site/docs/manual/tutorial/behavior.zh.md create mode 100644 packages/site/docs/manual/tutorial/elements.en.md create mode 100644 packages/site/docs/manual/tutorial/elements.zh.md create mode 100644 packages/site/docs/manual/tutorial/epilog.en.md create mode 100644 packages/site/docs/manual/tutorial/epilog.zh.md create mode 100644 packages/site/docs/manual/tutorial/example.en.md create mode 100644 packages/site/docs/manual/tutorial/example.zh.md create mode 100644 packages/site/docs/manual/tutorial/layout.en.md create mode 100644 packages/site/docs/manual/tutorial/layout.zh.md create mode 100644 packages/site/docs/manual/tutorial/plugins.en.md create mode 100644 packages/site/docs/manual/tutorial/plugins.zh.md create mode 100644 packages/site/docs/manual/tutorial/preface.en.md create mode 100644 packages/site/docs/manual/tutorial/preface.zh.md create mode 100644 packages/site/examples/algorithm/algoDemos/API.en.md create mode 100644 packages/site/examples/algorithm/algoDemos/API.zh.md create mode 100644 packages/site/examples/algorithm/algoDemos/demo/colorSets.js create mode 100644 packages/site/examples/algorithm/algoDemos/demo/gaddi.js create mode 100644 packages/site/examples/algorithm/algoDemos/demo/labelPropagation.js create mode 100644 packages/site/examples/algorithm/algoDemos/demo/louvain.js create mode 100644 packages/site/examples/algorithm/algoDemos/demo/meta.json create mode 100644 packages/site/examples/algorithm/algoDemos/demo/shortestPath.js create mode 100644 packages/site/examples/algorithm/algoDemos/index.en.md create mode 100644 packages/site/examples/algorithm/algoDemos/index.zh.md create mode 100644 packages/site/examples/case/graphDemos/demo/christmasBubbles.js create mode 100644 packages/site/examples/case/graphDemos/demo/customFlow.js create mode 100644 packages/site/examples/case/graphDemos/demo/decisionBubbles.js create mode 100644 packages/site/examples/case/graphDemos/demo/donutTransfer.js create mode 100644 packages/site/examples/case/graphDemos/demo/edgeBundling.js create mode 100644 packages/site/examples/case/graphDemos/demo/graphinsight.js create mode 100644 packages/site/examples/case/graphDemos/demo/largeGraph.js create mode 100644 packages/site/examples/case/graphDemos/demo/meta.json create mode 100644 packages/site/examples/case/graphDemos/demo/sequence.js create mode 100644 packages/site/examples/case/graphDemos/demo/simplifyCluster.js create mode 100644 packages/site/examples/case/graphDemos/index.en.md create mode 100644 packages/site/examples/case/graphDemos/index.zh.md create mode 100644 packages/site/examples/case/others/demo/meta.json create mode 100644 packages/site/examples/case/others/demo/metroLines.js create mode 100644 packages/site/examples/case/others/index.en.md create mode 100644 packages/site/examples/case/others/index.zh.md create mode 100644 packages/site/examples/case/treeDemos/API.en.md create mode 100644 packages/site/examples/case/treeDemos/API.zh.md create mode 100644 packages/site/examples/case/treeDemos/demo/australiaFire.js create mode 100644 packages/site/examples/case/treeDemos/demo/customFlow.js create mode 100644 packages/site/examples/case/treeDemos/demo/decisionTree.js create mode 100644 packages/site/examples/case/treeDemos/demo/indentedTree.js create mode 100644 packages/site/examples/case/treeDemos/demo/knowledgeTreeGraph.js create mode 100644 packages/site/examples/case/treeDemos/demo/meta.json create mode 100644 packages/site/examples/case/treeDemos/demo/mindmap.js create mode 100644 packages/site/examples/case/treeDemos/index.en.md create mode 100644 packages/site/examples/case/treeDemos/index.zh.md create mode 100644 packages/site/examples/interaction/combo/API.en.md create mode 100644 packages/site/examples/interaction/combo/API.zh.md create mode 100644 packages/site/examples/interaction/combo/demo/cCircle.js create mode 100644 packages/site/examples/interaction/combo/demo/cRect.js create mode 100644 packages/site/examples/interaction/combo/demo/circle.js create mode 100644 packages/site/examples/interaction/combo/demo/meta.json create mode 100644 packages/site/examples/interaction/combo/demo/rect.js create mode 100644 packages/site/examples/interaction/combo/index.en.md create mode 100644 packages/site/examples/interaction/combo/index.zh.md create mode 100644 packages/site/examples/interaction/createEdge/API.en.md create mode 100644 packages/site/examples/interaction/createEdge/API.zh.md create mode 100644 packages/site/examples/interaction/createEdge/demo/click-and-key.js create mode 100644 packages/site/examples/interaction/createEdge/demo/click-link-point.js create mode 100644 packages/site/examples/interaction/createEdge/demo/click.js create mode 100644 packages/site/examples/interaction/createEdge/demo/drag-link-point.js create mode 100644 packages/site/examples/interaction/createEdge/demo/drag.js create mode 100644 packages/site/examples/interaction/createEdge/demo/meta.json create mode 100644 packages/site/examples/interaction/createEdge/index.en.md create mode 100644 packages/site/examples/interaction/createEdge/index.zh.md create mode 100644 packages/site/examples/interaction/customBehavior/API.en.md create mode 100644 packages/site/examples/interaction/customBehavior/API.zh.md create mode 100644 packages/site/examples/interaction/customBehavior/demo/dragCanvasTwoFingers.js create mode 100644 packages/site/examples/interaction/customBehavior/demo/meta.json create mode 100644 packages/site/examples/interaction/customBehavior/index.en.md create mode 100644 packages/site/examples/interaction/customBehavior/index.zh.md create mode 100644 packages/site/examples/interaction/dragCanvasHideItem/API.en.md create mode 100644 packages/site/examples/interaction/dragCanvasHideItem/API.zh.md create mode 100644 packages/site/examples/interaction/dragCanvasHideItem/demo/hideItem.js create mode 100644 packages/site/examples/interaction/dragCanvasHideItem/demo/meta.json create mode 100644 packages/site/examples/interaction/dragCanvasHideItem/index.en.md create mode 100644 packages/site/examples/interaction/dragCanvasHideItem/index.zh.md create mode 100644 packages/site/examples/interaction/fitView/API.en.md create mode 100644 packages/site/examples/interaction/fitView/API.zh.md create mode 100644 packages/site/examples/interaction/fitView/demo/fitView.js create mode 100644 packages/site/examples/interaction/fitView/demo/meta.json create mode 100644 packages/site/examples/interaction/fitView/demo/moveTo.js create mode 100644 packages/site/examples/interaction/fitView/index.en.md create mode 100644 packages/site/examples/interaction/fitView/index.zh.md create mode 100644 packages/site/examples/interaction/highlight/API.en.md create mode 100644 packages/site/examples/interaction/highlight/API.zh.md create mode 100644 packages/site/examples/interaction/highlight/demo/activateRelations.js create mode 100644 packages/site/examples/interaction/highlight/demo/highlightDark.js create mode 100644 packages/site/examples/interaction/highlight/demo/meta.json create mode 100644 packages/site/examples/interaction/highlight/index.en.md create mode 100644 packages/site/examples/interaction/highlight/index.zh.md create mode 100644 packages/site/examples/interaction/hull/API.en.md create mode 100644 packages/site/examples/interaction/hull/API.zh.md create mode 100644 packages/site/examples/interaction/hull/demo/changeMembers.js create mode 100644 packages/site/examples/interaction/hull/demo/hull.js create mode 100644 packages/site/examples/interaction/hull/demo/meta.json create mode 100644 packages/site/examples/interaction/hull/index.en.md create mode 100644 packages/site/examples/interaction/hull/index.zh.md create mode 100644 packages/site/examples/interaction/label/API.en.md create mode 100644 packages/site/examples/interaction/label/API.zh.md create mode 100644 packages/site/examples/interaction/label/demo/changeImg.js create mode 100644 packages/site/examples/interaction/label/demo/meta.json create mode 100644 packages/site/examples/interaction/label/demo/update.js create mode 100644 packages/site/examples/interaction/label/index.en.md create mode 100644 packages/site/examples/interaction/label/index.zh.md create mode 100644 packages/site/examples/interaction/pagination/demo/dagrePagination.js create mode 100644 packages/site/examples/interaction/pagination/demo/meta.json create mode 100644 packages/site/examples/interaction/pagination/demo/treePagination.js create mode 100644 packages/site/examples/interaction/pagination/index.en.md create mode 100644 packages/site/examples/interaction/pagination/index.zh.md create mode 100644 packages/site/examples/interaction/partialResponse/API.en.md create mode 100644 packages/site/examples/interaction/partialResponse/API.zh.md create mode 100644 packages/site/examples/interaction/partialResponse/demo/meta.json create mode 100644 packages/site/examples/interaction/partialResponse/demo/partialNode.js create mode 100644 packages/site/examples/interaction/partialResponse/index.en.md create mode 100644 packages/site/examples/interaction/partialResponse/index.zh.md create mode 100644 packages/site/examples/interaction/position/API.en.md create mode 100644 packages/site/examples/interaction/position/API.zh.md create mode 100644 packages/site/examples/interaction/position/demo/meta.json create mode 100644 packages/site/examples/interaction/position/demo/move.js create mode 100644 packages/site/examples/interaction/position/demo/moveAnimate.js create mode 100644 packages/site/examples/interaction/position/index.en.md create mode 100644 packages/site/examples/interaction/position/index.zh.md create mode 100644 packages/site/examples/interaction/select/API.en.md create mode 100644 packages/site/examples/interaction/select/API.zh.md create mode 100644 packages/site/examples/interaction/select/demo/brush.js create mode 100644 packages/site/examples/interaction/select/demo/click.js create mode 100644 packages/site/examples/interaction/select/demo/lasso.js create mode 100644 packages/site/examples/interaction/select/demo/meta.json create mode 100644 packages/site/examples/interaction/select/index.en.md create mode 100644 packages/site/examples/interaction/select/index.zh.md create mode 100644 packages/site/examples/interaction/setMode/API.en.md create mode 100644 packages/site/examples/interaction/setMode/API.zh.md create mode 100644 packages/site/examples/interaction/setMode/demo/meta.json create mode 100644 packages/site/examples/interaction/setMode/demo/setMode.js create mode 100644 packages/site/examples/interaction/setMode/index.en.md create mode 100644 packages/site/examples/interaction/setMode/index.zh.md create mode 100644 packages/site/examples/interaction/treeBehavior/API.en.md create mode 100644 packages/site/examples/interaction/treeBehavior/API.zh.md create mode 100644 packages/site/examples/interaction/treeBehavior/demo/changeData.js create mode 100644 packages/site/examples/interaction/treeBehavior/demo/collapseSlibing.js create mode 100644 packages/site/examples/interaction/treeBehavior/demo/dragSubtree.js create mode 100644 packages/site/examples/interaction/treeBehavior/demo/loadTreeData.js create mode 100644 packages/site/examples/interaction/treeBehavior/demo/meta.json create mode 100644 packages/site/examples/interaction/treeBehavior/index.en.md create mode 100644 packages/site/examples/interaction/treeBehavior/index.zh.md create mode 100644 packages/site/examples/interaction/zoomCanvasFixItem/API.en.md create mode 100644 packages/site/examples/interaction/zoomCanvasFixItem/API.zh.md create mode 100644 packages/site/examples/interaction/zoomCanvasFixItem/demo/fixItem.js create mode 100644 packages/site/examples/interaction/zoomCanvasFixItem/demo/meta.json create mode 100644 packages/site/examples/interaction/zoomCanvasFixItem/index.en.md create mode 100644 packages/site/examples/interaction/zoomCanvasFixItem/index.zh.md create mode 100644 packages/site/examples/item/arrows/API.en.md create mode 100644 packages/site/examples/item/arrows/API.zh.md create mode 100644 packages/site/examples/item/arrows/demo/built-in-arrows.js create mode 100644 packages/site/examples/item/arrows/demo/custom-arrows.js create mode 100644 packages/site/examples/item/arrows/demo/meta.json create mode 100644 packages/site/examples/item/arrows/index.en.md create mode 100644 packages/site/examples/item/arrows/index.zh.md create mode 100644 packages/site/examples/item/customCombo/API.en.md create mode 100644 packages/site/examples/item/customCombo/API.zh.md create mode 100644 packages/site/examples/item/customCombo/demo/cCircle.js create mode 100644 packages/site/examples/item/customCombo/demo/cRect.js create mode 100644 packages/site/examples/item/customCombo/demo/meta.json create mode 100644 packages/site/examples/item/customCombo/index.en.md create mode 100644 packages/site/examples/item/customCombo/index.zh.md create mode 100644 packages/site/examples/item/customEdge/API.en.md create mode 100644 packages/site/examples/item/customEdge/API.zh.md create mode 100644 packages/site/examples/item/customEdge/demo/customPolyline.js create mode 100644 packages/site/examples/item/customEdge/demo/customPolyline2.js create mode 100644 packages/site/examples/item/customEdge/demo/edgeMulLabel.js create mode 100644 packages/site/examples/item/customEdge/demo/extraShape.js create mode 100644 packages/site/examples/item/customEdge/demo/meta.json create mode 100644 packages/site/examples/item/customEdge/index.en.md create mode 100644 packages/site/examples/item/customEdge/index.zh.md create mode 100644 packages/site/examples/item/customNode/API.en.md create mode 100644 packages/site/examples/item/customNode/API.zh.md create mode 100644 packages/site/examples/item/customNode/demo/areaChart.js create mode 100644 packages/site/examples/item/customNode/demo/barChart.js create mode 100644 packages/site/examples/item/customNode/demo/card.js create mode 100644 packages/site/examples/item/customNode/demo/cardNode.js create mode 100644 packages/site/examples/item/customNode/demo/intervalChartNode.js create mode 100644 packages/site/examples/item/customNode/demo/jsxNode.js create mode 100644 packages/site/examples/item/customNode/demo/jsxNodeWithAnimate.js create mode 100644 packages/site/examples/item/customNode/demo/lineChart.js create mode 100644 packages/site/examples/item/customNode/demo/lineChartNode.js create mode 100644 packages/site/examples/item/customNode/demo/list.js create mode 100644 packages/site/examples/item/customNode/demo/meta.json create mode 100644 packages/site/examples/item/customNode/demo/pieChart.js create mode 100644 packages/site/examples/item/customNode/demo/pieChartNode.js create mode 100644 packages/site/examples/item/customNode/demo/pointChart.js create mode 100644 packages/site/examples/item/customNode/demo/pointChartNode.js create mode 100644 packages/site/examples/item/customNode/demo/scrollNode.js create mode 100644 packages/site/examples/item/customNode/demo/stackChart.js create mode 100644 packages/site/examples/item/customNode/demo/svgDom.js create mode 100644 packages/site/examples/item/customNode/index.en.md create mode 100644 packages/site/examples/item/customNode/index.zh.md create mode 100644 packages/site/examples/item/defaultCombos/API.en.md create mode 100644 packages/site/examples/item/defaultCombos/API.zh.md create mode 100644 packages/site/examples/item/defaultCombos/demo/circle.js create mode 100644 packages/site/examples/item/defaultCombos/demo/meta.json create mode 100644 packages/site/examples/item/defaultCombos/demo/rect.js create mode 100644 packages/site/examples/item/defaultCombos/index.en.md create mode 100644 packages/site/examples/item/defaultCombos/index.zh.md create mode 100644 packages/site/examples/item/defaultEdges/API.en.md create mode 100644 packages/site/examples/item/defaultEdges/API.zh.md create mode 100644 packages/site/examples/item/defaultEdges/demo/arc.js create mode 100644 packages/site/examples/item/defaultEdges/demo/cubic1.js create mode 100644 packages/site/examples/item/defaultEdges/demo/cubic2.js create mode 100644 packages/site/examples/item/defaultEdges/demo/loop.js create mode 100644 packages/site/examples/item/defaultEdges/demo/meta.json create mode 100644 packages/site/examples/item/defaultEdges/demo/polyline1.js create mode 100644 packages/site/examples/item/defaultEdges/demo/polyline2.js create mode 100644 packages/site/examples/item/defaultEdges/demo/polyline3.js create mode 100644 packages/site/examples/item/defaultEdges/index.en.md create mode 100644 packages/site/examples/item/defaultEdges/index.zh.md create mode 100644 packages/site/examples/item/defaultNodes/API.en.md create mode 100644 packages/site/examples/item/defaultNodes/API.zh.md create mode 100644 packages/site/examples/item/defaultNodes/demo/circle.js create mode 100644 packages/site/examples/item/defaultNodes/demo/diamond.js create mode 100644 packages/site/examples/item/defaultNodes/demo/donut.js create mode 100644 packages/site/examples/item/defaultNodes/demo/ellipse.js create mode 100644 packages/site/examples/item/defaultNodes/demo/image.js create mode 100644 packages/site/examples/item/defaultNodes/demo/meta.json create mode 100644 packages/site/examples/item/defaultNodes/demo/modelRect.js create mode 100644 packages/site/examples/item/defaultNodes/demo/rect.js create mode 100644 packages/site/examples/item/defaultNodes/demo/star.js create mode 100644 packages/site/examples/item/defaultNodes/demo/triangle.js create mode 100644 packages/site/examples/item/defaultNodes/index.en.md create mode 100644 packages/site/examples/item/defaultNodes/index.zh.md create mode 100644 packages/site/examples/item/label/demo/copyLabel.js create mode 100644 packages/site/examples/item/label/demo/labelLen.js create mode 100644 packages/site/examples/item/label/demo/labelLen1.js create mode 100644 packages/site/examples/item/label/demo/meta.json create mode 100644 packages/site/examples/item/label/index.en.md create mode 100644 packages/site/examples/item/label/index.zh.md create mode 100644 packages/site/examples/item/labelBg/demo/edgeBg.js create mode 100644 packages/site/examples/item/labelBg/demo/meta.json create mode 100644 packages/site/examples/item/labelBg/demo/nodeBg.js create mode 100644 packages/site/examples/item/labelBg/index.en.md create mode 100644 packages/site/examples/item/labelBg/index.zh.md create mode 100644 packages/site/examples/item/multiEdge/demo/meta.json create mode 100644 packages/site/examples/item/multiEdge/demo/multiEdges.js create mode 100644 packages/site/examples/item/multiEdge/index.en.md create mode 100644 packages/site/examples/item/multiEdge/index.zh.md create mode 100644 packages/site/examples/net/aiLayout/API.en.md create mode 100644 packages/site/examples/net/aiLayout/API.zh.md create mode 100644 packages/site/examples/net/aiLayout/demo/layoutPrediction.js create mode 100644 packages/site/examples/net/aiLayout/demo/meta.json create mode 100644 packages/site/examples/net/aiLayout/index.en.md create mode 100644 packages/site/examples/net/aiLayout/index.zh.md create mode 100644 packages/site/examples/net/arcDiagram/demo/basicArcDiagram.js create mode 100644 packages/site/examples/net/arcDiagram/demo/circularArcDiagram.js create mode 100644 packages/site/examples/net/arcDiagram/demo/meta.json create mode 100644 packages/site/examples/net/arcDiagram/index.en.md create mode 100644 packages/site/examples/net/arcDiagram/index.zh.md create mode 100644 packages/site/examples/net/circular/API.en.md create mode 100644 packages/site/examples/net/circular/API.zh.md create mode 100644 packages/site/examples/net/circular/demo/basicCircular.js create mode 100644 packages/site/examples/net/circular/demo/circularConfigurationTranslate.js create mode 100644 packages/site/examples/net/circular/demo/degreeCircular.js create mode 100644 packages/site/examples/net/circular/demo/divisionCircular.js create mode 100644 packages/site/examples/net/circular/demo/meta.json create mode 100644 packages/site/examples/net/circular/demo/spiralCircular.js create mode 100644 packages/site/examples/net/circular/index.en.md create mode 100644 packages/site/examples/net/circular/index.zh.md create mode 100644 packages/site/examples/net/comboLayout/API.en.md create mode 100644 packages/site/examples/net/comboLayout/API.zh.md create mode 100644 packages/site/examples/net/comboLayout/demo/basicComboForce.js create mode 100644 packages/site/examples/net/comboLayout/demo/comboCombined.js create mode 100644 packages/site/examples/net/comboLayout/demo/meta.json create mode 100644 packages/site/examples/net/comboLayout/index.en.md create mode 100644 packages/site/examples/net/comboLayout/index.zh.md create mode 100644 packages/site/examples/net/concentricLayout/API.en.md create mode 100644 packages/site/examples/net/concentricLayout/API.zh.md create mode 100644 packages/site/examples/net/concentricLayout/demo/basicConcentric.js create mode 100644 packages/site/examples/net/concentricLayout/demo/meta.json create mode 100644 packages/site/examples/net/concentricLayout/index.en.md create mode 100644 packages/site/examples/net/concentricLayout/index.zh.md create mode 100644 packages/site/examples/net/dagreFlow/API.en.md create mode 100644 packages/site/examples/net/dagreFlow/API.zh.md create mode 100644 packages/site/examples/net/dagreFlow/demo/basicDagre.js create mode 100644 packages/site/examples/net/dagreFlow/demo/dagreCombo.js create mode 100644 packages/site/examples/net/dagreFlow/demo/dagreConfigurationTranslate.js create mode 100644 packages/site/examples/net/dagreFlow/demo/lrDagre.js create mode 100644 packages/site/examples/net/dagreFlow/demo/lrDagreUL.js create mode 100644 packages/site/examples/net/dagreFlow/demo/meta.json create mode 100644 packages/site/examples/net/dagreFlow/index.en.md create mode 100644 packages/site/examples/net/dagreFlow/index.zh.md create mode 100644 packages/site/examples/net/forceDirected/API.en.md create mode 100644 packages/site/examples/net/forceDirected/API.zh.md create mode 100644 packages/site/examples/net/forceDirected/demo/basicFA2.js create mode 100644 packages/site/examples/net/forceDirected/demo/basicForce2.js create mode 100644 packages/site/examples/net/forceDirected/demo/basicForceDirected.js create mode 100644 packages/site/examples/net/forceDirected/demo/basicForceDirectedDragFix.js create mode 100644 packages/site/examples/net/forceDirected/demo/forceBubbles.js create mode 100644 packages/site/examples/net/forceDirected/demo/forceClustering.js create mode 100644 packages/site/examples/net/forceDirected/demo/forceConstrainedInRect.js create mode 100644 packages/site/examples/net/forceDirected/demo/forceDirectedConfigurationTranslate.js create mode 100644 packages/site/examples/net/forceDirected/demo/forceDirectedFunctionalParams.js create mode 100644 packages/site/examples/net/forceDirected/demo/forceDirectedPreventOverlap.js create mode 100644 packages/site/examples/net/forceDirected/demo/gForceFix.js create mode 100644 packages/site/examples/net/forceDirected/demo/meta.json create mode 100644 packages/site/examples/net/forceDirected/index.en.md create mode 100644 packages/site/examples/net/forceDirected/index.zh.md create mode 100644 packages/site/examples/net/furchtermanLayout/API.en.md create mode 100644 packages/site/examples/net/furchtermanLayout/API.zh.md create mode 100644 packages/site/examples/net/furchtermanLayout/demo/basicFruchterman.js create mode 100644 packages/site/examples/net/furchtermanLayout/demo/fruchtermanClustering.js create mode 100644 packages/site/examples/net/furchtermanLayout/demo/fruchtermanConfigurationTranslate.js create mode 100644 packages/site/examples/net/furchtermanLayout/demo/fruchtermanFix.js create mode 100644 packages/site/examples/net/furchtermanLayout/demo/fruchtermanWebWorker.js create mode 100644 packages/site/examples/net/furchtermanLayout/demo/meta.json create mode 100644 packages/site/examples/net/furchtermanLayout/index.en.md create mode 100644 packages/site/examples/net/furchtermanLayout/index.zh.md create mode 100644 packages/site/examples/net/gpuLayout/API.en.md create mode 100644 packages/site/examples/net/gpuLayout/API.zh.md create mode 100644 packages/site/examples/net/gpuLayout/demo/basicFruchterman.js create mode 100644 packages/site/examples/net/gpuLayout/demo/basicGForce.js create mode 100644 packages/site/examples/net/gpuLayout/demo/frComplexDataWorker.js create mode 100644 packages/site/examples/net/gpuLayout/demo/fruchtermanClustering.js create mode 100644 packages/site/examples/net/gpuLayout/demo/fruchtermanComplexData.js create mode 100644 packages/site/examples/net/gpuLayout/demo/fruchtermanConfigurationTranslate.js create mode 100644 packages/site/examples/net/gpuLayout/demo/gForceCPU.js create mode 100644 packages/site/examples/net/gpuLayout/demo/gForceComplexData.js create mode 100644 packages/site/examples/net/gpuLayout/demo/meta.json create mode 100644 packages/site/examples/net/gpuLayout/index.en.md create mode 100644 packages/site/examples/net/gpuLayout/index.zh.md create mode 100644 packages/site/examples/net/gridLayout/API.en.md create mode 100644 packages/site/examples/net/gridLayout/API.zh.md create mode 100644 packages/site/examples/net/gridLayout/demo/basicGrid.js create mode 100644 packages/site/examples/net/gridLayout/demo/clusterGrid.js create mode 100644 packages/site/examples/net/gridLayout/demo/meta.json create mode 100644 packages/site/examples/net/gridLayout/index.en.md create mode 100644 packages/site/examples/net/gridLayout/index.zh.md create mode 100644 packages/site/examples/net/layoutMechanism/API.en.md create mode 100644 packages/site/examples/net/layoutMechanism/API.zh.md create mode 100644 packages/site/examples/net/layoutMechanism/demo/customBigraph.js create mode 100644 packages/site/examples/net/layoutMechanism/demo/dataChange.js create mode 100644 packages/site/examples/net/layoutMechanism/demo/layoutTiming.js create mode 100644 packages/site/examples/net/layoutMechanism/demo/layoutTranslate.js create mode 100644 packages/site/examples/net/layoutMechanism/demo/meta.json create mode 100644 packages/site/examples/net/layoutMechanism/demo/subgraphLayout.js create mode 100644 packages/site/examples/net/layoutMechanism/demo/sublayoutPipes.js create mode 100644 packages/site/examples/net/layoutMechanism/index.en.md create mode 100644 packages/site/examples/net/layoutMechanism/index.zh.md create mode 100644 packages/site/examples/net/mdsLayout/API.en.md create mode 100644 packages/site/examples/net/mdsLayout/API.zh.md create mode 100644 packages/site/examples/net/mdsLayout/demo/basicMDS.js create mode 100644 packages/site/examples/net/mdsLayout/demo/meta.json create mode 100644 packages/site/examples/net/mdsLayout/index.en.md create mode 100644 packages/site/examples/net/mdsLayout/index.zh.md create mode 100644 packages/site/examples/net/radialLayout/API.en.md create mode 100644 packages/site/examples/net/radialLayout/API.zh.md create mode 100644 packages/site/examples/net/radialLayout/demo/basicRadial.js create mode 100644 packages/site/examples/net/radialLayout/demo/interactRadial.js create mode 100644 packages/site/examples/net/radialLayout/demo/meta.json create mode 100644 packages/site/examples/net/radialLayout/demo/preventOverlapRadial.js create mode 100644 packages/site/examples/net/radialLayout/demo/preventOverlapUnstrictRadial.js create mode 100644 packages/site/examples/net/radialLayout/demo/radialConfigurationTranslate.js create mode 100644 packages/site/examples/net/radialLayout/demo/sortRadial.js create mode 100644 packages/site/examples/net/radialLayout/index.en.md create mode 100644 packages/site/examples/net/radialLayout/index.zh.md create mode 100644 packages/site/examples/performance/perf/demo/eva.js create mode 100644 packages/site/examples/performance/perf/demo/meta.json create mode 100644 packages/site/examples/performance/perf/demo/moreData.js create mode 100644 packages/site/examples/performance/perf/demo/netscience.js create mode 100644 packages/site/examples/performance/perf/index.en.md create mode 100644 packages/site/examples/performance/perf/index.zh.md create mode 100644 packages/site/examples/scatter/changePosition/demo/default.js create mode 100644 packages/site/examples/scatter/changePosition/demo/meta.json create mode 100644 packages/site/examples/scatter/changePosition/index.en.md create mode 100644 packages/site/examples/scatter/changePosition/index.zh.md create mode 100644 packages/site/examples/scatter/customAnimate/demo/meta.json create mode 100644 packages/site/examples/scatter/customAnimate/demo/position.js create mode 100644 packages/site/examples/scatter/customAnimate/index.en.md create mode 100644 packages/site/examples/scatter/customAnimate/index.zh.md create mode 100644 packages/site/examples/scatter/edge/API.en.md create mode 100644 packages/site/examples/scatter/edge/API.zh.md create mode 100644 packages/site/examples/scatter/edge/demo/arrowAnimate.js create mode 100644 packages/site/examples/scatter/edge/demo/edge.js create mode 100644 packages/site/examples/scatter/edge/demo/lineGrowth.js create mode 100644 packages/site/examples/scatter/edge/demo/meta.json create mode 100644 packages/site/examples/scatter/edge/demo/pointInLine.js create mode 100644 packages/site/examples/scatter/edge/index.en.md create mode 100644 packages/site/examples/scatter/edge/index.zh.md create mode 100644 packages/site/examples/scatter/node/API.en.md create mode 100644 packages/site/examples/scatter/node/API.zh.md create mode 100644 packages/site/examples/scatter/node/demo/meta.json create mode 100644 packages/site/examples/scatter/node/demo/node.js create mode 100644 packages/site/examples/scatter/node/index.en.md create mode 100644 packages/site/examples/scatter/node/index.zh.md create mode 100644 packages/site/examples/scatter/stateChange/API.en.md create mode 100644 packages/site/examples/scatter/stateChange/API.zh.md create mode 100644 packages/site/examples/scatter/stateChange/demo/hover.js create mode 100644 packages/site/examples/scatter/stateChange/demo/meta.json create mode 100644 packages/site/examples/scatter/stateChange/index.en.md create mode 100644 packages/site/examples/scatter/stateChange/index.zh.md create mode 100644 packages/site/examples/scatter/viewport/API.en.md create mode 100644 packages/site/examples/scatter/viewport/API.zh.md create mode 100644 packages/site/examples/scatter/viewport/demo/default.js create mode 100644 packages/site/examples/scatter/viewport/demo/meta.json create mode 100644 packages/site/examples/scatter/viewport/index.en.md create mode 100644 packages/site/examples/scatter/viewport/index.zh.md create mode 100644 packages/site/examples/tool/contextMenu/API.en.md create mode 100644 packages/site/examples/tool/contextMenu/API.zh.md create mode 100644 packages/site/examples/tool/contextMenu/demo/contextMenu.js create mode 100644 packages/site/examples/tool/contextMenu/demo/meta.json create mode 100644 packages/site/examples/tool/contextMenu/index.en.md create mode 100644 packages/site/examples/tool/contextMenu/index.zh.md create mode 100644 packages/site/examples/tool/edgeBundling/API.en.md create mode 100644 packages/site/examples/tool/edgeBundling/API.zh.md create mode 100644 packages/site/examples/tool/edgeBundling/demo/edgeBundling.js create mode 100644 packages/site/examples/tool/edgeBundling/demo/meta.json create mode 100644 packages/site/examples/tool/edgeBundling/index.en.md create mode 100644 packages/site/examples/tool/edgeBundling/index.zh.md create mode 100644 packages/site/examples/tool/edgeFilterLens/API.en.md create mode 100644 packages/site/examples/tool/edgeFilterLens/API.zh.md create mode 100644 packages/site/examples/tool/edgeFilterLens/demo/edgeFilter.js create mode 100644 packages/site/examples/tool/edgeFilterLens/demo/edgeFilterConfig.js create mode 100644 packages/site/examples/tool/edgeFilterLens/demo/meta.json create mode 100644 packages/site/examples/tool/edgeFilterLens/index.en.md create mode 100644 packages/site/examples/tool/edgeFilterLens/index.zh.md create mode 100644 packages/site/examples/tool/fisheye/API.en.md create mode 100644 packages/site/examples/tool/fisheye/API.zh.md create mode 100644 packages/site/examples/tool/fisheye/demo/fisheye.js create mode 100644 packages/site/examples/tool/fisheye/demo/fisheyeStyle.js create mode 100644 packages/site/examples/tool/fisheye/demo/meta.json create mode 100644 packages/site/examples/tool/fisheye/index.en.md create mode 100644 packages/site/examples/tool/fisheye/index.zh.md create mode 100644 packages/site/examples/tool/legend/API.en.md create mode 100644 packages/site/examples/tool/legend/API.zh.md create mode 100644 packages/site/examples/tool/legend/demo/legend.js create mode 100644 packages/site/examples/tool/legend/demo/legendClick.js create mode 100644 packages/site/examples/tool/legend/demo/legendMouseenter.js create mode 100644 packages/site/examples/tool/legend/demo/meta.json create mode 100644 packages/site/examples/tool/legend/index.en.md create mode 100644 packages/site/examples/tool/legend/index.zh.md create mode 100644 packages/site/examples/tool/minimap/API.en.md create mode 100644 packages/site/examples/tool/minimap/API.zh.md create mode 100644 packages/site/examples/tool/minimap/demo/imageMinimap.js create mode 100644 packages/site/examples/tool/minimap/demo/meta.json create mode 100644 packages/site/examples/tool/minimap/demo/minimap.js create mode 100644 packages/site/examples/tool/minimap/index.en.md create mode 100644 packages/site/examples/tool/minimap/index.zh.md create mode 100644 packages/site/examples/tool/snapline/API.en.md create mode 100644 packages/site/examples/tool/snapline/API.zh.md create mode 100644 packages/site/examples/tool/snapline/demo/custom.js create mode 100644 packages/site/examples/tool/snapline/demo/default.js create mode 100644 packages/site/examples/tool/snapline/demo/meta.json create mode 100644 packages/site/examples/tool/snapline/index.en.md create mode 100644 packages/site/examples/tool/snapline/index.zh.md create mode 100644 packages/site/examples/tool/timebar/API.en.md create mode 100644 packages/site/examples/tool/timebar/API.zh.md create mode 100644 packages/site/examples/tool/timebar/demo/meta.json create mode 100644 packages/site/examples/tool/timebar/demo/simple-timebar.js create mode 100644 packages/site/examples/tool/timebar/demo/slice-timebar.js create mode 100644 packages/site/examples/tool/timebar/demo/timebar.js create mode 100644 packages/site/examples/tool/timebar/index.en.md create mode 100644 packages/site/examples/tool/timebar/index.zh.md create mode 100644 packages/site/examples/tool/toolbar/API.en.md create mode 100644 packages/site/examples/tool/toolbar/API.zh.md create mode 100644 packages/site/examples/tool/toolbar/demo/meta.json create mode 100644 packages/site/examples/tool/toolbar/demo/self-toolbar.js create mode 100644 packages/site/examples/tool/toolbar/demo/toolbar.js create mode 100644 packages/site/examples/tool/toolbar/index.en.md create mode 100644 packages/site/examples/tool/toolbar/index.zh.md create mode 100644 packages/site/examples/tool/tooltip/API.en.md create mode 100644 packages/site/examples/tool/tooltip/API.zh.md create mode 100644 packages/site/examples/tool/tooltip/demo/meta.json create mode 100644 packages/site/examples/tool/tooltip/demo/tooltipClick.js create mode 100644 packages/site/examples/tool/tooltip/demo/tooltipFix.js create mode 100644 packages/site/examples/tool/tooltip/demo/tooltipLocalCustom.js create mode 100644 packages/site/examples/tool/tooltip/demo/tooltipPlugin.js create mode 100644 packages/site/examples/tool/tooltip/demo/tooltipPluginLocal.js create mode 100644 packages/site/examples/tool/tooltip/index.en.md create mode 100644 packages/site/examples/tool/tooltip/index.zh.md create mode 100644 packages/site/examples/tree/compactBox/API.en.md create mode 100644 packages/site/examples/tree/compactBox/API.zh.md create mode 100644 packages/site/examples/tree/compactBox/demo/basicCompactBox.js create mode 100644 packages/site/examples/tree/compactBox/demo/compactBoxLeftAlign.js create mode 100644 packages/site/examples/tree/compactBox/demo/meta.json create mode 100644 packages/site/examples/tree/compactBox/demo/tbCompactBox.js create mode 100644 packages/site/examples/tree/compactBox/index.en.md create mode 100644 packages/site/examples/tree/compactBox/index.zh.md create mode 100644 packages/site/examples/tree/customItemTree/API.en.md create mode 100644 packages/site/examples/tree/customItemTree/API.zh.md create mode 100644 packages/site/examples/tree/customItemTree/demo/customEdgeTree.js create mode 100644 packages/site/examples/tree/customItemTree/demo/customTree.js create mode 100644 packages/site/examples/tree/customItemTree/demo/meta.json create mode 100644 packages/site/examples/tree/customItemTree/demo/treeEdgeLabel.js create mode 100644 packages/site/examples/tree/customItemTree/index.en.md create mode 100644 packages/site/examples/tree/customItemTree/index.zh.md create mode 100644 packages/site/examples/tree/dendrogram/API.en.md create mode 100644 packages/site/examples/tree/dendrogram/API.zh.md create mode 100644 packages/site/examples/tree/dendrogram/demo/basicDendrogram.js create mode 100644 packages/site/examples/tree/dendrogram/demo/meta.json create mode 100644 packages/site/examples/tree/dendrogram/demo/tbDendrogram.js create mode 100644 packages/site/examples/tree/dendrogram/index.en.md create mode 100644 packages/site/examples/tree/dendrogram/index.zh.md create mode 100644 packages/site/examples/tree/indented/API.en.md create mode 100644 packages/site/examples/tree/indented/API.zh.md create mode 100644 packages/site/examples/tree/indented/demo/filesystem.js create mode 100644 packages/site/examples/tree/indented/demo/hIntended.js create mode 100644 packages/site/examples/tree/indented/demo/intendAlignTop.js create mode 100644 packages/site/examples/tree/indented/demo/meta.json create mode 100644 packages/site/examples/tree/indented/index.en.md create mode 100644 packages/site/examples/tree/indented/index.zh.md create mode 100644 packages/site/examples/tree/mindmap/API.en.md create mode 100644 packages/site/examples/tree/mindmap/API.zh.md create mode 100644 packages/site/examples/tree/mindmap/demo/hCustomSideMindmap.js create mode 100644 packages/site/examples/tree/mindmap/demo/hLeftMindmap.js create mode 100644 packages/site/examples/tree/mindmap/demo/hMindmap.js create mode 100644 packages/site/examples/tree/mindmap/demo/hRightMindmap.js create mode 100644 packages/site/examples/tree/mindmap/demo/meta.json create mode 100644 packages/site/examples/tree/mindmap/index.en.md create mode 100644 packages/site/examples/tree/mindmap/index.zh.md create mode 100644 packages/site/examples/tree/radialtree/API.en.md create mode 100644 packages/site/examples/tree/radialtree/API.zh.md create mode 100644 packages/site/examples/tree/radialtree/demo/meta.json create mode 100644 packages/site/examples/tree/radialtree/demo/radialCompactBox.js create mode 100644 packages/site/examples/tree/radialtree/demo/radialDendrogram.js create mode 100644 packages/site/examples/tree/radialtree/index.en.md create mode 100644 packages/site/examples/tree/radialtree/index.zh.md create mode 100644 packages/site/package.json diff --git a/.babelrc.js b/.babelrc.js new file mode 100644 index 0000000000..2b4c862884 --- /dev/null +++ b/.babelrc.js @@ -0,0 +1,26 @@ +module.exports = api => { + api.cache(() => process.env.NODE_ENV); + + if (process.env.GATSBY === 'true') { + return { + presets: ['@babel/preset-env', 'babel-preset-gatsby'], + }; + } + return { + presets: [ + [ + '@babel/preset-env', + { + loose: true, + modules: false, + }, + ], + '@babel/preset-react', + { + "plugins": [ + "@babel/plugin-proposal-class-properties" + ] + } + ] + }; +}; diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000000..47a42abef3 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,10 @@ +# Browsers that we support + +[production staging] +> 1% +last 2 version +ie 11 + +[development] +> 1% +last 2 version diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..a2f217c26e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab +indent_size = 1 + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..9aa37feb26 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,14 @@ +lib/ +dist/ +node_modules/ +.cache +public +bin +esm/ +es/ +tests/ +webpack.*.js +gatsby-*.js +global.d.ts +jest.config.js +.eslintrc.* diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..e5d65167ef --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,51 @@ +module.exports = { + extends: [require.resolve('@umijs/fabric/dist/eslint')], + globals: { + $: true, + _: true, + }, + rules: { + 'no-bitwise': 0, + 'import/order': 0, + 'no-plusplus': 0, + 'no-console': ['error', { allow: ['warn', 'error'] }], + 'operator-assignment': 0, + 'consistent-return': 0, + 'lines-between-class-members': 0, + 'class-methods-use-this': 0, + 'lines-between-class-members': 0, + 'no-multi-assign': 0, + 'no-continue': 0, + 'no-underscore-dangle': 0, + 'no-useless-constructor': 0, + 'prefer-destructuring': 0, + 'guard-for-in': 0, + 'no-restricted-globals': 0, + 'max-classes-per-file': 0, + // 后面需要去掉 + 'no-restricted-syntax': 0, + 'prefer-spread': 0, + '@typescript-eslint/camelcase': 0, + 'no-loop-func': 0, + '@typescript-eslint/no-loop-func': 0, + '@typescript-eslint/no-redeclare': 0, + '@typescript-eslint/no-shadow': 0, + '@typescript-eslint/no-unused-vars': 0, + 'no-param-reassign': 0, + 'import/no-extraneous-dependencies': 0, + 'no-unused-expressions': 0, + 'dot-notation': 0, + 'array-callback-return': 0, + 'one-var': 0, + 'no-lonely-if': 0, + '@typescript-eslint/consistent-type-imports': 0, + "@typescript-eslint/no-this-alias": 0, + "@typescript-eslint/consistent-indexed-object-style": 0, + "@typescript-eslint/no-invalid-this": 0, + "@typescript-eslint/array-type": 0, + "@typescript-eslint/consistent-type-definitions": 0, + // using Record instead + "@typescript-eslint/ban-types": 0, + "@typescript-eslint/no-useless-constructor": 0 + }, +}; diff --git a/.fatherrc.js b/.fatherrc.js new file mode 100644 index 0000000000..5185d1910b --- /dev/null +++ b/.fatherrc.js @@ -0,0 +1,5 @@ +export default { + entry: './src/index.ts', + esm: 'babel', + cjs: 'babel', +}; diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..4d2ae24b95 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,24 @@ + + +- **G6 Version**: +- **Platform**: +- **Mini Showcase(like screenshots)**: +- **CodePen Link**: + + diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..c09ad6c80f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,85 @@ +name: 'Bug report' +description: Create a report to help us improve +body: + - type: markdown + attributes: + value: | + Thank you for reporting an issue :pray:. + + This issue tracker is for reporting bugs found in G6 (https://github.com/antvis/G6). + If you have a question about how to achieve something and are struggling, please post a question + inside of G6's Discussion's tab: https://github.com/antvis/G6/discussions + + Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already: + - G6's Issue's tab: https://github.com/antvis/G6/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc + - G6's closed issues tab: https://github.com/antvis/G6/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed + - G6 Discussion's tab: https://github.com/antvis/G6/discussions + + The more information you fill in, the better the community can help you. + - type: textarea + id: description + attributes: + label: Describe the bug + description: Provide a clear and concise description of the challenge you are running into. + validations: + required: true + - type: input + id: link + attributes: + label: Your Example Website or App + description: | + Which website or app were you using when the bug happened? + Note: + - Your bug will may get fixed much faster if we can run your code and it doesn't have dependencies other than the G6 npm package. + - To create a shareable code example you can use Stackblitz (https://stackblitz.com/) or CodeSandbox (https://codesandbox.io/s/new). Please no localhost URLs. + - Please read these tips for providing a minimal example: https://stackoverflow.com/help/mcve. + placeholder: | + e.g. Stackblitz, Code Sandbox app url + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to Reproduce the Bug or Issue + description: Describe the steps we have to take to reproduce the behavior. + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + description: Provide a clear and concise description of what you expected to happen. + placeholder: | + As a user, I expected ___ behavior but i am seeing ___ + validations: + required: true + - type: textarea + id: screenshots_or_videos + attributes: + label: Screenshots or Videos + description: | + If applicable, add screenshots or a video to help explain your problem. + For more information on the supported file image/file types and the file size limits, please refer + to the following link: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files + placeholder: | + You can drag your video or image files inside of this editor ↓ + - type: textarea + id: platform + attributes: + label: Platform + value: | + - OS: [e.g. macOS, Windows, Linux] + - Browser: [e.g. Chrome, Safari, Firefox] + - Version: [e.g. 91.1] + validations: + required: true + - type: textarea + id: additional + attributes: + label: Additional context + description: Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report_chinese.yml b/.github/ISSUE_TEMPLATE/bug_report_chinese.yml new file mode 100644 index 0000000000..207af8a529 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report_chinese.yml @@ -0,0 +1,69 @@ +name: '🐞 新 Issue' +description: 创建一个新的 issue,如果你的 issue 不符合规范,它将会被自动关闭。 +body: + - type: markdown + attributes: + value: | + 在提交新 issue 之前,先通过以下链接查看有没有类似的 bug 或者建议: + - [G6 Issues](https://github.com/antvis/G6/issues) + - [G6 Closed Issues](https://github.com/antvis/G6/issues?q=is%3Aissue+is%3Aclosed) + - [G6 Discussions](https://github.com/antvis/G6/discussions) + - type: textarea + id: description + attributes: + label: 问题描述 + description: 简洁清晰地描述你遇到的问题。 + validations: + required: true + - type: input + id: link + attributes: + label: 重现链接 + description: | + 可以使用 CodeSandbox(https://codesandbox.io/s/new) 或者 StackBlitz(https://stackblitz.com/) 重现你的问题。 + placeholder: | + 示例: CodeSandBox 或者 StackBlitz URL + validations: + required: true + - type: textarea + id: steps + attributes: + label: 重现步骤 + description: 简洁清晰的重现步骤能够帮助我们更迅速地定位问题所在。 + placeholder: | + 1.进入页面... + 2.点击.... + 3.查看错误.... + validations: + required: true + - type: textarea + id: expected + attributes: + label: 预期行为 + description: 描述你期望的结果以及实际的结果。 + placeholder: | + 我期望看到...,但我看到了... + validations: + required: true + - type: textarea + id: platform + attributes: + label: 平台 + value: | + - 操作系统: [macOS, Windows, Linux, React Native ...] + - 网页浏览器: [Google Chrome, Safari, Firefox] + - G6 版本: [4.5.1 ... ] + validations: + required: true + - type: textarea + id: screenshots_or_videos + attributes: + label: 屏幕截图或视频(可选) + description: 可以添加屏幕截图或视频帮助你解释问题。 + placeholder: | + 可以将你的图片或者视频拖拽到此处↓ + - type: textarea + id: additional + attributes: + label: 补充说明(可选) + description: 比如:遇到这个 bug 的业务场景、上下文。 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..e59dd0f4a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Feature Requests & Questions + url: https://github.com/antvis/G6/discussions + about: Please ask and answer questions here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..bf185f273b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ + + +##### Checklist + + + +- [ ] `npm test` passes +- [ ] tests and/or benchmarks are included +- [ ] commit message follows commit guidelines + +##### Description of change + + diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml new file mode 100644 index 0000000000..016d14ba35 --- /dev/null +++ b/.github/workflows/gitleaks.yml @@ -0,0 +1,17 @@ +name: gitleaks + +on: [push,pull_request] + +jobs: + gitleaks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: '1' + - name: wget + uses: wei/wget@v1 + with: + args: -O .gitleaks.toml https://raw.githubusercontent.com/ycjcl868/gitleaks/master/.gitleaks.toml + - name: gitleaks-action + uses: zricethezav/gitleaks-action@v1.6.0 \ No newline at end of file diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 0000000000..afc1784e49 --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,18 @@ +name: 🔂 Surge PR Preview + +on: pull_request + +jobs: + preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: afc163/surge-preview@v1 + with: + surge_token: ${{ secrets.SURGE_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} + build: | + npm install + npm run build + npm run site:build + dist: public diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 0000000000..a94c643760 --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,17 @@ +on: + issue_comment: + types: [created] +name: Automatic Rebase +jobs: + rebase: + name: Rebase + if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + with: + fetch-depth: 0 + - name: Automatic Rebase + uses: cirrus-actions/rebase@1.3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..737a820c95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,88 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +yarn.lock +es + +# lock +package-lock.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +build +dist +temp +.DS_Store +.idea +demos +lib + +*.sw* +*.un~ + +# cache +*.cache +public + +esm + +es + +.github +# .storybook +.vscode/ + +stats.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..6c0a891a9e --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,19 @@ +# 不要修改该文件, 会自动生成, 详见 http://gitlab.alibaba-inc.com/node/ci +# 不要使用 ci 生成,直接按照 gitlab 文档自己写吧! +before_script: + - export PATH=$PWD/node_modules/.bin:$PWD/.node/bin:$PATH + - time enclose install tnpm:tnpm + - tnpm -v +stages: + - test + +# job1 +test: + image: reg.docker.alibaba-inc.com/dockerlab/node-ci:3.2.0 + stage: test + script: + - env -u CI tnpm i --silent --internal-oss-cache --install-node=10 + # - tnpm run ci ci 环境问题导致 electron 安装失败暂时跑不了 + - tnpm run lint + tags: + - swarm diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000000..af137ec718 --- /dev/null +++ b/.npmignore @@ -0,0 +1,83 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# lock +package-lock.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +.DS_Store + +# npmignore - content above this line is automatically generated and modifications may be omitted +# see npmjs.com/npmignore for more details. +test + +*.sw* +*.un~ +.idea +bin +demos +docs +temp +webpack-dev.config.js +webpack.config.js +public +.cache +site +examples +gatsby-browser.js +gatsby-config.js diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..281977b179 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +.cache +package.json +package-lock.json +public +dist +es +lib +.* +*.png +**/assets/** diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000000..7b597d7891 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,5 @@ +const fabric = require('@umijs/fabric'); + +module.exports = { + ...fabric.prettier, +}; diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..bd3fe3bcae --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +language: node_js + +node_js: + - '12' + +env: + - NODE_ENV=test + +addons: + apt: + packages: + - xvfb + - libgconf-2-4 + +install: + - export DISPLAY=':99.0' + - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & + - yarn + +script: + - | + if [ "$TEST_TYPE" = test ]; then + yarn ci && bash <(curl -s https://codecov.io/bash) + else + yarn $TEST_TYPE + fi +env: + matrix: + - TEST_TYPE=lint + - TEST_TYPE=test diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..4099d88496 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..2e9870c429 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1507 @@ +# ChangeLog + +### 4.8.0 + +- fix: destroy graph and call layout problem, closes: #4126; +- fix: remove duplicated event emit, closes: #4043; +- fix: mousedown on other DOMs and mouseup on canvas, click is triggered unexpectly, closes: #2922; +- fix: mousemove and mouseup are not triggered with drag and dragend, closes: #3086; +- fix: replace DOMMouseScroll and mousewheel with wheel event, closes: #3256; +- perf: refresh item when updateChild, updateChildren, addChild, removeChild for TreeGraph; + +### 4.7.17 + +- fix: expandCombo and the edges of the children are not refreshed, closes: #3250; +- fix: the item param of the afterremoveitem for combo should be data; +- fix: add type to the parameter list of beforeremoveitem event; +- fix: edge update with destroyed end items, closes: #3925; +- perf: take the max value of padding array for circle combo, closes: #4113; +- feat: support top-center for rect combo label position, closes: #3750; +- feat: createCombo and uncombo support stack, closes: #3695, #3323; + +### 4.7.16 + +- feat: allowDragOnItem config for scroll-canvas, closes: #3062; +- feat: allow to setTextWaterMarker and setImageWaterMarker with an undefined parameter to remove the watermarker, closes: #3478; +- feat: hideEdge config for minimap to enhance the performance, closes: #3158; +- fix: minimap has incorrect shape zIndex with keyShape type and delegate type, closes: #3132; +- fix: minimap viewport dragging problem in firefox and safari, closes: #2939; +- docs: add sequence demo to site, closes: #3027; +- perf: unify the formats of shouldBegin, shouldUpdate, and shouldEnd in behaviors, closes: #3028; +- perf: fitView and fitCenter according to the corner ndoes insead of getCanvasBBox to avoid maximum call stack size exceeded, closes: #2447; +- fix: treeGraph changeData with node properties lost, closes: #3215; +- fix: error occurs while calling updateLayout from gpu layout to a cpu layout, closes: #3272; +- fix: error occurs while calling changeData to remove a node in a combo, closes: #3293; + +### 4.7.15 + +- fix: dagre layout for collapsed combos; +- perf: give layout algorithm vedges; + +### 4.7.14 + +- fix: error occurs while dragging combo with drag-node behavior; + +### 4.7.13 + +- fix: unexpected move with fitCenter with animation; +- fix: update modelRect with rendering error, closes: #4041; + +### 4.7.12 + +- fix: drag-canvas incorrectly stopped by right click; +- fix: createCombo with nodes which already has parent combos; +- fix: setItemState on node, related edges's linking positions are not refreshed; +- perf: combo animate inherit from graph's animate config; +- perf: improve the performance of setItemState and active-relations again; +- feat: graph supports optimizeThreshold to control the number threshold of nodes to enable the optimization on rendering and interaction, currently only affects the edges' refresh while the related node state style changed; + +### 4.7.11 + +- perf: improve the performance of setItemState and active-relations; +- perf: keyShape is hiden when a combo is collapsed with collapsedSubstituIcon; +- fix: drag-node incorrectly stopped by right click; +- fix: timebar plugin destroy problem, closes: #3998; +- fix: controllerCfg does not take effect in timebar with tick type, closes: #3843; +- feat: timebar plugin supports config the default time type; +- feat: timebar with play and pause API; +- chore: use addItem and removeItem instead of changeData in timebar; + +### 4.7.10 + +- perf: force layout with animation calls graph refreshPositions instead positionsAnimate while refreshing; + +### 4.7.9 + +- perf: init node positions when the node has no x and y in the origin data; + +### 4.7.8 + +- feat: pointPadding config for loop edges with non-circle nodes, closes: #3974; +- fix: image lost while updating the size for an image node, closes: #3938; + +### 4.7.7 + + - feat: getContentPlaceholder and getTitlePlaceholder for Annotation plugin; + +### 4.7.6 + +- fix: Annotation readData with inexistent item; +- perf: improve the performance for updating; + +### 4.7.5 + +- perf: Annotation support updating positions for outside cards by calling updateOutsideCards; + + ### 4.7.4 + + - perf: Annotation min-width and input width; + +### 4.7.3 + +- feat: beforechangedata and afterchagnedata for graph changeData; +- feat: Annotation supports icon events callbacks; +- feat: Annotation supports defaultBegin position configuration for new annotation cards; +- perf: Annotation updated automatically when graph data changed and graph item visiblity changed; +- fix: Destroy legend canvas when the plugin is destroyed, closes: #3931; + +### 4.7.2 + +- feat: Annotation plugin supports configuring behaviors for collapse and close icon; +- feat: Annotation plugin supports canvas annotation; +- fix: gForce layout has animation by default; +- fix: createCombo creates vedges asynchronously, closes: #3912; +- fix: strange polyline path edge related to combo, closes: #3913; + +#### 4.7.1 + +- feat: Annotation plugin; +- fix: combo and drag-node with heap maximum problem, closes: #3886; +- fix: combo and graph re-read problem, closes: 3902; +- fix: d3 force layout with default animate; +- perf: bundling plugin ts problem, closes: #3904; + +#### 4.7.0 + +- fix: combo collapsed edge problems, closes: #3839; + +#### 4.7.0-beta + +- feat: force2 from graphin-force; +- feat: preset for layout; +- feat: tweak incremental layout init for force like layouts; + +#### 4.6.18 + +- feat: updateLayout from no pipes to pipes, closes: #3726; +- fix: relayout with pipes; + +#### 4.6.17 + +- fix: legend changeData problem, closes: #3561; +- fix: redo and undo with an image node, closes: #3782; +- fix: call refreshPositions instead of positionsAnimate while there is no layout configuration; + +#### 4.6.16 + +- feat: ID check; +- feat: fitView with animation; +- feat: findAllByState with additional filter; +- fix: wrong dropped position for drag-combo with enableDelegate, closes: #3810; +- fix: stack for drag-combo with onlyChangeComboSize, closes: #3801; +- fix: stack updateLayout, closes: #3765; +- fix: drag-canvas and zoom-canvas with enableOptimize show a hidden shape which is controlled by state, closes: #3635; +- fix: typing problem for react node; + +#### 4.6.15 + +- fix: fitView does not zoom the graph with animate true; + +#### 4.6.14 + +- perf: optimize the performance of combo graph; + +#### 4.6.12 + +- perf: optimize the performance of combo graph first rendering; + +#### 4.6.11 + +- fix: star node with leftBottom linkPoint show and hide problem; +- fix: relayout does not execute onAllLayoutEnd problem; +- fix: combo edge state update problem, closes: #3639; + +#### 4.6.10 + +- feat: maxLength for labelCfg; +- fix: custom layout warning and layout failed problem; +- fix: upgrade layout to fix DagreLayoutOptions type error; +- fix: upgrade layout to fix comboCombined with original node infomations problem; + +#### 4.6.8 + +- fix: spelling error for 'nodeselectChange', closes: #3736; +- fix: update node icon from show false to show true; +- fix: afterrender should be emitted when the layout is not configured; +- perf: update related edges while drag-combo; +- feat: combo supports collapsedSubstituteIcon showing after collapsed; +- feat: remove animations while first rendering with (collapsed)combos; +- refactor: toolbar plugin functions; + +#### 4.6.6 + +- fix: destroyLayout error, closes: #3727; +- fix: drag combo with stack problem, closes: #3699; +- fix: updateLayout does not take effect if update layout with same type as graph instance configuration, closes: #3706; +- fix: legendStateStyles typo, closes: #3705; +- perf: zoom-canvas take the maximum and minimum values instead of return directly; +- perf: minimap cursor move; +- feat: fitView and fitCenter with animation; +- feat: addItems to add multiple items into graph in the same time; +- feat: enable edge selection in click-select; + +#### 4.6.4 + +- chore: improve the types of graph events; +- fix: position animate considers origin attributes; + + +#### 4.6.3 + +- feat: shouldDeselect param for lasso-select; +- fix: initial collapsed combos with unexpected size; + +#### 4.6.1 + +- fix: layoutController is null problem; + +#### 4.6.0-beta + +- feat: comboCombined Layout from @antv/layout; +- feat: combo supports position configurations for any situations; +- fix: run layout promise only when the layout is configured; + +#### 4.5.5 + +- fix: tooltip with wrong duplicated child DOM nodes; + +#### 4.5.4 + +- feat: tooltip plugin supports dynamic dom configurations; +- feat: context menu plugin supports mobile touch event; +- feat: allow enabling stack operations at runtime; +- fix: use origin data when changeData without data param, closes: #3459; +- feat: shouldBegin for canvas click in click-select behavior; + +#### 4.5.3 + +- fix: import G6 in head and call getComputedStyle, the document body is not exist; + +#### 4.5.2 + +- fix: node update from no icon to iconfont icon failed; +- fix: getUpdateType with type error; +- fix: edge label background with clearItemStates problem; +- fix: edge label with autoRotate false and padding problem; +- fix: changeData in the process of create-edge behavior, an error occurs, closes: #3384; +- fix: node update from no icon to iconfont icon failed; + +#### 4.5.1 + +- feat: translate graph with animation; +- feat: zoom graph with animation; +- feat: timebar supports filterItemTypes to configure the types of the graph items to be filtered; only nodes can be filtered before; +- feat: timebar supports to configure the rotate of the tick labels by tickLabelStyle[dot]rotate; +- feat: timebar supports container CSS configuration by containerCSS; +- feat: timebar supports a function getDate to returns the date value according to each node or edge by user; +- feat: timebar supports afunction getValue to returns the value (for trend line of trend timebar) according to each node or edge by user; +- feat: timebar supports to configure a boolean changeData to control the filter way, true means filters by graph[dot]changeData, false means filters by graph[dot]showItem and graph[dot]hideItem; +- feat: timebar supports to configure a function shouldIgnore to return true or false by user to decide whether the node or the edge should be ignored while filtering; +- fix: simple timebar silder text position strategy and expand the lineAppendWidth for the slider; +- fix: edge label padding bug, closes: #3346; +- fix: update node with iconfont icon, the icon is updated to a wrong position, closes: #3348; + + +#### 4.5.0 + +- fix: add item type to the parameter of afterremoveitem event; + +#### 4.4.1 + +- feat: zoom with animation, contributed by @Blakko; + +#### 4.4.0-beta.1 + +- fix: drag-combo and drag-node with wrongly calling shouldUpdate; + +#### 4.4.0-beta.0 + +- feat: better performance for item drawing; +- fix: disable the capture of hull shape to enhance the performance of dragging canvas with hulls; +- fix: uncombo an empty combo, fix: #3248; +- fix: upgrade layout to beta 5 to solve proxy problem for IE; + +#### 4.3.11 + +#### 4.3.9 + +- fix: update edge to be horizontal and the label is on wrong position for min file; + +#### 4.3.9 + +- fix: addBehavior with behavior string name, closes: #3020; +- fix: drag-node shouldEnd does not stop the dragging node behavior, closes: #3173; +- fix: drag-combo fails to merge combo with enableDelegate, closes: #3137; +- fix: uncombo does not trigger afterremoveitem event, closes: #3179; +- fix: error label background position when the edge label has position start, closes: #3129; +- fix: destroyed graph judgement, closes: #3203; +- fix: edge click event will not be triggered when the contextmenu is configure with trigger click, closes: #3201; +- feat: drag-combo with shouldEnd, closes: #3202; +- chore: information for failing to download image, closes: #2980; + +#### 4.3.7 + +- fix: update edge to be horizontal and the label is on wrong position; + +#### 4.3.6 + +- fix: drag-node on mobile, closes: #3127; +- fix: removeBehaviors drag-canvas cause canvas:drag event cannot be listened; +- fix: drag-node with unexpected offseted edge end points, closes: #3118; +- fix: delete node with combo, closes: #3141; +- fix: update node position with wrong position; +- feat: enableStack for drag-node behavior, closes: #3128; + + +#### 4.3.5 + +- fix: drag a node without comboId by drag-node with onlyChangeComboSize; +- fix: gpu layout with async; +- fix: minimap with delegate type cannot reach the top of the canvas, closes: #2885; +- feat: improve the performance for updating nodes; +- feat: updateLayout with align and alignPoint; + +#### 4.3.4 + +- fix: when select a node with click-select, selected combos should be deselected; +- fix: contextmenu with click trigger does not show the menu up, closes: #2982; +- fix: layout with collapsed combo, closes: #2988; +- fix: zoom-canvas with optimizeZoom, drag-canvas shows the node shapes hiden by zoom-canvas optimizeZoom, closes: #2996; + +#### 4.3.3 + +- fix: uncombo with id, closes: #2924; +- fix: image node with state changing, closes: #2923; +- fix: mouseentering tooltip DOM hides the DOM; +- feat: moveTo with animate, closes: #2252; + +#### 4.3.2 + +- fix: upgrade the layout package to 0.1.14 to solve the different results from gpu and cpu problem in gForce layout, closes: #2902; +- fix: auto fitting container without width and height for graph problem, closes: #2901; +- fix: minimap with zoomingproblem, closes: #2863 +- feat: fx and fy for fruchterman and gForce layout in both gpu and cpu version; +- feat: barWidth for interval bar chart for TimeBar plugin, closes: #2989; +- feat: click trigger for context munu, closes: #2686; + +#### 4.3.0 + +- fix: empty object for TreeGraph data; +- fix: combo edge arrow error with state styles; +- fix: depth problem for addItem with comboId, closes: #2888; +- feat: focus edge item; +- feat: legend plugin; +- feat: allow to new a tree layout independently; + +#### 4.2.7 + +- fix: edges disappear when collapsing combo, closes: #2816; +- fix: drag-node with edge key, closes: #2819; +- fix: failed to update startArrow to be false, closes #2814; +- fix: createCombo and add combId or parentId to the related nodes or combos, closes #2815; +- feat: no animation when first rendering with collapsed combos, closes: #2826; + +#### 4.2.6 + +- feat: scroll-canvas behavior; +- feat: iconfont for node icon; +- feat: percentage of scalable range for drag-canvas; +- fix: missing brushStyle in type ModeOption; +- fix: the comboId remains in the node after uncombo(), closes #2801; +- fix: disappearing edges when combos are expanded/collapsed, closes #2798; +- fix: invisible nodes and edges should not be selected by brush-select and lasso-select, closes #2810; + +#### 4.2.5 + +- feat: donut node; +- feat: downloadImage with watermarker; +- fix: multiple layout calling error; +- fix: combo collapse and related edges diappearing; +- fix: forceAtlas2 with descrete node error; + +#### 4.2.4 + +- fix: change data with dulplicated name between nodes and combos; +- fix: pixelRatio for graph types; + +#### 4.2.3 + +- fix: layout with fitView; + +#### 4.2.2 + +- feat: pipe layouts for subgraphs; + +#### 4.2.1 + +- fix: circle combo edge linking position problem; +- fix: drag minimap viewport with forbidden icon in chrome on windows; +- fix: show node without node position problem; +- fix: addItem and getNodeDgree with wrong result problem; +- fix: timebar data filtering problem; +- fix: update endArrow to be false and set state problem; +- feat: pass comb and comboEdge data for layout; +- feat: tooltip with fixToItem to avoid following the mouse when moving; +- feat: getViewPortCenterPoint and getGraphCenterPoint API; +- feat: tooltip with trigger configuration, supports mouseenter and click; + +#### 4.2.0 +#### 4.1.14 + +- fix: combo edge link position problem; +- fix: activate-relations with combo and combo edges problem; +- feat: support config TimeBar handler, background, foreground, tick label, tick line style; + +#### 4.1.16 + +- fix: webworker in dist; + +#### 4.1.15 + +- fix: cubic-x problem, closes: #2698; + +#### 4.1.14 + +- fix: gridSize for polyline; +- fix: create-edge undo problem; +- fix: tslib spreadArray problem; +- fix: rect combo position with state problem; +- feat: simple polyline for better performance; +- fix: gridSize for polyline; +- fix: cubic-x problem, closes: #2698; +- fix: create-edge undo problem; +- fix: tslib spreadArray problem; +- fix: rect combo position with state problem; +- feat: simple polyline for better performance; + +#### 4.1.13 + +- fix: getHulls with error type; +- fix: createHull with destroyed hullMap problem; +- fix: refining TimeBar minor problems; +- fix: tooltip with display none to avoid enlarging graph container; +- feat: TimeBar supports controller style configuration; +- feat: TimeBar supports filtering edges; +- feat: dagre with nested combo; + +#### 4.1.13-beta + +- chore: update layout and register in G6; +- fix: performance problem in create-edge with polyline; +- fix: performance for polyline; +- fix: debounce updating the polyline edges in drag-node behavior; +- fix: toolbar redo undo max clone in drag-node behavior; +- feat: dagre layout with combo; +- feat: cubic-vertical and cubic-horizontal with curveOffset and minCurveOffset + +#### 4.1.12 + +- chore: update layout with alpha gwebgpu; +- chore: update algorithm with fixed publicPath problem; + +#### 4.1.11 + +- chore: link correct core; + +#### 4.1.10 + +- chore: update algorithm; + +#### 4.1.9 + +- feat: allowDragOnItem for drag-canvas behavior; +- fix: drag-canvas with two fingers on mobile affects zoom-canvas; + +#### 4.1.8 + +- fix: shouldBegin false for zoom-canvas behavior; +- fix: shouldBegin originScale from graph zoom; +- fix: error in collapse-expand with touch on canvas; + +#### 4.1.7 + +- fix: polyline with negative endpoints; +- fix: polyline direction when linkCenter; +- fix: remove g6-core browser since it has no umd output; +- feat: custom texts for the time range and time point text in timeBar plugin; +- chore: types for strict mode; + +#### 4.1.6 + +- fix: webworker problem after removing broswer in pc and g6; + +#### 4.1.5 + +- fix: wrong style for modelRect after updating and state changing, closes: #2613; +- fix: drag-canvas with shouldBegin false, closes: #2571; +- fix: pack plugin with es module, closes: #2577; +- feat: dijkstra with multiple shortest paths, closes: #2297; +- fix: setMode while the delegates of brush-select and drag-node is on the canvas, closes: #2607; +- docs: update the english TimeBar docs, closes: #2597; +- fix: TimeBar time point switch text configurable, closes: #2597; + +#### 4.1.4 + +- fix: drag-canvas with touch on mobile; + +#### 4.1.2 + +- fix: registerBehavior export problem; +- fix: shouldEnd of create-edge with groupByTypes as false; +- fix: collapse and expand a combo with an empty sub combo error; +- fix: update padding of rect combo; +- fix: the graph in the minimap with circular layout is not centered, closes: #2555; +- fix: edge background displays on a wrong place when autoRotate is true; + +#### 4.1.1 + +- fix: soomth-convex hull with one line nodes leads to unshift problem; +- fix: zoom-canvas to optimizeZoom and hide the label, the label will not show up any more problem; +- fix: the ts type for parameter of timing event listener, closes: #2499; + +#### 4.1.0 + +- chore: ts lint; +- feat: getEdgeConfig for create-edge behavior; +- fix: uniqueId with timestamp and random; +- fix: fix zoom-canvas and drag-canvas with enableOptimize conflict problem shrink the settimeout; + +#### 4.1.0-beta.1 + +- chore: unpack the g6 into core, pc, element, plugin, mobile, and exported by g6; +- feat: layout with onLayoutEnd and custom layout with tag; +- feat: emit beforecollapseexpandcombo and aftercollapseexpandcombo; +- fix: toolbar for firefox and other browsers; +- fix: edge label position with state problem; +- fix: set item state to false at the first time; +- fix: hull with one node; +- fix: combo state size problem; +- fix: state with fontSize changed problem; +- fix: edge label with background when the two end nodes are overlapped; +- fix: text rasidual of timebar; +- fix: maximum stack size problem for image node type, fix: #2383; + +#### 4.0.3 + +- fix: state style restore for non-circle shapes; + +#### 4.0.2 + +- fix: node and edge state style with update problem; +- fix: import lib problem; +- fix: import node module problem; +- fix: hidden shapes show up after zoom-canvas or drag-canvas with enableOptimize; +- fix: tooltip for combo; +- fix: update edge with false endArrow and startArrow; + +#### 4.0.1 + +- fix: glslang problem; + +#### 4.0.0-beta.0 + +- feat: fruchterman and gforce layout with gpu; +- feat: gforce; +- feat: updateChildren API for TreeGraph; +- feat: louvain clustering algorithm; +- feat: container of plugins with dom id; +- feat: label propagation clustering algorithm; +- feat: get color sets by subject color array; +- feat: canvas context menu; +- feat: stop gforce; +- feat: dark rules for colors; +- fix: text redidual problem, closes: #2045 #2193; +- fix: graph on callback parameter type problem, closes: #2250; +- fix: combo zIndex problem; +- fix: webworker updateLayoutCfg problem; +- fix: drag-canvas and click node on mobile; + +#### 3.8.5 + +- fix: get fontFamily of the window in global leads to DOM depending when using bigfish; + +#### 3.8.4 + +- feat: new version of basic styles for light version; +- feat: shortcuts-call behavior for calling a Graph function by shortcuts; +- feat: color generate util function getColorsWithSubjectColor; +- fix: drag-canvas on mobile problem; +- fix: style update problem with stateStyles in the options of registerNode; + +#### 3.8.3 + +- feat: drag the viewport of the minimap out of the the view; +- fix: extend modelRect with description problem, closes: #2235; + +#### 3.8.2 + +- feat: graph.setImageWaterMarker, graph.setTextWaterMarker API; +- feat: zoom-canvas support mobile; +- fix: drag-canvas behavior support scalable range, closes: #2136; +- fix: TreeGraph changeData clear all states, closes: #2173; +- chore: auto zoom tooltip & contextMenu component when zoom-canvas; +- chore: upgrade @antv/g-canvas; +- feat: destroyLayout API for graph, closes: #2140; +- feat: clustering for force layout, closes: #2196; +- fix: svg renderer minimap hidden elements probem, closes: #2174; +- feat: add extra parameter graph for menu plugin, closes: #2204; +- fix: tooltip plugin, crossing different shape cant execute the getContent function, closes: #2153; +- feat: add edgeConfig for create-edge behavior, closes: #2195; +- fix: remove the source node while creat-edge; +- feat: create-edge for combo, closes: #2211; +- fix: update the typings for G6Event; + +#### 3.8.1 + +- fix: update edge states with updateItem problem, closes: #2142; +- fix: create-edge behavior with polyline problem, closes: #2165; +- fix: console.warn show duplicate ID, closes: #2163; +- feat: support the drag-canvas behavior on the mobile device, closes: #816; +- chore: timeBar component docs; + +#### 3.8.0 + +- fix: treeGraph render with addItem and stack problem, closes: #2084; +- feat: G6 Interactive Document GraphMarker; +- feat: registerNode with jsx support afterDraw and setState; +- feat: edge filter lens plugin; +- feat: timebar plugin; + +#### 3.7.3 + +- fix: update G to fix the shape disappear when it has been dragged out of the view port problem, closes: #2078, #2030, #2007; +- fix: redo undo with treeGraph problem; +- fix: remove item with itemType problem, closes: #2096. + +#### 3.7.2 + +- fix: toolbar redo undo addItem with type problem, closes #2043; +- fix: optimized drag-canvas with hidden items; +- fix: state style with 0 value problem, closes: #2039; +- fix: layout with webworker leads to twice beforelayout, closes: #2052; +- fix: context menu with sibling doms of graph container leads to position problem, closes: #2053; +- fix: changeData with combos problem, closes: #2064; +- fix: improve the position of the context menu before showing up; +- feat: fisheye allows user to config the trigger of scaling range(r) and magnify factor(d) by scaleRBy and scaleDBy respectively; +- feat: add the percent text of magnify factor(d) for fisheye and users are allowed to configure it by show showDPercent. + +#### 3.7.1 + +- fix: hide the tooltip plugin when drag node and contextmenu, closes #1975; +- fix: processParellelEdges without edge id problem; +- fix: label background with left, right position problem, closes #1861; +- fix: create-edge and redo undo problem, #1976; +- fix: tooltip plugin with shouldBegin problem, closes #2006; +- fix: tooltip behavior with shouldBegin problem, closes #2016; +- fix: the position of grid plugins when there is something on the top of the canvas, closes: #2012; +- fix: fisheye destroy and new problem, closes: #2018; +- fix: node event with wrong canvasX and canvasY problem, closes: #2027; +- fix: drag combo and drag node to drop on canvas/combo/node problem; +- feat: improve the performance on the combos; +- fix: redo and undo problem when update item after additem, closes #2019; +- feat: hide shapes beside keyShape while zooming; +- feat: improve the performance on the combos. + +#### 3.7.0 + +- feat: chart node; +- feat: bubble set; +- feat: custom node with JSX; +- feat: minimum spanning tree algorithm; +- feat: path finding algorithm; +- feat: cycle finding algorithm; +- chore: update antv/hierarchy to fix indented tree with dropCap problem. + +#### 3.6.2 + +- feat: find all paths and the shortest path between two nodes; +- feat: fisheye with dragging; +- feat: fisheye with scaling range and d; +- feat: click and drag to create an edge by create-edge behavior; +- feat: process multiple parallel edges to quadratic with proper curveOffset; +- fix: polyline with rect and radius=0 problem; +- fix: arrow state & linkpoint; +- fix: the position of the tooltip plugin; +- fix: drop a node onto a sub node of a combo; +- chore: update hierarchy to solve the children ordering problem for indented tree layout; +- chore: extract the public calculation to enhance the performance of fisheye. + +#### 3.6.1-beta + +- chore: update g-canvas to support quickHit and pruning the rendering of the graph outside the viewport; +- feat: add statistical chart nodes; +- feat: add hull for create smooth contour to include specific items; +- fix: clear combos before render; +- fix: menu plugin with clickHandler problem. + +#### 3.6.1 + +- feat: image minimap; +- feat: visible can be controlled in the data; +- feat: item type for tooltip plugin; +- feat: menu plugin with shouldUpdate; +- fix: tooltip plugin position and hidden by removeItem; +- fix: tooltip behavior hidden by removeItem; +- fix: menu plugin with clicking on canvas problem; +- fix: menu plugin with clickHandler problem; +- fix: createCombo with double nodes problem. + +#### 3.6.0 + +- feat: fisheye lens plugin; +- feat: lasso-select behavior; +- feat: TimeBar plugin; +- feat: ToolBar plugin. + +#### 3.5.12 + +- fix: node:click is triggered twice while clicking a node; +- fix: update combo edge when drag node out of it problem; +- feat: animate configuration for combo, true by default; +- fix: calling canvas.on('\*', ...) instead of origin way in event controller leads to malposition while dragging nodes with zoomed graph. + +#### 3.5.11 + +- feat: graph.priorityState api; +- feat: graph.on support name:event mode. +- fix: combo edge with uncorrect end points; +- fix: combo polyline edge with wrong path; +- fix: getViewCenter with padding problem; +- fix: cannot read property 'getModel' of null problem on contextmenu when the target is not an item; +- feat: allow user to configure the initial positions for empty combos; +- feat: optimize by hiding edges and shapes which are not keyShape while dragging canvas; +- feat: fix the initial positions by equably distributing for layout to produce similar result. + +#### 3.5.10 + +- fix: fitView and fitCenter with animate in the initial state; +- fix: dulplicated edges in nodeselectchange event of brush-select; +- fix: triple click and drag canvas problem; +- fix: sync the minZoom and maxZoom in drag-canvas and graph; +- fix: integrate getSourceNeighbors and getTargetNeighbors to getNeighbors(node, type); +- feat: initial x and y for combo data; +- feat: dagre layout supports sortByCombo; +- feat: allow user to disable relayout in collapse-expand-combo behavior; +- feat: dijkstra shortest path lenght algorithm. + +#### 3.5.9 + +- fix: multiple animate update shape for combo; +- fix: removeItem from a combo. + +#### 3.5.8 + +- fix: combo edge problem, issues #1722; +- feat: adjacency matrix algorithm; +- feat: Floyd Warshall shortest path algorithm; +- feat: built-in arrows; +- feat: built-in markers; +- fix: force layout with addItem and relayout; +- fix: create combo with parentId problem; +- feat: allow user to configure the pixelRatio for Canvas; +- chore: update G to resolve the blur canvas problem. + +#### 3.5.7 + +- feat: shouldBegin for click-select behavior; +- feat: graph.getGroup, graph.getContainer, graph.getMinZoom, graph.setMinZoom, graph.getMaxZoom, graph.setMaxZoom, graph.getWidth, graph.getHeight API; +- fix: combo edge dashLine attribute; +- fix: combo collapse and expand with edges problem; +- fix: destroy the tooltip DOMs when destroy the graph; +- fix: unify the shape names for custom node and extended node; +- fix: update the edges after first render with collapsed combos. + +#### 3.5.6 + +- feat: dropCap for indented TreeGraph layout. + +#### 3.5.5 + +- fix: custom node with setState problem; +- fix: validationCombo in drag-combo and drag-node. + +#### 3.5.3 + +- feat: focusItem with animation; +- feat: generate the image url of the full graph by graph.toFullDataUrl; +- fix: graph dispears after being dragged out of the canvas and back; +- fix: the graph cannot be dragged back if it is already out of the view; +- fix: size and radius of the linkPoints problem; +- fix: combo graph with unused state name in comboStateStyles; +- fix: preventDefault in drag-canvas behavior. + +#### 3.5.2 + +- feat: degree algorithm; +- feat: graph.getNodeDegree; +- fix: downloadFullImage changes the matrix of the graph problem; +- fix: circular layout modifies the origin data with infinite hierarchy problem. + +#### 3.5.1 + +- feat: graph.fitCenter to align the graph center to canvas center; +- fix: getType is not a function error occurs when addItem with point; +- fix: checking comboTrees avaiability; +- fix: error occurs when createCombo into the graph without any combos; +- fix: endPoint and startPoint are missing in modelConfig type; +- fix: edge background leads to empty canvas when the autoRotate is false; +- fix: combo state style bug. + +#### 3.5.0 + +- feat: combo and combo layout; +- feat: graph algorithms: DFS, BFS and circle detection; +- feat: add `getNeighbors`, `getSourceNeighbors`, `getTargetNeighbors` methods on Graph and Node; +- feat: add `getID` method on Item; +- fix: All Configuration type declarations are migrated to types folder, refer [here](https://github.com/antvis/G6/commit/3691cb51264df8529f75222147ac3f248b71f2f6?diff=unified#diff-76cf0eb5e3d8032945f1ac79ffc5e815R6); +- fix: Some configuration type declarations have removed the `I` prefix, refer [here](https://github.com/antvis/G6/commit/3691cb51264df8529f75222147ac3f248b71f2f6?diff=unified#diff-aa582974831cee2972b8c96cfcce503aR16); +- feat: Util.getLetterWidth and Util.getTextSize. + +#### 3.4.10 + +- fix: TreeGraphData type with style and stateStyles; +- fix: wrong controlpoint position for bezier curves with getControlPoint. + +#### 3.4.9 + +- fix: transplie d3-force to support IE11. + +#### 3.4.8 + +- feat: update the keyShape type minimap when the node or edge's style is updated; +- fix: problem about switching to another applications or browser menu and then switch back, the drag-canvas does not take effect; +- fix: fix the problem about fail to render the graph when the animate and fitView are true by turn off the animate for rendering temporary; +- fix: curveOffset for arc, quadratic, cubic edge. + +#### 3.4.7 + +- feat: downloadFullImage when the (part of) graph is out of the screen; +- feat: With pre-graph has no layout configurations and no positions in data, calling changeData to change into a new data with positions, results in show the node with positions in data; +- feat: allow user to assign curveOffset and curvePostion for Bezier curves; +- fix: moveTo wrong logic problem; +- fix: removeItem to update the minimap. + +#### 3.4.6 + +- same as 3.4.5, published wrongly. + +#### 3.4.5 + +- feat: background of the label on node or edge; +- feat: better performance of minimap; +- fix: minimap viewport displace problem; +- feat: offset of tooltip; +- fix: the length of the node's name affects the tree layout; +- fix: toFront does not work for svg renderer; +- fix: error occurs when the fontSize is smaller than 12 with svg renderer; +- fix: changeData clears states; +- fix: state does not work when default labelCfg is not assigned. + +#### 3.4.4 + +- feat: background color for downloadImage and toDataURL; +- feat: support configure image for grid plugin; +- fix: initial position for fruchterman layout; +- refactor: clip for image node. +- fix: cubic with only one controlPoint error; +- fix: polyline without L attributes. + +#### 3.4.3 + +- fix: support extends BehaviorOption; +- fix: click-select Behavior support multiple selection using ctrl key. + +#### 3.4.2 + +- feat: zoom-canvas behavior supports hiding non-keyshape elements when scaling canvas; +- refactor: when the second parameter is null, clearItemStates will clear all states of the item; +- fix: [changeData bug](https://github.com/antvis/G6/issues/1323); +- fix: update antv/hierarchy to fix fixedRoot for TreeGraph; +- fix: problem of a graph has multiple polyline edges; +- fix: problem of dagre with controlPoints and loop edges. + +#### 3.4.1 + +- feat: force layout clone original data model to allow the customized properties; +- fix: BehaviorOptions type error; +- fix: fitView the graph with data whose nodes and edges are empty arrays; +- fix: rect node positions are changed after calling graph.changeData; +- fix: drag behavior is disabled when the keys are released invalidly; +- refactor: update G and the fill of custom arrow should be assigned by user. + +#### 3.4.0 + +- feat: SVG renderer; +- refactor: new state mechanism with multiple values, sub graphics shape style settings. + +#### 3.3.7 + +- feat: beforeaddchild and afteraddchild emit for TreeGraph; +- feat: built-in nodes' labels can be captured; +- fix: drag shadow caused by localRefresh, update the g-canvas version; +- fix: abnormal polyline bendding; +- fix: collapse-expand trigger problem; +- fix: update nodes with empty string label; +- fix: abnormal rendering when a graph has image nodes and other type nodes. + +#### 3.3.6 + +- feat: support edge weight for dagre layout; +- feat: automatically add draggable to keyShape, users do not need to assign it when custom a node or an edge; +- fix: cannot read 0 or null problem in getPointByCanvas; +- fix: brush-select bug; +- fix: set autoDraw to canvas when graph's setAutoPaint is called; +- fix: modify the usage of bbox in view controller since the interface is chagned by G; +- fix: the shape.attr error in updateShapeStyle; +- fix: local refresh influence on changeData; +- refactor: upgrade g-canvas to 0.3.23 to solve lacking of removeChild function; +- doc: update the demo fo custom behavior doc; +- doc: add plugin demos and cases for site; +- doc: fix shouldUpdate problem in treeWithLargeData demo on the site. + +#### 3.3.5 + +- fix: 3.3.4 is not published successfully; + +#### 3.3.4 + +- fix: 3.3.3 is not published successfully; +- fix: delegate or keyShape type minimap does not display bug; +- fix: dragging bug on minimap with a graph whose bbox is nagtive; +- fix: null matrix bug, create a unit matrix for null. + +#### 3.3.3 + +- fix: delegate or keyShape type minimap does not display bug; +- fix: null matrix in focus() and getLoopCfgs() bug. + +#### 3.3.2 + +- fix: ts type export problem; +- fix: edge with endArrow and autoRotate label bug; +- fix: code prettier; +- fix: line with control points bug; +- fix: matrix null bug. + +#### 3.3.1 + +- fix: resolve 3.3.0 compatibility problem. + +#### 3.3.0 + +- Graph API + - refactor: delete removeEvent function, use off; +- refactor: parameters of Shape animate changed, shape.animate(toAttrs, animateCfg) or shape.animate(onFrame, animateCfg); +- feat: descriptionCfg for modelRect to define the style of description by user; +- feat: update a node from without some shapes to with them, such as linkPoints, label, logo icon and state icon for modelRect; +- feat: the callback paramter of event nodeselectchange is changed to { target, selectedItems, ... }; +- feat: support stateStyles in node and edge data; +- feat: calculate pixelRatio by G automatically, user do not need to assign it to graph instance; +- chore: G 4.0 +- refactor: refreshLayout of TreeGraph is renamed as layout +- fix: no fan shape in G any more +- feat: recommand to assign name for each shape when addShape +- fix: do not support SVG renderer anymore. no renderer for graph configuration anymore +- refactor: plugins usage is changed into new G6.PluginName() + +#### 3.2.7 + +- feat: supports create the group without nodes in node-group; +- fix: supports destoryed properties and fix issue 1094; + +#### 3.2.6 + +- feat: supports sort the nodes on one circle according to the data ordering or some attribute in radial layout +- fix: grid layout with cols and rows +- feat: fix the nodes with position information in their original data and random the positions of others when the layout is not defined for graph + +#### 3.2.5 + +- fix: click-select trigger error +- fix: solved position problem for minimap + +#### 3.2.4 + +- fix: typescript compile error +- fix: delete sankey lib + +#### 3.2.3 + +- fix: group position error +- fix: supports not set layout type + +#### 3.1.5 + +- feat: supports g6 types file +- fix: set brush-select trigger param to ctrl not work +- fix: when set fitView to true, drag-group Behavior not get desired positon + +#### 3.1.3 + +- feat: radial layout nonoverlap iterations can be controlled by user +- feat: add lock, unlock and hasLocked function, supports lock and unlock node +- fix: mds with discrete points problem +- fix: fruchterman-group layout title position for rect groups + +#### 3.1.2 + +- feat: default behavior supports configuration trigger mode +- feat: node combining supports configuration title +- fix: update demo state styles + +#### 3.1.1 + +- fix: update node use custom config +- fix: update demo +- feat: default node implement getShapeStyle function + +#### 3.1.0 + +- feat: support for rich layouts:random, radial, mds, circular, fruchterman, force, dagre +- feat: more flexible configuration for shape +- feat: build-in rich default nodes +- feat: cases that provide layout and default nodes + +#### 3.0.7-beta.1 + +`2019-09-11` + +- fix: zoom-canvas support IE and Firefox + +#### 3.0.6 + +`2019-09-11` + +- fix: group data util function use module.exports +- feat: update @antv/hierarchy version + +#### 3.0.5 + +`2019-09-10` + +- feat: support add and remove group +- feat: support collapse and expand group +- feat: add graph api: collapseGroup and expandGroup + +#### 3.0.5-beta.12 + +- feat: add rect group +- feat: add rect group demo +- feat: add chart node + +--- + +#### 3.0.5-beta.10 + +- feat: add 5 chart node +- feat: collapse-expand tree support click and dblclick by trigger option +- fix: drag group bug fix + +#### 3.0.5-beta.9 + +- feat: support render group +- feat: support drag group, collapse and expand group, drag node in/out group +- feat: add drag-group、collapse-expand-group and drag-node-with-group behavior +- feat: add drag-group and collapse-expand-group demo +- feat: add register list node demo + +#### 3.0.5-beta.8 + +`2019-07-19` + +- feat: add five demos +- refactor: update three behaviors + +#### 2.2.5 + +`2018-12-20` + +- feat: add saveimage limitRatio + +#### 2.2.4 + +`2018-12-20` + +- fix: bug fix + +#### 2.2.3 + +`2018-12-10` + +- fix: bug fix + +#### 2.2.2 + +`2018-11-30` + +- fix: tree remove guide will not getEdges.closes #521 + +#### 2.2.1 + +`2018-11-25` + +- fix: Compatible with MOUSEWHEEL +- fix: fadeIn aniamtion +- fix: fix wheelZoom behaviour by removing the deprecated mousewheel event + +#### 2.2.0 + +`2018-11-22` + +- fix: Graph read zIndex +- refactor: Animation + +#### 2.1.5 + +`2018-10-26` + +- fix: svg pixelRatio bug +- feat: add wheel event + +#### 2.1.4 + +`2018-10-06` + +- fix: custom math.sign to compatible with ie browser.Closes #516. +- fix: legend component from @antv/component +- feat: update svg minimap && fix svg dom event + +#### 2.1.3 + +`2018-09-27` + +- feat: add label rotate +- feat: if there is no items the graph box equal canvas size + +#### 2.1.2 + +`2018-09-19` + +- fix: dom getShape bug.Closes #472 +- fix: template.maxSpanningForest bug + +#### 2.1.1 + +`2018-09-17` + +- fix: tool.highlightSubgraph calculate box bug +- fix: plugin.grid.Closes #479 +- chore(dev): upgrade babel & torchjs + +#### 2.1.0 + +`2018-09-03` + +- feat: svg render +- feat: plugin.layout.forceAtlas2 +- feat: plugin.tool.fisheye +- feat: plugin.tool.textDisplay +- feat: plugin.tool.grid +- feat: plugin.template.tableSankey +- feat: plugin.edge.polyline + +#### 2.0.5 + +`2018-07-12` + +- improve: add g6 arrow + +#### 2.0.4 + +`2018-07-12` + +- feat: layout export group.Closes #355 +- feat(plugin): add tool.tooltip. Closes #360. +- style: change the calling way of forceAtlas2 on template.maxSpanningForest +- fix: origin tree data collapsed is true tree edge visible bug.Closes #357 +- fix: remove the forceAtlas.js in template.maxSpanningForest, use forceAtlas from layout.forceAtlas2 +- fix: add demos: plugin-fisheye, plugin-forceAtlas2, gallery-graphanalyzer +- fix: add demos: plugin-forceAtlas2, plugin-fisheye + +#### 2.0.3 + +`2018-06-29` + +- feat: update g to 3.0.x. Closes #346 +- fix: group should use rect intersect box. Close #297 +- fix(plugin): dagre edge controlpoints remove start point and end point +- style: remove some annotations +- chore: update torchjs && improve demo name + +#### 2.0.2 + +`2018-06-13` + +- chore(plugin): require g6 by src/index +- chore(dev test): remove useless test script +- fix(plugin) minimap destroy Closes #308 +- fix(saveImage) saveImage bug +- fix(event): fix dom coord. Closes #305 + +#### 2.0.1 + +`2018-06-11` + +- fix: reDraw edge after layout +- feat: add quadraticCurve config cpd +- feat: add beforelayout && afterlayout event +- chore: .travis.yml add add Node.js +- chore: .travis.yml cache node_modules + +#### 2.0.0 + +`2018-06-06` + +- refactor: refactor architecture && code + +#### 1.2.1 + +`2018-03-15` + +- feat: layout interface + +#### 1.2.0 + +`2018-01-15` + +- fix: nodeActivedBoxStyle spelling error +- fix: error when deleting a circle +- fix: trigger dragstart while right clicking and moveing +- feat: Unify Layout mechanism +- feat: Plugin mechanism +- feat: Data filter mechanism +- feat: Activated interface +- feat: Action wheelZoomAutoLabel +- feat: configuration of graph -- preciseAnchor +- remove: Global.preciseAnchor +- remove: Layout.Flow、Layout.Force +- improve: html container strategy + +#### 1.1.6 + +`2017-10-15` + +- fix: pack problem in layout algorithm + +#### 1.1.5 + +`2017-09-15` + +- fix: dragCanvas is effective while mousemove, prevent it from affecting click events +- fix: unactivate pick-up in activeRectBox of node + +#### 1.1.4 + +`2017-08-15` + +- feat: graph.invertPoint() +- feat: third configuration of anchor to support style setting, float style, connection +- feat: item.getGroup() +- feat: events -- afteritemrender、itemremove、itemadd +- feat: behaviourSignal +- improve: mouseWheel is affective after focusing the canvas + +#### 1.1.3 + +`2017-08-8` + +- feat: Graph configuration -- useNodeSortGroup +- feat: Global.nodeDelegationStyle, Global.edgeDelegationStyle, isolate the delegation of edge and node on graph +- fix: itemremove is triggered before destroying a graph + +#### 1.1.2 + +`2017-08-01` + +- feat: dragBlankX dragBlankY + +#### 1.1.1 + +`2017-07-18` + +- improve: dragNode protect mechanism + +#### 1.1.0 + +`2017-07-05` + +- feat: HTML node +- feat: mapper support callback function +- feat: Graph interfaces -- updateMatrix、changeSize、showAnchor、hideAnchor、updataNodesPosition +- feat: tool functions -- Util.isNode()、Util.isEdge() +- feat: Shape polyLineFlow +- feat: dragEdgeEndHideAnchor、dragNodeEndHideAnchor、hoverAnchorSetActived、hoverNodeShowAnchor + +#### 1.0.7 + +`2017-06-21` + +- fix: draw one more time in 16ms after first draw +- improve: add zoom by scroll in edit mode + +#### 1.0.6 + +`2017-06-15` + +- fix: compatible in chrome in windows. triggering mousemove after first click leads to wrong click event. +- feat: support fix size graphics +- feat: analysis mode +- feat: updateNodesPositon update a set of nodes' position +- improve: change useAnchor to be a configuration of edge + +#### 1.0.5 + +`2017-06-01` + +- feat: downloadImage support saving with name +- feat: automatically detect tooltip padding +- improve: stop the action while mouse dragging out of the canvas + +#### 1.0.4 + +`2017-05-20` + +- fix: tree changeData Bug +- fix: when getAnchorPoints returns auto, anchor is the intersection of edge and the bounding box +- fix: generate node label according to isNull +- feat: viewport parameters -- tl、tc、tr、rc、br、bc、bl、lc、cc +- improve: reduce tolerance to improve the accuracy of interception +- improve: improve tooltip event mechanisom to enhance performance + +#### 1.0.3 + +`2017-05-10` + +- feat: graph.guide().link() + +#### 1.0.2 + +`2017-05-10` + +- fix: Object.values => Util.getObjectValues +- fix: when anchorPoints is auto, there is only anchorpoint on edge, it will also return the intersection +- fix: tree update interface Bug +- improve: represent positions information by group.transfrom() + +#### 1.0.1 + +`2017-04-22` + +- fix: copy and paste bug +- feat: draw once in 16ms +- feat: itemactived itemunactived itemhover itemupdate itemmouseenter itemmouseleave +- improve: be clear the status of graphics before activating graphics by frame selection +- improve: dragAddEdge, linkable to anchor +- improve: performance of animation + +#### 1.0.0 + +`2017-03-31` + +- feat: fitView configurations +- feat: graph.zoom() +- feat: wheelZoomHideEdges hide the edges while zooming by wheel +- feat: dragHideEdges hide the edge while dragging edge +- feat: graph.filterBehaviour() +- feat: graph.addBehaviour() +- feat: graph.changeLayout() +- feat: read interface, re-define save interface +- feat: graph.snapshot, graph.downloadImage +- feat: graph.autoSize() +- feat: graph.focusPoint() +- feat: tree graph、net graph +- feat: interaction mechanism -- event => action => mode +- feat: animation mechanism +- feat: itemmouseleave、itemmouseenter +- remove: graph.refresh() +- remove: graph.changeNodes() +- remove: graph attributes -- zoomable、dragable、resizeable、selectable +- improve: anchor mechanism +- improve: hide G6.GraphUtil functions, unified in G6.Util +- improve: replace g-canvas-core to g-canvas to improve performance +- improve: Global.nodeAcitveBoxStyle instead of Global.nodeBoxStyle +- improve: afterAdd => afteradd +- improve: G6.Graph to be an abstract class + +#### 0.2.3 + +`2017-03-2` + +- fix: dragable for controlling dragable under default mode +- feat: graph.converPoint() +- feat: graph.autoSize() +- feat: rightmousedown leftmousedown wheeldown +- improve: use try catch to prevent the length of getPoint of path equals zero + +#### 0.2.2 + +`2017-02-24` + +- fix: add px totooltip css padding +- fix: tooltip mapping error +- fix: accurate intersection +- fix: zoom error on double accuracy screen +- fix: buonding box extended from keyShape +- feat: afterAdd +- feat: dblclick +- improve: width、height default null +- improve: remove hovershape on node +- improve: tooltip defense mechanism + +#### 0.2.1 + +`2017-02-14` + +- fix: rollback when add node +- fix: apply tranformation of parent container while calculating bounding box +- feat: waterPath +- feat: tooltip tip information +- feat: mouseover +- feat: multiSelectable, default false +- feat: set forceFit to true while width is undefined +- improve: zoomable、dragable、resizeable、selectable default true + +#### 0.2.0 + +`2017-02-07` + +- feat: accurate anchor mechanism +- feat: GraphUtil.getEllipsePath +- feat: GraphUtil.pointsToPolygon +- feat: GraphUtil.pointsToBezier +- feat: GraphUtil.snapPreciseAnchor +- feat: GraphUtil.arrowTo +- feat: GraphUtil.drawEdge +- feat: bezierQuadratic +- feat: node.show +- feat: node.hide +- feat: node.getLinkNodes +- feat: node.getUnLinkNodes +- feat: node.getRelativeItems +- feat: node.getUnRelativeItems +- feat: edge.show +- feat: edge.hide +- feat: Shape afterDraw +- improve: the controlling point positions of Bezier Curve 改进贝塞尔曲线控制点位置 +- improve: grpah.delete => graph.del +- improve: error when adding id + +#### 0.1.4 + +`2017-01-17` + +- fix: delegator of dragging a node is the center of bbox +- fix: use cardinality sort for all the sorting algorithm +- fix: random id on edges +- feat: level sort on edges, edge labels on the top level +- feat: while extending shape is undefined when register an edge, find the extending shaoe automatically + +#### 0.1.3 + +`2017-01-15` + +- fix: judge the existance of the object while operating assistGrid +- feat: rollback judgement, default unactivate +- feat: style mapping channel +- feat: return the intersections while getAnchorPoints is null or returns false +- feat: bezierHorizontal、bezierVertical +- improve: 'eventEnd' + +#### 0.1.2 + +`2017-01-12` + +- fix: judge the configuration before updating grid +- fix: the size of graphContainer in unsetable, setted by inner canvas +- fix: will not add an edge if the target or source is undefined +- fix: changeSize() maximum tolerance for error +- feat: graph.get('el') to get canvas DOM +- feat: event exposures shape + +#### 0.1.1 + +`2017-01-09` + +- feat: entrance of graph is G6.Graph + +#### 0.1.0 + +`2017-01-07` + +- feat: color calculation library +- feat: hot key +- feat: updo, redo +- feat: copy, paste +- feat: reset zoom, auto zoom +- feat: tree graph, linear graph, sankey graph, flow laout +- feat: flow chart package +- feat: timing diagram package +- feat: single selection, frame selection +- feat: node deformation +- feat: edge deformation +- feat: drag node and edge +- feat: link edge and node +- feat: drag canvas +- feat: zoom +- feat: select mode +- feat: integrate g-graph diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..7ac73188b9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Alipay.inc + +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. diff --git a/README.en-US.md b/README.en-US.md new file mode 100644 index 0000000000..2042bf95ec --- /dev/null +++ b/README.en-US.md @@ -0,0 +1,170 @@ +# G6: A Graph Visualization Framework in TypeScript + +![](https://user-images.githubusercontent.com/6113694/45008751-ea465300-b036-11e8-8e2a-166cbb338ce2.png) + +[![travis-ci](https://img.shields.io/travis/antvis/g6/master.svg)](https://travis-ci.org/antvis/g6) [![codecov](https://codecov.io/gh/antvis/G6/branch/master/graph/badge.svg)](https://codecov.io/gh/antvis/G6) ![typescript](https://img.shields.io/badge/language-typescript-red.svg) ![MIT](https://img.shields.io/badge/license-MIT-000000.svg) [![npm package](https://img.shields.io/npm/v/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6) [![NPM downloads](http://img.shields.io/npm/dm/@antv/g6.svg)](https://npmjs.org/package/@antv/g6) [![Percentage of issues still open](http://isitmaintained.com/badge/open/antvis/g6.svg)](http://isitmaintained.com/project/antvis/g6 'Percentage of issues still open') + +[中文 README](README.md) + +## What is G6 + +[G6](https://github.com/antvis/g6) is a graph visualization engine, which provides a set of basic mechanisms, including rendering, layout, analysis, interaction, animation, and other auxiliary tools. G6 aims to simplify the relationships, and help people to obtain the insight of relational data. + + + +Developers are able to build graph visualization **analysis** applications or graph visualization **modeling** applications easily. + + + + + +> Powerful Animation and Interactions + + + + + +> Powerful Layouts + +## Features + +- Abundant Built-in Items: Nodes and edges with free configurations; +- Steerable Interactions: More than 10 basic interaction behaviors ; +- Powerful Layout: More than 10 layout algorithms; +- Convenient Components: Outstanding ability and performance; +- Friendly User Experience: Complete documents for different levels of user requirements. TypeScript supported. + +G6 concentrates on the principle of 'good by default'. In addition, the custom mechanism of the item, interation behavior, and layout satisfies the customazation requirements. + + + +> Abundant Built-in Items + +## Installation + +```bash +$ npm install @antv/g6 +``` + +## Usage + + + +```js +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'node1', + label: 'Circle1', + x: 150, + y: 150, + }, + { + id: 'node2', + label: 'Circle2', + x: 400, + y: 150, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, + defaultNode: { + type: 'circle', + size: [100], + color: '#5B8FF9', + style: { + fill: '#9EC9FF', + lineWidth: 3, + }, + labelCfg: { + style: { + fill: '#fff', + fontSize: 20, + }, + }, + }, + defaultEdge: { + style: { + stroke: '#e2e2e2', + }, + }, +}); + +graph.data(data); +graph.render(); +``` + +[![Edit compassionate-lalande-5lxm7](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/compassionate-lalande-5lxm7?fontsize=14&hidenavigation=1&theme=dark) + +For more information of the usage, please refer to [Getting Started](https://g6.antv.antgroup.com/en/manual/getting-started). + +## Development + +```bash +$ npm install + +# lerna bootstrap for multiple packages +$ npm run bootstrap + +# build the packages +$ npm run build:all + +# if you wanna watch one of the packages, e.g. packages/core +$ cd ./packages/core +$ npm run watch + +# run test case +$ npm test + +# run test case in watch mode +npm test -- --watch ./tests/unit/algorithm/find-path-spec +DEBUG_MODE=1 npm test -- --watch ./tests/unit/algorithm/find-path-spec +``` + +## Documents + +- Tutorial +- Middle Guides +- Further Reading +- API Reference + +## React project integration + +For React project integration, we have an independent product recommendation: [Graphin](https://graphin.antv.vision), which is a toolkit based on G6 and React, that focuses on relational visual analysis. It's simple, efficient, out of the box. + +At present, Graphin has good practices in business graph analysis projects. For details, see [《Who uses Graphin》](https://github.com/antvis/Graphin/issues/212) + +## G6 Communication Group + +Welcome to join the **G6 Communication Group** or **G6 Communication Group-2** (DingTalk groups). We also welcome the github issues. + +

+ + + + + + + + + +

+## How to Contribute + +Please let us know what you are you going to help. Do check out [issues](https://github.com/antvis/g6/issues) for bug reports or suggestions first. + +## License + +[MIT license](./LICENSE). diff --git a/README.md b/README.md new file mode 100644 index 0000000000..83e53004c7 --- /dev/null +++ b/README.md @@ -0,0 +1,177 @@ +# G6:图分析引擎 + +![](https://user-images.githubusercontent.com/6113694/45008751-ea465300-b036-11e8-8e2a-166cbb338ce2.png) + +[![travis-ci](https://img.shields.io/travis/antvis/g6.svg)](https://travis-ci.org/antvis/g6) [![codecov](https://codecov.io/gh/antvis/G6/branch/master/graph/badge.svg)](https://codecov.io/gh/antvis/G6) ![typescript](https://img.shields.io/badge/language-typescript-red.svg) ![MIT](https://img.shields.io/badge/license-MIT-000000.svg) [![npm package](https://img.shields.io/npm/v/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6) [![NPM downloads](http://img.shields.io/npm/dm/@antv/g6.svg)](https://npmjs.org/package/@antv/g6) [![Percentage of issues still open](http://isitmaintained.com/badge/open/antvis/g6.svg)](http://isitmaintained.com/project/antvis/g6 'Percentage of issues still open') + +[English README](README.en-US.md) + +## 什么是 G6 + +[G6](https://github.com/antvis/g6) 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。旨在让关系变得透明,简单。让用户获得关系数据的 Insight。 + + + +基于 G6,用户可以快速搭建自己的 **图分析** 或 **图编辑** 应用。 + + + + + +> 强大的动画及交互 + + + + + +> 强大的布局 + +## G6 的特性 + +G6 作为一款专业的图可视化引擎,具有以下特性: + +- 丰富的元素:内置丰富的节点与边元素,自由配置,支持自定义; +- 可控的交互:内置 10+ 交互行为,支持自定义交互; +- 强大的布局:内置了 10+ 常用的图布局,支持自定义布局; +- 便捷的组件:优化内置组件功能及性能; +- 友好的体验:根据用户需求分层梳理文档,支持 TypeScript 类型推断。 + +除了默认好用、配置自由的内置功能,元素、交互、布局均具有高可扩展的自定义机制。 + + + +> 丰富的图元素 + +## 安装 + +```bash +$ npm install @antv/g6 +``` + +## 使用 + + + +```js +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'node1', + label: 'Circle1', + x: 150, + y: 150, + }, + { + id: 'node2', + label: 'Circle2', + x: 400, + y: 150, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, + defaultNode: { + type: 'circle', + size: [100], + color: '#5B8FF9', + style: { + fill: '#9EC9FF', + lineWidth: 3, + }, + labelCfg: { + style: { + fill: '#fff', + fontSize: 20, + }, + }, + }, + defaultEdge: { + style: { + stroke: '#e2e2e2', + }, + }, +}); + +graph.data(data); +graph.render(); +``` + +[![Edit compassionate-lalande-5lxm7](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/compassionate-lalande-5lxm7?fontsize=14&hidenavigation=1&theme=dark) + +更多关于 G6 使用的问题,请参考[快速上手](https://g6.antv.antgroup.com/manual/getting-started)。 + +## 开发 + +```bash +$ npm install + +# lerna bootstrap for multiple packages +$ npm run bootstrap + +# build the packages +$ npm run build:all + +# if you wanna watch one of the packages, e.g. packages/core +$ cd ./packages/core +$ npm run watch + +# run test case +$ npm test + +# run test case in watch mode +npm test -- --watch ./tests/unit/algorithm/find-path-spec +DEBUG_MODE=1 npm test -- --watch ./tests/unit/algorithm/find-path-spec +``` + +## 文档 + +- 入门教程 +- 核心概念 +- 扩展阅读 +- API + +## React 项目集成 + +针对 React 项目集成,我们有一款单独的产品推荐:[Graphin](https://graphin.antv.vision),它是基于 G6 封装的 React 组件库,专注在关系分析领域,简单高效,开箱即用。 + +目前 Graphin 在商业图分析项目中均有良好的实践,具体查看[《谁在使用 Graphin》](https://github.com/antvis/Graphin/issues/212) + +## G6 图可视化交流群 + +欢迎各界 G6 使用者、图可视化爱好者加入 **G6 图可视化交流群** 及 **G6 图可视化交流二群**(钉钉群,使用钉钉扫一扫加入)讨论与交流。Graphin 的使用者,爱好者请加入 **Graphin's Group Chat** + +> **G6 图可视化交流群** 已满员,该群会不定期移除不活跃的成员。 + +> 由于维护精力有限,**G6 图可视化交流群** 仅供社区同学相互交流,不进行答疑。欢迎对 G6 感兴趣的同学加入到答疑中来,非常感谢! + +

+ + + + + + + + + +

+ +## 如何贡献 + +请让我们知道您要解决或贡献什么,所以在贡献之前请先提交 [issues](https://github.com/antvis/g6/issues) 描述 bug 或建议。 + +## License + +[MIT license](./LICENSE). diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000000..e8b1ea1a23 --- /dev/null +++ b/lerna.json @@ -0,0 +1,29 @@ +{ + "version": "*", + "npmClient": "yarn", + "packages": ["packages/*"], + "useWorkspaces": true, + "command": { + "version": { + "ignoreChanges": ["*.md", "**/*.test.ts", "**/*.spec.ts"], + "message": "chore(release): publish" + }, + "bootstrap": { + "npmClientArgs": ["--no-package-lock", "--no-ci"] + } + }, + "changelog": { + "labels": { + "pr(fix)": ":bug: Bug Fix", + "pr(chore)": ":turtle: Chore", + "pr(feature)": ":tada: New feature", + "pr(breaking)": ":boom: Breaking Change", + "pr(internal)": ":house: Internal", + "pr(dependency)": ":shamrock: Dependency", + "pr(documentation)": ":book: Documentation", + "pr(enhancement)": ":rocket: Enhancement", + "pr(refactor)": ":100: Refactoring", + "pr(test)": ":white_check_mark: Test Case" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000..210785d29e --- /dev/null +++ b/package.json @@ -0,0 +1,90 @@ +{ + "name": "g6", + "private": true, + "workspaces": [ + "packages/*" + ], + "scripts": { + "build:demos": "cd ./packages/pc && npm run demos", + "build:site": "cd ./packages/site && npm run start", + "build:core": "cd ./packages/core && npm run build", + "build:element": "cd ./packages/element && npm run build", + "build:plugin": "cd ./packages/plugin && npm run build", + "build:pc": "cd ./packages/pc && npm run build", + "build:g6": "cd ./packages/g6 && npm run build", + "build:react-node": "cd ./packages/react-node && npm run build", + "build:all": "npm run build:core && npm run build:element && npm run build:plugin && npm run build:pc && npm run build:g6", + "lint:core": "cd ./packages/core && npm run lint", + "lint:element": "cd ./packages/element && npm run lint", + "lint:plugin": "cd ./packages/plugin && npm run lint", + "lint:pc": "cd ./packages/pc && npm run lint", + "lint:g6": "cd ./packages/g6 && npm run lint", + "lint:all": "npm run lint:core && npm run lint:element && npm run lint:plugin && npm run lint:pc && npm run lint:g6", + "build": "lerna run build --include-dependencies --stream", + "lint": "npm run lint:all", + "test:core": "cd ./packages/core && npm run test", + "test:element": "cd ./packages/element && npm run test", + "test:plugin": "cd ./packages/plugin && npm run test", + "test:pc": "cd ./packages/pc && npm run test", + "test": "npm run test:core && npm run test:element && npm run test:plugin && npm run test:pc", + "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", + "pretty-quick": "pretty-quick", + "clean": "lerna clean -y", + "clear": "lerna clean && lerna clean -y", + "clean:modules": "rimraf node_modules", + "bootstrap": "lerna bootstrap", + "ci": "npm run clean && npm run clean:modules && npm install && npm run bootstrap && npm run build:all && npm run lint:all && npm run test", + "ls": "lerna list" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged && lerna run --concurrency 1 --stream precommit" + } + }, + "lint-staged": { + "*.{js,jsx,tsx,ts,less,md,json}": [ + "pretty-quick —-staged" + ] + }, + "dependencies": { + "eslint": "^7.11.0", + "eslint-config-prettier": "^6.7.0", + "eslint-plugin-import": "^2.22.1", + "husky": "^4.2.5", + "lerna": "^3.19.0", + "lint-staged": "^10.2.2", + "monaco-editor": "0.29.1", + "monaco-editor-webpack-plugin": "5.0.0", + "normalize-url": "^7.0.3", + "npm-run-all": "^4.1.5", + "prettier": "^2.1.2", + "pretty-quick": "^3.0.2", + "react-monaco-editor": "0.40.0", + "rimraf": "^3.0.0", + "tslint": "^6.1.3", + "tslint-config-airbnb": "^5.11.2", + "tslint-config-prettier": "^1.18.0", + "tslint-eslint-rules": "^5.4.0", + "typescript": "^4.6.3" + }, + "devDependencies": { + "@types/react": "^16.9.35", + "@types/react-dom": "^16.9.8", + "@umijs/fabric": "^2.3.1", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-react": "^7.22.0", + "eslint-plugin-react-hooks": "^4.2.0", + "eslint-plugin-unicorn": "^27.0.0", + "pre-commit": "^1.2.2", + "react-scripts": "3.1.2" + }, + "resolutions": { + "@types/react": "^16.9.35", + "monaco-editor": "0.29.1", + "monaco-editor-webpack-plugin": "5.0.0", + "react-monaco-editor": "^0.40.0", + "normalize-url": "^4.1.0", + "sharp": "^0.30.4" + } +} diff --git a/packages/core/.eslintignore b/packages/core/.eslintignore new file mode 100644 index 0000000000..b1fc0c82cd --- /dev/null +++ b/packages/core/.eslintignore @@ -0,0 +1,22 @@ +build/ +coverage/ +lib/ +dist/ +mocks/ +node_modules/ +demos/ +.cache +public +bin +esm/ +es/ +examples/ +tests/ +stories/ +gatsby-browser.js +site/ +webpack.*.js +gatsby-*.js +global.d.ts +jest.config.js +.eslintrc.* diff --git a/packages/core/.fatherrc.js b/packages/core/.fatherrc.js new file mode 100644 index 0000000000..5185d1910b --- /dev/null +++ b/packages/core/.fatherrc.js @@ -0,0 +1,5 @@ +export default { + entry: './src/index.ts', + esm: 'babel', + cjs: 'babel', +}; diff --git a/packages/core/.npmignore b/packages/core/.npmignore new file mode 100644 index 0000000000..af137ec718 --- /dev/null +++ b/packages/core/.npmignore @@ -0,0 +1,83 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# lock +package-lock.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +.DS_Store + +# npmignore - content above this line is automatically generated and modifications may be omitted +# see npmjs.com/npmignore for more details. +test + +*.sw* +*.un~ +.idea +bin +demos +docs +temp +webpack-dev.config.js +webpack.config.js +public +.cache +site +examples +gatsby-browser.js +gatsby-config.js diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js new file mode 100644 index 0000000000..ac9f7eb785 --- /dev/null +++ b/packages/core/jest.config.js @@ -0,0 +1,19 @@ +module.exports = { + runner: 'jest-electron/runner', + testEnvironment: 'jest-electron/environment', + preset: 'ts-jest', + collectCoverage: false, + collectCoverageFrom: ['src/**/*.{ts,js}', '!**/node_modules/**', '!**/vendor/**'], + testRegex: '/tests/.*-spec\\.ts?$', + moduleDirectories: ['node_modules', 'src'], + moduleFileExtensions: ['js', 'ts', 'json'], + moduleNameMapper: { + '@g6/types': '/types', + '@g6/(.*)': '/src/$1', + }, + globals: { + 'ts-jest': { + diagnostics: false, + }, + }, +}; diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000000..4acf838c80 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,85 @@ +{ + "name": "@antv/g6-core", + "version": "5.0.0", + "description": "A Graph Visualization Framework in JavaScript", + "keywords": [ + "antv", + "g6", + "graph", + "graph analysis", + "graph editor", + "graph visualization", + "relational data" + ], + "homepage": "https://g6.antv.antgroup.com/", + "bugs": { + "url": "https://github.com/antvis/g6/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/antvis/g6" + }, + "license": "MIT", + "author": "https://github.com/orgs/antvis/people", + "files": [ + "package.json", + "es", + "lib", + "dist", + "LICENSE", + "README.md" + ], + "main": "lib/index.js", + "module": "es/index.js", + "types": "lib/index.d.ts", + "scripts": { + "start": "father build --watch", + "build": "npm run clean && father build", + "ci": "npm run build && npm run coverage", + "clean": "rimraf es lib", + "coverage": "jest --coverage", + "doc": "rimraf apis && typedoc", + "lint": "eslint --ext .js,.jsx,.ts,.tsx --format=pretty \"./\"", + "lint:src": "eslint --ext .ts --format=pretty \"./src\"", + "prettier": "prettier -c --write \"**/*\"", + "test": "jest", + "test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/graph/graph-spec.ts", + "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx", + "watch": "father build -w" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged & npm run test" + } + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx}": [ + "npm run lint-staged:js" + ] + }, + "dependencies": { + }, + "devDependencies": { + "@types/jest": "^25.2.1", + "@types/node": "13.11.1", + "@typescript-eslint/eslint-plugin": "^4.11.1", + "@umijs/fabric": "^2.0.0", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.6", + "eslint": "^7.7.0", + "event-simulate": "~1.0.0", + "father": "^2.29.1", + "husky": "^4.2.5", + "jest": "^26.0.1", + "jest-electron": "^0.1.7", + "jest-extended": "^0.11.2", + "lint-staged": "^10.2.11", + "pre-commit": "^1.2.2", + "prettier": "^2.0.5", + "rimraf": "^3.0.0", + "ts-jest": "^24.1.0", + "ts-loader": "^7.0.3", + "typescript": "^4.6.3", + "stats-js": "^1.0.1" + } +} \ No newline at end of file diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000000..d7ed2a9e9e --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,5 @@ +export * from './types'; + +export {}; + +export default {}; diff --git a/packages/core/src/interface/index.ts b/packages/core/src/interface/index.ts new file mode 100644 index 0000000000..399efa1659 --- /dev/null +++ b/packages/core/src/interface/index.ts @@ -0,0 +1,4 @@ +export * from './graph'; +export * from './behavior'; +export * from './item'; +export * from './shape'; diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/util/index.ts b/packages/core/src/util/index.ts new file mode 100644 index 0000000000..40f9a7908a --- /dev/null +++ b/packages/core/src/util/index.ts @@ -0,0 +1,2 @@ +const Util = { }; +export default Util; diff --git a/packages/core/tests/unit/template-spec.ts b/packages/core/tests/unit/template-spec.ts new file mode 100644 index 0000000000..f55424c538 --- /dev/null +++ b/packages/core/tests/unit/template-spec.ts @@ -0,0 +1,10 @@ +describe('template', () => { + const el = document.createElement('div'); + el.id = 'test-div-id'; + el.innerHTML = 'hello g6'; + document.querySelector('body').appendChild(el); + + it('div content', () => { + expect(document.querySelector('#test-div-id').innerHTML).toBe('hello g6'); + }); +}); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000000..d0a7d1bc71 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "rootDir": "./", + "module": "esnext", + "declaration": true, + "sourceMap": true, + "target": "es5", + "outDir": "lib", + "jsx": "react", + "importHelpers": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "types": ["jest"], + "skipLibCheck": true + }, + "include": ["src", "tests"], + "typedocOptions": { + "mode": "modules", + "out": "docs/api-ts", + "excludePrivate": true, + "excludeProtected": true, + "excludeExternals": true, + "plugin": "typedoc-plugin-markdown", + "theme": "docusaurus2" + } +} diff --git a/packages/element/.eslintignore b/packages/element/.eslintignore new file mode 100644 index 0000000000..f02222b2fe --- /dev/null +++ b/packages/element/.eslintignore @@ -0,0 +1,12 @@ +lib/ +dist/ +node_modules/ +bin +esm/ +es/ +tests/ +webpack.*.js +gatsby-*.js +global.d.ts +jest.config.js +.eslintrc.* diff --git a/packages/element/.fatherrc.js b/packages/element/.fatherrc.js new file mode 100644 index 0000000000..5185d1910b --- /dev/null +++ b/packages/element/.fatherrc.js @@ -0,0 +1,5 @@ +export default { + entry: './src/index.ts', + esm: 'babel', + cjs: 'babel', +}; diff --git a/packages/element/.npmignore b/packages/element/.npmignore new file mode 100644 index 0000000000..af137ec718 --- /dev/null +++ b/packages/element/.npmignore @@ -0,0 +1,83 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# lock +package-lock.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +.DS_Store + +# npmignore - content above this line is automatically generated and modifications may be omitted +# see npmjs.com/npmignore for more details. +test + +*.sw* +*.un~ +.idea +bin +demos +docs +temp +webpack-dev.config.js +webpack.config.js +public +.cache +site +examples +gatsby-browser.js +gatsby-config.js diff --git a/packages/element/.prettierignore b/packages/element/.prettierignore new file mode 100644 index 0000000000..2126812219 --- /dev/null +++ b/packages/element/.prettierignore @@ -0,0 +1,8 @@ +package.json +package-lock.json +dist +es +lib +.* +*.png +**/assets/** diff --git a/packages/element/.prettierrc.js b/packages/element/.prettierrc.js new file mode 100644 index 0000000000..7b597d7891 --- /dev/null +++ b/packages/element/.prettierrc.js @@ -0,0 +1,5 @@ +const fabric = require('@umijs/fabric'); + +module.exports = { + ...fabric.prettier, +}; diff --git a/packages/element/jest.config.js b/packages/element/jest.config.js new file mode 100644 index 0000000000..ac9f7eb785 --- /dev/null +++ b/packages/element/jest.config.js @@ -0,0 +1,19 @@ +module.exports = { + runner: 'jest-electron/runner', + testEnvironment: 'jest-electron/environment', + preset: 'ts-jest', + collectCoverage: false, + collectCoverageFrom: ['src/**/*.{ts,js}', '!**/node_modules/**', '!**/vendor/**'], + testRegex: '/tests/.*-spec\\.ts?$', + moduleDirectories: ['node_modules', 'src'], + moduleFileExtensions: ['js', 'ts', 'json'], + moduleNameMapper: { + '@g6/types': '/types', + '@g6/(.*)': '/src/$1', + }, + globals: { + 'ts-jest': { + diagnostics: false, + }, + }, +}; diff --git a/packages/element/package.json b/packages/element/package.json new file mode 100644 index 0000000000..22ca558f4f --- /dev/null +++ b/packages/element/package.json @@ -0,0 +1,89 @@ +{ + "name": "@antv/g6-element", + "version": "0.8.0", + "description": "A Graph Visualization Framework in JavaScript", + "keywords": [ + "antv", + "g6", + "graph", + "graph analysis", + "graph editor", + "graph visualization", + "relational data" + ], + "homepage": "https://g6.antv.antgroup.com", + "bugs": { + "url": "https://github.com/antvis/g6/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/antvis/g6" + }, + "license": "MIT", + "author": "https://github.com/orgs/antvis/people", + "files": [ + "package.json", + "es", + "lib", + "dist", + "LICENSE", + "README.md" + ], + "main": "lib/index.js", + "module": "es/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "npm run clean && father build", + "ci": "npm run build && npm run coverage", + "clean": "rimraf es esm lib dist", + "coverage": "jest --coverage", + "lint": "eslint --ext .js,.jsx,.ts,.tsx --format=pretty \"./\"", + "lint:src": "eslint --ext .ts --format=pretty \"./src\"", + "prettier": "prettier -c --write \"**/*\"", + "test": "jest", + "test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/graph/graph-watermarker-spec.ts", + "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx", + "watch": "father build -w" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx}": [ + "npm run lint-staged:js", + "prettier --write" + ], + "**/*.{less,md,json}": [ + "prettier --write" + ] + }, + "dependencies": { + "@antv/g6-core": "*" + }, + "devDependencies": { + "@babel/core": "^7.7.7", + "@types/jest": "^25.2.1", + "@types/node": "13.11.1", + "@typescript-eslint/eslint-plugin": "^4.11.1", + "@umijs/fabric": "^2.0.0", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.6", + "eslint": "^7.7.0", + "event-simulate": "~1.0.0", + "father": "^2.29.1", + "husky": "^4.2.5", + "jest": "^26.0.1", + "jest-electron": "^0.1.7", + "jest-extended": "^0.11.2", + "lint-staged": "^10.2.11", + "pre-commit": "^1.2.2", + "prettier": "^2.0.5", + "rimraf": "^3.0.0", + "ts-jest": "^24.1.0", + "ts-loader": "^7.0.3", + "typescript": "^4.6.3", + "@antv/g6": "4.5.1" + } +} \ No newline at end of file diff --git a/packages/element/src/edges/index.ts b/packages/element/src/edges/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/element/src/index.ts b/packages/element/src/index.ts new file mode 100644 index 0000000000..c1fb22d3bc --- /dev/null +++ b/packages/element/src/index.ts @@ -0,0 +1,2 @@ +import './nodes'; +import './edges'; diff --git a/packages/element/src/nodes/index.ts b/packages/element/src/nodes/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/element/tsconfig.json b/packages/element/tsconfig.json new file mode 100644 index 0000000000..d0a7d1bc71 --- /dev/null +++ b/packages/element/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "rootDir": "./", + "module": "esnext", + "declaration": true, + "sourceMap": true, + "target": "es5", + "outDir": "lib", + "jsx": "react", + "importHelpers": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "types": ["jest"], + "skipLibCheck": true + }, + "include": ["src", "tests"], + "typedocOptions": { + "mode": "modules", + "out": "docs/api-ts", + "excludePrivate": true, + "excludeProtected": true, + "excludeExternals": true, + "plugin": "typedoc-plugin-markdown", + "theme": "docusaurus2" + } +} diff --git a/packages/g6/.babelrc.js b/packages/g6/.babelrc.js new file mode 100644 index 0000000000..2b4c862884 --- /dev/null +++ b/packages/g6/.babelrc.js @@ -0,0 +1,26 @@ +module.exports = api => { + api.cache(() => process.env.NODE_ENV); + + if (process.env.GATSBY === 'true') { + return { + presets: ['@babel/preset-env', 'babel-preset-gatsby'], + }; + } + return { + presets: [ + [ + '@babel/preset-env', + { + loose: true, + modules: false, + }, + ], + '@babel/preset-react', + { + "plugins": [ + "@babel/plugin-proposal-class-properties" + ] + } + ] + }; +}; diff --git a/packages/g6/.eslintignore b/packages/g6/.eslintignore new file mode 100644 index 0000000000..f02222b2fe --- /dev/null +++ b/packages/g6/.eslintignore @@ -0,0 +1,12 @@ +lib/ +dist/ +node_modules/ +bin +esm/ +es/ +tests/ +webpack.*.js +gatsby-*.js +global.d.ts +jest.config.js +.eslintrc.* diff --git a/packages/g6/.fatherrc.js b/packages/g6/.fatherrc.js new file mode 100644 index 0000000000..5185d1910b --- /dev/null +++ b/packages/g6/.fatherrc.js @@ -0,0 +1,5 @@ +export default { + entry: './src/index.ts', + esm: 'babel', + cjs: 'babel', +}; diff --git a/packages/g6/.npmignore b/packages/g6/.npmignore new file mode 100644 index 0000000000..af137ec718 --- /dev/null +++ b/packages/g6/.npmignore @@ -0,0 +1,83 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# lock +package-lock.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +.DS_Store + +# npmignore - content above this line is automatically generated and modifications may be omitted +# see npmjs.com/npmignore for more details. +test + +*.sw* +*.un~ +.idea +bin +demos +docs +temp +webpack-dev.config.js +webpack.config.js +public +.cache +site +examples +gatsby-browser.js +gatsby-config.js diff --git a/packages/g6/package.json b/packages/g6/package.json new file mode 100644 index 0000000000..c6e9f75373 --- /dev/null +++ b/packages/g6/package.json @@ -0,0 +1,90 @@ +{ + "name": "@antv/g6", + "version": "5.0.0", + "description": "A Graph Visualization Framework in JavaScript", + "keywords": [ + "antv", + "g6", + "graph", + "graph analysis", + "graph editor", + "graph visualization", + "relational data" + ], + "homepage": "https://g6.antv.antgroup.com", + "bugs": { + "url": "https://github.com/antvis/g6/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/antvis/g6" + }, + "license": "MIT", + "author": "https://github.com/orgs/antvis/people", + "files": [ + "package.json", + "es", + "lib", + "dist", + "LICENSE", + "README.md" + ], + "main": "lib/index.js", + "module": "es/index.js", + "unpkg": "dist/g6.min.js", + "types": "lib/index.d.ts", + "scripts": { + "start": "father build --watch", + "build": "npm run clean && father build && npm run build:umd", + "build:umd": "webpack --config webpack.config.js --mode production", + "ci": "npm run build && npm run coverage", + "clean": "rimraf es esm lib dist", + "coverage": "jest --coverage", + "doc": "rimraf apis && typedoc", + "lint": "eslint --ext .js,.jsx,.ts,.tsx --format=pretty \"./\"", + "lint:src": "eslint --ext .ts --format=pretty \"./src\"", + "prettier": "prettier -c --write \"**/*\"", + "test": "jest --passWithNoTests", + "test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/graph/graph-watermarker-spec.ts", + "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx", + "watch": "father build -w", + "cdn": "antv-bin upload -n @antv/g6" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx}": [ + "npm run lint-staged:js", + "prettier --write" + ], + "**/*.{less,md,json}": [ + "prettier --write" + ] + }, + "dependencies": { + "@antv/g6-pc": "*" + }, + "devDependencies": { + "@babel/core": "^7.7.7", + "@babel/plugin-proposal-class-properties": "^7.1.0", + "@types/jest": "^25.2.1", + "@types/node": "13.11.1", + "@typescript-eslint/eslint-plugin": "^4.11.1", + "@umijs/fabric": "^2.0.0", + "babel-loader": "^8.0.6", + "eslint": "^7.7.0", + "father": "^2.29.1", + "husky": "^4.2.5", + "lint-staged": "^10.2.11", + "pre-commit": "^1.2.2", + "prettier": "^2.0.5", + "rimraf": "^3.0.0", + "ts-loader": "^7.0.3", + "typescript": "^4.6.3", + "webpack": "^4.41.4", + "webpack-cli": "^3.3.10" + } +} \ No newline at end of file diff --git a/packages/g6/src/index.ts b/packages/g6/src/index.ts new file mode 100644 index 0000000000..0e34042c02 --- /dev/null +++ b/packages/g6/src/index.ts @@ -0,0 +1,7 @@ +import G6 from '@antv/g6-pc'; + +G6.version = '5.0.0'; + +export * from '@antv/g6-pc'; +export default G6; +export const version = '5.0.0'; \ No newline at end of file diff --git a/packages/g6/tsconfig.json b/packages/g6/tsconfig.json new file mode 100644 index 0000000000..679ea2db4f --- /dev/null +++ b/packages/g6/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "rootDir": "./", + "module": "esnext", + "declaration": true, + "sourceMap": true, + "target": "es5", + "outDir": "lib", + "jsx": "react", + "importHelpers": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/packages/g6/webpack.config.js b/packages/g6/webpack.config.js new file mode 100644 index 0000000000..88766beafe --- /dev/null +++ b/packages/g6/webpack.config.js @@ -0,0 +1,47 @@ +const webpack = require('webpack'); +// eslint-disable-next-line prefer-destructuring +const resolve = require('path').resolve; + +module.exports = { + entry: { + g6: './src/index.ts', + }, + output: { + filename: '[name].min.js', + library: 'G6', + libraryTarget: 'umd', + libraryExport: 'default', + path: resolve(process.cwd(), 'dist/'), + globalObject: 'this', + }, + resolve: { + // Add `.ts` as a resolvable extension. + extensions: ['.ts', '.js'], + }, + module: { + rules: [ + { + test: /\.js$/, + include: /node_modules/, + use: { + loader: 'babel-loader', + options: { + // babelrc: true, + presets: ['@babel/preset-env'], + }, + }, + }, + { + test: /\.ts$/, + use: { + loader: 'ts-loader', + options: { + transpileOnly: true, + }, + }, + }, + ], + }, + plugins: [new webpack.NoEmitOnErrorsPlugin(), new webpack.optimize.AggressiveMergingPlugin()], + devtool: 'source-map', +}; diff --git a/packages/g6/webpack.dev.config.js b/packages/g6/webpack.dev.config.js new file mode 100644 index 0000000000..67d0566f55 --- /dev/null +++ b/packages/g6/webpack.dev.config.js @@ -0,0 +1,14 @@ +const webpackConfig = require('./webpack.config'); + +module.exports = Object.assign( + { + devtool: 'cheap-source-map', + watch: true, + watchOptions: { + aggregateTimeout: 300, + poll: 1000, + ignored: /node_modules/, + }, + }, + webpackConfig, +); diff --git a/packages/pc/.babelrc.js b/packages/pc/.babelrc.js new file mode 100644 index 0000000000..2b4c862884 --- /dev/null +++ b/packages/pc/.babelrc.js @@ -0,0 +1,26 @@ +module.exports = api => { + api.cache(() => process.env.NODE_ENV); + + if (process.env.GATSBY === 'true') { + return { + presets: ['@babel/preset-env', 'babel-preset-gatsby'], + }; + } + return { + presets: [ + [ + '@babel/preset-env', + { + loose: true, + modules: false, + }, + ], + '@babel/preset-react', + { + "plugins": [ + "@babel/plugin-proposal-class-properties" + ] + } + ] + }; +}; diff --git a/packages/pc/.eslintignore b/packages/pc/.eslintignore new file mode 100644 index 0000000000..f02222b2fe --- /dev/null +++ b/packages/pc/.eslintignore @@ -0,0 +1,12 @@ +lib/ +dist/ +node_modules/ +bin +esm/ +es/ +tests/ +webpack.*.js +gatsby-*.js +global.d.ts +jest.config.js +.eslintrc.* diff --git a/packages/pc/.fatherrc.js b/packages/pc/.fatherrc.js new file mode 100644 index 0000000000..5185d1910b --- /dev/null +++ b/packages/pc/.fatherrc.js @@ -0,0 +1,5 @@ +export default { + entry: './src/index.ts', + esm: 'babel', + cjs: 'babel', +}; diff --git a/packages/pc/.npmignore b/packages/pc/.npmignore new file mode 100644 index 0000000000..af137ec718 --- /dev/null +++ b/packages/pc/.npmignore @@ -0,0 +1,83 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# lock +package-lock.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +.DS_Store + +# npmignore - content above this line is automatically generated and modifications may be omitted +# see npmjs.com/npmignore for more details. +test + +*.sw* +*.un~ +.idea +bin +demos +docs +temp +webpack-dev.config.js +webpack.config.js +public +.cache +site +examples +gatsby-browser.js +gatsby-config.js diff --git a/packages/pc/global.d.ts b/packages/pc/global.d.ts new file mode 100644 index 0000000000..34078e6d7b --- /dev/null +++ b/packages/pc/global.d.ts @@ -0,0 +1,4 @@ +declare module '*.json' { + export const version: string; + export const value: any; +} diff --git a/packages/pc/jest.config.js b/packages/pc/jest.config.js new file mode 100644 index 0000000000..ac9f7eb785 --- /dev/null +++ b/packages/pc/jest.config.js @@ -0,0 +1,19 @@ +module.exports = { + runner: 'jest-electron/runner', + testEnvironment: 'jest-electron/environment', + preset: 'ts-jest', + collectCoverage: false, + collectCoverageFrom: ['src/**/*.{ts,js}', '!**/node_modules/**', '!**/vendor/**'], + testRegex: '/tests/.*-spec\\.ts?$', + moduleDirectories: ['node_modules', 'src'], + moduleFileExtensions: ['js', 'ts', 'json'], + moduleNameMapper: { + '@g6/types': '/types', + '@g6/(.*)': '/src/$1', + }, + globals: { + 'ts-jest': { + diagnostics: false, + }, + }, +}; diff --git a/packages/pc/package.json b/packages/pc/package.json new file mode 100644 index 0000000000..4c5167fa74 --- /dev/null +++ b/packages/pc/package.json @@ -0,0 +1,103 @@ +{ + "name": "@antv/g6-pc", + "version": "0.8.0", + "description": "A Graph Visualization Framework in JavaScript", + "keywords": [ + "antv", + "g6", + "graph", + "graph analysis", + "graph editor", + "graph visualization", + "relational data" + ], + "homepage": "https://g6.antv.antgroup.com", + "bugs": { + "url": "https://github.com/antvis/g6/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/antvis/g6" + }, + "license": "MIT", + "author": "https://github.com/orgs/antvis/people", + "files": [ + "package.json", + "es", + "lib", + "dist", + "LICENSE", + "README.md" + ], + "main": "lib/index.js", + "module": "es/index.js", + "unpkg": "dist/g6.min.js", + "types": "lib/index.d.ts", + "scripts": { + "start": "father build --watch", + "build": "npm run clean && father build && npm run build:umd", + "build:umd": "webpack --config webpack.config.js --mode production", + "build:umd:profile": "webpack --config webpack.config.js --mode production --profile --json > stats.json", + "ci": "npm run build && npm run coverage", + "clean": "rimraf es esm lib dist", + "coverage": "jest --coverage", + "doc": "rimraf apis && typedoc", + "lint": "eslint --ext .js,.jsx,.ts,.tsx --format=pretty \"./\"", + "lint:src": "eslint --ext .ts --format=pretty \"./src\"", + "prettier": "prettier -c --write \"**/*\"", + "test": "jest", + "test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/layout/pipes-spec.ts", + "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx", + "watch": "father build -w", + "cdn": "antv-bin upload -n @antv/g6" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx}": [ + "npm run lint-staged:js", + "prettier --write" + ], + "**/*.{less,md,json}": [ + "prettier --write" + ] + }, + "dependencies": { + "@antv/g6-core": "*", + "@antv/g6-element": "*", + "@antv/g6-plugin": "*", + "@antv/hierarchy": "^0.6.7", + "@antv/algorithm": "^0.1.8", + "@antv/layout": "^0.3.0" + }, + "devDependencies": { + "@babel/core": "^7.7.7", + "@babel/plugin-proposal-class-properties": "^7.1.0", + "@babel/preset-react": "^7.7.4", + "@types/jest": "^25.2.1", + "@types/node": "13.11.1", + "@typescript-eslint/eslint-plugin": "^4.11.1", + "@umijs/fabric": "^2.0.0", + "babel-loader": "^8.0.6", + "eslint": "^7.7.0", + "father": "^2.29.1", + "husky": "^4.2.5", + "jest": "^26.0.1", + "jest-electron": "^0.1.7", + "jest-extended": "^0.11.2", + "lint-staged": "^10.2.11", + "pre-commit": "^1.2.2", + "prettier": "^2.0.5", + "rimraf": "^3.0.0", + "ts-jest": "^24.1.0", + "ts-loader": "^7.0.3", + "typescript": "^4.6.3", + "webpack": "^4.41.4", + "webpack-cli": "^3.3.10", + "worker-loader": "^3.0.0", + "stats-js": "1.0.1" + } +} \ No newline at end of file diff --git a/packages/pc/src/index.ts b/packages/pc/src/index.ts new file mode 100644 index 0000000000..64f3d5244a --- /dev/null +++ b/packages/pc/src/index.ts @@ -0,0 +1,3 @@ +export {}; + +export default {}; diff --git a/packages/pc/tests/unit/template-spec.ts b/packages/pc/tests/unit/template-spec.ts new file mode 100644 index 0000000000..f55424c538 --- /dev/null +++ b/packages/pc/tests/unit/template-spec.ts @@ -0,0 +1,10 @@ +describe('template', () => { + const el = document.createElement('div'); + el.id = 'test-div-id'; + el.innerHTML = 'hello g6'; + document.querySelector('body').appendChild(el); + + it('div content', () => { + expect(document.querySelector('#test-div-id').innerHTML).toBe('hello g6'); + }); +}); diff --git a/packages/pc/tsconfig.json b/packages/pc/tsconfig.json new file mode 100644 index 0000000000..faceb80e18 --- /dev/null +++ b/packages/pc/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "rootDir": "./", + "module": "esnext", + "declaration": true, + "sourceMap": true, + "target": "es5", + "outDir": "lib", + "jsx": "react", + "importHelpers": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": [ + "esnext", + "dom" + ], + "types": [ + "jest" + ], + "skipLibCheck": true + }, + "include": [ + "src", + "tests" + ], + "typedocOptions": { + "mode": "modules", + "out": "docs/api-ts", + "excludePrivate": true, + "excludeProtected": true, + "excludeExternals": true, + "plugin": "typedoc-plugin-markdown", + "theme": "docusaurus2" + } +} \ No newline at end of file diff --git a/packages/pc/webpack.config.js b/packages/pc/webpack.config.js new file mode 100644 index 0000000000..88766beafe --- /dev/null +++ b/packages/pc/webpack.config.js @@ -0,0 +1,47 @@ +const webpack = require('webpack'); +// eslint-disable-next-line prefer-destructuring +const resolve = require('path').resolve; + +module.exports = { + entry: { + g6: './src/index.ts', + }, + output: { + filename: '[name].min.js', + library: 'G6', + libraryTarget: 'umd', + libraryExport: 'default', + path: resolve(process.cwd(), 'dist/'), + globalObject: 'this', + }, + resolve: { + // Add `.ts` as a resolvable extension. + extensions: ['.ts', '.js'], + }, + module: { + rules: [ + { + test: /\.js$/, + include: /node_modules/, + use: { + loader: 'babel-loader', + options: { + // babelrc: true, + presets: ['@babel/preset-env'], + }, + }, + }, + { + test: /\.ts$/, + use: { + loader: 'ts-loader', + options: { + transpileOnly: true, + }, + }, + }, + ], + }, + plugins: [new webpack.NoEmitOnErrorsPlugin(), new webpack.optimize.AggressiveMergingPlugin()], + devtool: 'source-map', +}; diff --git a/packages/pc/webpack.dev.config.js b/packages/pc/webpack.dev.config.js new file mode 100644 index 0000000000..7ec3fff665 --- /dev/null +++ b/packages/pc/webpack.dev.config.js @@ -0,0 +1,14 @@ +const webpackConfig = require('./webpack.config'); + +module.exports = Object.assign( + { + devtool: 'cheap-source-map', + watch: true, + watchOptions: { + aggregateTimeout: 300, + poll: 1000, + ignored: /node_modules/ + }, + }, + webpackConfig, +); diff --git a/packages/plugin/.eslintignore b/packages/plugin/.eslintignore new file mode 100644 index 0000000000..c00348bc91 --- /dev/null +++ b/packages/plugin/.eslintignore @@ -0,0 +1,15 @@ +lib/ +dist/ +mocks/ +node_modules/ +demos/ +.cache +public +bin +esm/ +es/ +examples/ +tests/ +webpack.*.js +jest.config.js +.eslintrc.* diff --git a/packages/plugin/.fatherrc.js b/packages/plugin/.fatherrc.js new file mode 100644 index 0000000000..9ca3a8c198 --- /dev/null +++ b/packages/plugin/.fatherrc.js @@ -0,0 +1,6 @@ +export default { + entry: './src/index.ts', + esm: 'babel', + cjs: 'babel', + }; + \ No newline at end of file diff --git a/packages/plugin/.npmignore b/packages/plugin/.npmignore new file mode 100644 index 0000000000..4795814757 --- /dev/null +++ b/packages/plugin/.npmignore @@ -0,0 +1,49 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log +.DS_Store + +# Coverage directory used by tools like istanbul +coverage + +# Dependency directories +node_modules +**/node_modules +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Output of 'npm pack' +*.tgz + + +# dotenv environment variables file +.env + +# IDE +.idea + +# lock +package-lock.json + + +# yarn-lock +yarn.lock + + +# ignore dist files +.vscode/ +src +jest.config.js +.fatherrc.js +__mock__ +public +tsconfig.json \ No newline at end of file diff --git a/packages/plugin/jest.config.js b/packages/plugin/jest.config.js new file mode 100644 index 0000000000..542312daf3 --- /dev/null +++ b/packages/plugin/jest.config.js @@ -0,0 +1,25 @@ +module.exports = { + runner: 'jest-electron/runner', + testEnvironment: 'jest-electron/environment', + preset: 'ts-jest', + collectCoverage: false, + collectCoverageFrom: ['src/**/*.{ts,js}', '!**/node_modules/**', '!**/vendor/**'], + testRegex: '/tests/unit/.*-spec\\.ts?$', + moduleDirectories: ['node_modules', 'src'], + moduleFileExtensions: ['js', 'ts', 'json'], + moduleNameMapper: { + '@g6/types': '/types', + '@g6/(.*)': '/src/$1', + }, + transformIgnorePatterns: ['./node_modules/'], + + globals: { + 'ts-jest': { + diagnostics: false, + }, + }, + testPathIgnorePatterns: [ + '/tests/unit/image-minimap-spec.ts', + '/tests/unit/edge-filter-lens-spec.ts', + ], +}; diff --git a/packages/plugin/package.json b/packages/plugin/package.json new file mode 100644 index 0000000000..b42eb0e28a --- /dev/null +++ b/packages/plugin/package.json @@ -0,0 +1,53 @@ +{ + "name": "@antv/g6-plugin", + "version": "0.8.0", + "description": "G6 Plugin", + "main": "lib/index.js", + "module": "es/index.js", + "scripts": { + "build": "npm run clean && father build", + "ci": "npm run build && npm run coverage", + "clean": "rimraf es esm lib dist", + "coverage": "jest --coverage", + "lint": "eslint --ext .js,.jsx,.ts,.tsx --format=pretty \"./\"", + "lint:src": "eslint --ext .ts --format=pretty \"./src\"", + "prettier": "prettier -c --write \"**/*\"", + "test": "jest", + "test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/snapline-spec.ts", + "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx", + "watch": "father build -w" + }, + "dependencies": { + "@antv/g6-core": "*", + "@antv/g6-element": "*" + }, + "sideEffects": false, + "author": "", + "license": "MIT", + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx}": [ + "npm run lint-staged:js", + "prettier --write" + ], + "**/*.{less,md,json}": [ + "prettier --write" + ] + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@types/jest": "^26.0.18", + "father": "^2.30.0", + "jest": "^26.6.3", + "jest-electron": "^0.1.11", + "rimraf": "^3.0.2", + "ts-jest": "^26.4.4", + "@antv/g6": "^4.7.10" + } +} \ No newline at end of file diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts new file mode 100644 index 0000000000..87f7f6ffe7 --- /dev/null +++ b/packages/plugin/src/index.ts @@ -0,0 +1,5 @@ +export {}; + +const Plugin = {}; + +export default Plugin; diff --git a/packages/plugin/tsconfig.json b/packages/plugin/tsconfig.json new file mode 100644 index 0000000000..fdce806695 --- /dev/null +++ b/packages/plugin/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "allowJs": true, + "declaration": true, + "target": "es5", + "moduleResolution": "node", + "jsx": "react", + "resolveJsonModule": true, + "noImplicitAny": false, + "lib": ["dom", "esnext"], + "module": "esnext", + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src", "tests"], + "exclude": ["./node_modules"] +} diff --git a/packages/react-node/.editorconfig b/packages/react-node/.editorconfig new file mode 100755 index 0000000000..7e3649acc2 --- /dev/null +++ b/packages/react-node/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/packages/react-node/.fatherrc.ts b/packages/react-node/.fatherrc.ts new file mode 100644 index 0000000000..cc3a27be0b --- /dev/null +++ b/packages/react-node/.fatherrc.ts @@ -0,0 +1,4 @@ +export default { + esm: 'rollup', + cjs: 'rollup', +}; diff --git a/packages/react-node/.gitignore b/packages/react-node/.gitignore new file mode 100644 index 0000000000..131f5f2345 --- /dev/null +++ b/packages/react-node/.gitignore @@ -0,0 +1,21 @@ +# 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 +/dist +/docs-dist + +# misc +.DS_Store + +# umi +.umi +.umi-production +.umi-test +.env.local diff --git a/packages/react-node/.npmignore b/packages/react-node/.npmignore new file mode 100644 index 0000000000..384d1eecb8 --- /dev/null +++ b/packages/react-node/.npmignore @@ -0,0 +1,3 @@ +docs +src/.umi +docs-dist \ No newline at end of file diff --git a/packages/react-node/.prettierignore b/packages/react-node/.prettierignore new file mode 100644 index 0000000000..ecb24d3313 --- /dev/null +++ b/packages/react-node/.prettierignore @@ -0,0 +1,7 @@ +**/*.svg +**/*.ejs +**/*.html +package.json +.umi +.umi-production +.umi-test diff --git a/packages/react-node/.prettierrc b/packages/react-node/.prettierrc new file mode 100644 index 0000000000..94beb14840 --- /dev/null +++ b/packages/react-node/.prettierrc @@ -0,0 +1,11 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "printWidth": 80, + "overrides": [ + { + "files": ".prettierrc", + "options": { "parser": "json" } + } + ] +} diff --git a/packages/react-node/.umirc.ts b/packages/react-node/.umirc.ts new file mode 100644 index 0000000000..e7387d763d --- /dev/null +++ b/packages/react-node/.umirc.ts @@ -0,0 +1,18 @@ +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 +}); diff --git a/packages/react-node/README.md b/packages/react-node/README.md new file mode 100644 index 0000000000..93965ebb79 --- /dev/null +++ b/packages/react-node/README.md @@ -0,0 +1,90 @@ +# G6 React Node + +> Using React Component to custom your g6 node + +## Docs + +[https://dicegraph.github.io/](https://dicegraph.github.io/g6-react-node) + +## Example + +```jsx +import { + Group, + Rect, + Text, + Circle, + Image, + createNodeFromReact, +} from '@antv/g6-react-node'; +const ReactNode = ({ cfg = {} }) => { + const { description, meta = {}, label = 'label' } = cfg; + return ( + + + + + {label} + + + + + Desc: {description} + + + Creator: {meta.creatorName} + + + + + + + + ); +}; +G6.registerNode('yourNode', createNodeFromReact(ReactNode)); +``` diff --git a/packages/react-node/docs/index.en-US.md b/packages/react-node/docs/index.en-US.md new file mode 100644 index 0000000000..abbb3454cf --- /dev/null +++ b/packages/react-node/docs/index.en-US.md @@ -0,0 +1,174 @@ +# 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 ( + + + + + {label} + + + + + Desc: {description} + + + Creator: {meta.creatorName} + + + + + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + +```jsx | pure +import { + Group, + Rect, + Text, + Circle, + Image, + createNodeFromReact, +} from '@antv/g6-react-node'; + +const ReactNode = ({ cfg = {} }) => { + const { description, meta = {}, label = 'label' } = cfg; + return ( + + + + + {label} + + + + + 描述: {description} + + + 创建者: {meta.creatorName} + + + + + + + + ); +}; + +G6.registerNode('yourNode', createNodeFromReact(ReactNode)); +``` diff --git a/packages/react-node/docs/index.md b/packages/react-node/docs/index.md new file mode 100644 index 0000000000..0f1c52e9e4 --- /dev/null +++ b/packages/react-node/docs/index.md @@ -0,0 +1,170 @@ +# 用 React 定义节点 + +直接用 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 ( + + + + + {label} + + + + + 描述: {description} + + + 创建者: {meta.creatorName} + + + + + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + +```jsx | pure +import { + Group, + Rect, + Text, + Circle, + Image, + createNodeFromReact, +} from '@antv/g6-react-node'; + +const ReactNode = ({ cfg = {} }) => { + const { description, meta = {}, label = 'label' } = cfg; + return ( + + + + + {label} + + + + + 描述: {description} + + + 创建者: {meta.creatorName} + + + + + + + + ); +}; + +G6.registerNode('yourNode', createNodeFromReact(ReactNode)); +``` diff --git a/packages/react-node/package.json b/packages/react-node/package.json new file mode 100644 index 0000000000..8ca5fed8d5 --- /dev/null +++ b/packages/react-node/package.json @@ -0,0 +1,47 @@ +{ + "name": "@antv/g6-react-node", + "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" + }, + "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" + ] + }, + "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" + }, + "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": "^1.19.1", + "yorkie": "^2.0.0" + } +} \ No newline at end of file diff --git a/packages/react-node/src/API/CircleStyle.tsx b/packages/react-node/src/API/CircleStyle.tsx new file mode 100644 index 0000000000..b5b749adf1 --- /dev/null +++ b/packages/react-node/src/API/CircleStyle.tsx @@ -0,0 +1,5 @@ +import { CircleStyle } from '../ReactNode/Shape/Circle'; +import React from 'react'; + +const Circle: React.FC = (props) =>
{props}
; +export default Circle; diff --git a/packages/react-node/src/API/EllipseStyle.tsx b/packages/react-node/src/API/EllipseStyle.tsx new file mode 100644 index 0000000000..cb8e4fd767 --- /dev/null +++ b/packages/react-node/src/API/EllipseStyle.tsx @@ -0,0 +1,5 @@ +import { EllipseStyle } from '../ReactNode/Shape/Ellipse'; +import React from 'react'; + +const Ellipse: React.FC = (props) =>
{props}
; +export default Ellipse; diff --git a/packages/react-node/src/API/Event.tsx b/packages/react-node/src/API/Event.tsx new file mode 100644 index 0000000000..9570fdd5ad --- /dev/null +++ b/packages/react-node/src/API/Event.tsx @@ -0,0 +1,5 @@ +import React from 'react'; +import { EventAttrs } from '../Register/event'; + +const Event: React.FC = (props) =>
{props}
; +export default Event; diff --git a/packages/react-node/src/API/ImageStyle.tsx b/packages/react-node/src/API/ImageStyle.tsx new file mode 100644 index 0000000000..c24b310398 --- /dev/null +++ b/packages/react-node/src/API/ImageStyle.tsx @@ -0,0 +1,5 @@ +import { ImageStyle } from '../ReactNode/Shape/Image'; +import React from 'react'; + +const Image: React.FC = (props) =>
{props}
; +export default Image; diff --git a/packages/react-node/src/API/MarkerStyle.tsx b/packages/react-node/src/API/MarkerStyle.tsx new file mode 100644 index 0000000000..76b48317fd --- /dev/null +++ b/packages/react-node/src/API/MarkerStyle.tsx @@ -0,0 +1,5 @@ +import { MarkerStyle } from '../ReactNode/Shape/Marker'; +import React from 'react'; + +const Marker: React.FC = (props) =>
{props}
; +export default Marker; diff --git a/packages/react-node/src/API/PathStyle.tsx b/packages/react-node/src/API/PathStyle.tsx new file mode 100644 index 0000000000..b77b005271 --- /dev/null +++ b/packages/react-node/src/API/PathStyle.tsx @@ -0,0 +1,5 @@ +import { PathStyle } from '../ReactNode/Shape/Path'; +import React from 'react'; + +const Path: React.FC = (props) =>
{props}
; +export default Path; diff --git a/packages/react-node/src/API/PolygonStyle.tsx b/packages/react-node/src/API/PolygonStyle.tsx new file mode 100644 index 0000000000..c2ad2f75b8 --- /dev/null +++ b/packages/react-node/src/API/PolygonStyle.tsx @@ -0,0 +1,5 @@ +import { PolygonStyle } from '../ReactNode/Shape/Polygon'; +import React from 'react'; + +const Polygon: React.FC = (props) =>
{props}
; +export default Polygon; diff --git a/packages/react-node/src/API/RectStyle.tsx b/packages/react-node/src/API/RectStyle.tsx new file mode 100644 index 0000000000..787306bdb5 --- /dev/null +++ b/packages/react-node/src/API/RectStyle.tsx @@ -0,0 +1,5 @@ +import { RectStyle } from '../ReactNode/Shape/Rect'; +import React from 'react'; + +const Rect: React.FC = (props) =>
; +export default Rect; diff --git a/packages/react-node/src/API/TextStyle.tsx b/packages/react-node/src/API/TextStyle.tsx new file mode 100644 index 0000000000..38fd416db4 --- /dev/null +++ b/packages/react-node/src/API/TextStyle.tsx @@ -0,0 +1,5 @@ +import { TextStyle } from '../ReactNode/Shape/Text'; +import React from 'react'; + +const Text: React.FC = (props) =>
{props}
; +export default Text; diff --git a/packages/react-node/src/API/circle.en-US.md b/packages/react-node/src/API/circle.en-US.md new file mode 100644 index 0000000000..2230c5958b --- /dev/null +++ b/packages/react-node/src/API/circle.en-US.md @@ -0,0 +1,28 @@ +# 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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/circle.md b/packages/react-node/src/API/circle.md new file mode 100644 index 0000000000..6fa56970ae --- /dev/null +++ b/packages/react-node/src/API/circle.md @@ -0,0 +1,29 @@ +# 圆 (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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/ellipse.en-US.md b/packages/react-node/src/API/ellipse.en-US.md new file mode 100644 index 0000000000..1f605c7a56 --- /dev/null +++ b/packages/react-node/src/API/ellipse.en-US.md @@ -0,0 +1,29 @@ +# 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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/ellipse.md b/packages/react-node/src/API/ellipse.md new file mode 100644 index 0000000000..9424a30fbb --- /dev/null +++ b/packages/react-node/src/API/ellipse.md @@ -0,0 +1,29 @@ +# 椭圆 (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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/event.en-US.md b/packages/react-node/src/API/event.en-US.md new file mode 100644 index 0000000000..372848f8ff --- /dev/null +++ b/packages/react-node/src/API/event.en-US.md @@ -0,0 +1,14 @@ +# 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; +``` + + diff --git a/packages/react-node/src/API/event.md b/packages/react-node/src/API/event.md new file mode 100644 index 0000000000..7524eaf91b --- /dev/null +++ b/packages/react-node/src/API/event.md @@ -0,0 +1,14 @@ +# 事件(Event)属性 + +每一个形状都会有单独的事件响应,他们之间不存在冒泡触发逻辑; + +```typescript +type ShapeEventListner = ( + event: IG6GraphEvent, + node: INode | null, + shape: IShape, + graph: Graph, +) => void; +``` + + diff --git a/packages/react-node/src/API/image.en-US.md b/packages/react-node/src/API/image.en-US.md new file mode 100644 index 0000000000..731ced2a09 --- /dev/null +++ b/packages/react-node/src/API/image.en-US.md @@ -0,0 +1,28 @@ +# 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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/image.md b/packages/react-node/src/API/image.md new file mode 100644 index 0000000000..77facc578f --- /dev/null +++ b/packages/react-node/src/API/image.md @@ -0,0 +1,28 @@ +# 图片 (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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/index.en-US.md b/packages/react-node/src/API/index.en-US.md new file mode 100644 index 0000000000..11805f3948 --- /dev/null +++ b/packages/react-node/src/API/index.en-US.md @@ -0,0 +1,5 @@ +# Group & Common + +Every component should be wrapped in a Group component, it's a way to group shape. + + diff --git a/packages/react-node/src/API/index.md b/packages/react-node/src/API/index.md new file mode 100644 index 0000000000..35dba687c8 --- /dev/null +++ b/packages/react-node/src/API/index.md @@ -0,0 +1,5 @@ +# 图形共有属性 与 Group(组) + +我们建议每一个单元的图形,都应该有序的存放在一个 Group 里,一个节点组件,应该被 Group 包裹 + + diff --git a/packages/react-node/src/API/marker.en-US.md b/packages/react-node/src/API/marker.en-US.md new file mode 100644 index 0000000000..cbd0092817 --- /dev/null +++ b/packages/react-node/src/API/marker.en-US.md @@ -0,0 +1,31 @@ +# 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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/marker.md b/packages/react-node/src/API/marker.md new file mode 100644 index 0000000000..a6062d16e8 --- /dev/null +++ b/packages/react-node/src/API/marker.md @@ -0,0 +1,31 @@ +# 标记 (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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/path.en-US.md b/packages/react-node/src/API/path.en-US.md new file mode 100644 index 0000000000..6187cc6264 --- /dev/null +++ b/packages/react-node/src/API/path.en-US.md @@ -0,0 +1,34 @@ +# 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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/path.md b/packages/react-node/src/API/path.md new file mode 100644 index 0000000000..20422c3f11 --- /dev/null +++ b/packages/react-node/src/API/path.md @@ -0,0 +1,34 @@ +# 路径 (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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/polygon.en-US.md b/packages/react-node/src/API/polygon.en-US.md new file mode 100644 index 0000000000..9c37450e2b --- /dev/null +++ b/packages/react-node/src/API/polygon.en-US.md @@ -0,0 +1,34 @@ +# 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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/polygon.md b/packages/react-node/src/API/polygon.md new file mode 100644 index 0000000000..01705c4142 --- /dev/null +++ b/packages/react-node/src/API/polygon.md @@ -0,0 +1,34 @@ +# 多边形 (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 = {} }) => ( + + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/rect.en-US.md b/packages/react-node/src/API/rect.en-US.md new file mode 100644 index 0000000000..3032447dd5 --- /dev/null +++ b/packages/react-node/src/API/rect.en-US.md @@ -0,0 +1,45 @@ +# 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 ( + + + + + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/rect.md b/packages/react-node/src/API/rect.md new file mode 100644 index 0000000000..2b9a85ebe5 --- /dev/null +++ b/packages/react-node/src/API/rect.md @@ -0,0 +1,45 @@ +# 矩形 (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 ( + + + + + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/text.en-US.md b/packages/react-node/src/API/text.en-US.md new file mode 100644 index 0000000000..b3ade00a40 --- /dev/null +++ b/packages/react-node/src/API/text.en-US.md @@ -0,0 +1,31 @@ +# 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 = {} }) => ( + + + Text + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/API/text.md b/packages/react-node/src/API/text.md new file mode 100644 index 0000000000..c8204ef678 --- /dev/null +++ b/packages/react-node/src/API/text.md @@ -0,0 +1,31 @@ +# 文本 (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 = {} }) => ( + + + Text + + +); + +G6.registerNode('test', createNodeFromReact(ReactNode)); + +export default () => ; +``` + + diff --git a/packages/react-node/src/Animation/animate.ts b/packages/react-node/src/Animation/animate.ts new file mode 100644 index 0000000000..1ab1ceeae5 --- /dev/null +++ b/packages/react-node/src/Animation/animate.ts @@ -0,0 +1,22 @@ +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, + 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(); + } +}; diff --git a/packages/react-node/src/Animation/animateFunc.ts b/packages/react-node/src/Animation/animateFunc.ts new file mode 100644 index 0000000000..5276b31337 --- /dev/null +++ b/packages/react-node/src/Animation/animateFunc.ts @@ -0,0 +1,158 @@ +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, + }; + }, +}; diff --git a/packages/react-node/src/Example/animate.md b/packages/react-node/src/Example/animate.md new file mode 100644 index 0000000000..a974de3e60 --- /dev/null +++ b/packages/react-node/src/Example/animate.md @@ -0,0 +1,92 @@ +# 动画使用案例 + +> 这是一个简单的形状事件绑定案例, 点击右上角按钮展开节点 + +```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 }) => ( + + {text} + +); + +const Card = ({ cfg }) => { + const { animated = false } = cfg; + + return ( + + + { + graph.updateItem(node, { + animated: !animated, + }); + }} + animation={ + animated && { + animate: 'flash', + repeat: true, + duration: 2000, + } + } + > + 点我{animated ? '暂停' : '看'}动画 + + + 我是一段特别特别特别特别特别特别特别长的描述 + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(Card)); + +export default () => ; +``` diff --git a/packages/react-node/src/Example/card.en-US.md b/packages/react-node/src/Example/card.en-US.md new file mode 100644 index 0000000000..3741dfebab --- /dev/null +++ b/packages/react-node/src/Example/card.en-US.md @@ -0,0 +1,95 @@ +# 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 }) => ( + + {text} + +); + +const Card = () => { + return ( + + + + This is a card + + + I'm a very very very very very very very long description. + + + + + + + + + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(Card)); + +export default () => ; +``` diff --git a/packages/react-node/src/Example/card.md b/packages/react-node/src/Example/card.md new file mode 100644 index 0000000000..dce671ee1b --- /dev/null +++ b/packages/react-node/src/Example/card.md @@ -0,0 +1,94 @@ +# 简单卡片案例 + +> 这是一个简单的卡片案例 + +```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 }) => ( + + {text} + +); + +const Card = () => { + return ( + + + + 这是一个卡片 + + + 我是一段特别特别特别特别特别特别特别长的描述 + + + + { + ["我是", "很多个", "很多个的", "标签"].map(e => ) + } + + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(Card)); + +export default () => ; +``` diff --git a/packages/react-node/src/Example/event.en-US.md b/packages/react-node/src/Example/event.en-US.md new file mode 100644 index 0000000000..d55f726622 --- /dev/null +++ b/packages/react-node/src/Example/event.en-US.md @@ -0,0 +1,118 @@ +# 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 }) => ( + + {text} + +); + +const Card = ({ cfg }) => { + const { collapsed = false } = cfg; + + return ( + + + + 这是一个卡片 + + + 我是一段特别特别特别特别特别特别特别长的描述 + + {collapsed && ( + + + + + + + + + + )} + + { + graph.updateItem(node, { + collapsed: !collapsed, + }); + }} + > + {collapsed ? '-' : '+'} + + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(Card)); + +export default () => ; +``` diff --git a/packages/react-node/src/Example/event.md b/packages/react-node/src/Example/event.md new file mode 100644 index 0000000000..d448141185 --- /dev/null +++ b/packages/react-node/src/Example/event.md @@ -0,0 +1,118 @@ +# 事件使用示例 + +> 这是一个简单的形状事件绑定案例, 点击右上角按钮展开节点 + +```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 }) => ( + + {text} + +); + +const Card = ({ cfg }) => { + const { collapsed = false } = cfg; + + return ( + + + + 这是一个卡片 + + + 我是一段特别特别特别特别特别特别特别长的描述 + + {collapsed && ( + + + + + + + + + + )} + + { + graph.updateItem(node, { + collapsed: !collapsed, + }); + }} + > + {collapsed ? '-' : '+'} + + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(Card)); + +export default () => ; +``` diff --git a/packages/react-node/src/Layout/LayoutEnums.ts b/packages/react-node/src/Layout/LayoutEnums.ts new file mode 100644 index 0000000000..56b39ec4e1 --- /dev/null +++ b/packages/react-node/src/Layout/LayoutEnums.ts @@ -0,0 +1,64 @@ +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; +} diff --git a/packages/react-node/src/Layout/getPositionsUsingYoga.ts b/packages/react-node/src/Layout/getPositionsUsingYoga.ts new file mode 100644 index 0000000000..229cbc6dfe --- /dev/null +++ b/packages/react-node/src/Layout/getPositionsUsingYoga.ts @@ -0,0 +1,282 @@ +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; diff --git a/packages/react-node/src/Layout/getShapeSize.ts b/packages/react-node/src/Layout/getShapeSize.ts new file mode 100644 index 0000000000..1ff2f397ea --- /dev/null +++ b/packages/react-node/src/Layout/getShapeSize.ts @@ -0,0 +1,186 @@ +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; + 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; diff --git a/packages/react-node/src/Loading/index.tsx b/packages/react-node/src/Loading/index.tsx new file mode 100644 index 0000000000..262af67dad --- /dev/null +++ b/packages/react-node/src/Loading/index.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +export default function Loading() { + return ( +
+
+ +

L o a d i n g . . .

+
+
+ ); +} diff --git a/packages/react-node/src/ReactNode/Group.tsx b/packages/react-node/src/ReactNode/Group.tsx new file mode 100644 index 0000000000..f500036bb3 --- /dev/null +++ b/packages/react-node/src/ReactNode/Group.tsx @@ -0,0 +1,65 @@ +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; + /** + * @description.en-US Nodes wrapped within the component + * @description.zh-CN 组件内包装的节点 + */ + children?: React.ReactNode; +} + +export type CommonProps = GroupProps & EventAttrs; + +const Group: React.FC = (props) => { + // @ts-ignore + const { children, ...rest } = props; + return ( +
+ {children} +
+ ); +}; + +export default Group; diff --git a/packages/react-node/src/ReactNode/Shape/Circle.tsx b/packages/react-node/src/ReactNode/Shape/Circle.tsx new file mode 100644 index 0000000000..cd5e02d2bb --- /dev/null +++ b/packages/react-node/src/ReactNode/Shape/Circle.tsx @@ -0,0 +1,36 @@ +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 = (props) => { + // @ts-ignore + const { children, ...rest } = props; + + return ( +
+ {children} +
+ ); +}; + +export default Circle; diff --git a/packages/react-node/src/ReactNode/Shape/Ellipse.tsx b/packages/react-node/src/ReactNode/Shape/Ellipse.tsx new file mode 100644 index 0000000000..71b0909062 --- /dev/null +++ b/packages/react-node/src/ReactNode/Shape/Ellipse.tsx @@ -0,0 +1,41 @@ +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 = (props) => { + // @ts-ignore + const { children, ...rest } = props; + + return ( +
+ {children} +
+ ); +}; + +export default Ellipse; diff --git a/packages/react-node/src/ReactNode/Shape/Image.tsx b/packages/react-node/src/ReactNode/Shape/Image.tsx new file mode 100644 index 0000000000..b1feece4cf --- /dev/null +++ b/packages/react-node/src/ReactNode/Shape/Image.tsx @@ -0,0 +1,46 @@ +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 = (props) => { + // @ts-ignore + const { children, ...rest } = props; + + return ( +
+ {children} +
+ ); +}; + +export default Image; diff --git a/packages/react-node/src/ReactNode/Shape/Marker.tsx b/packages/react-node/src/ReactNode/Shape/Marker.tsx new file mode 100644 index 0000000000..2f3447c4a4 --- /dev/null +++ b/packages/react-node/src/ReactNode/Shape/Marker.tsx @@ -0,0 +1,47 @@ +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 = (props) => { + // @ts-ignore + const { children, ...rest } = props; + + return ( +
+ {children} +
+ ); +}; + +export default Marker; diff --git a/packages/react-node/src/ReactNode/Shape/Path.tsx b/packages/react-node/src/ReactNode/Shape/Path.tsx new file mode 100644 index 0000000000..af98ba0e02 --- /dev/null +++ b/packages/react-node/src/ReactNode/Shape/Path.tsx @@ -0,0 +1,68 @@ +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 = (props) => { + // @ts-ignore + const { children, ...rest } = props; + + return ( +
+ {children} +
+ ); +}; + +export default Path; diff --git a/packages/react-node/src/ReactNode/Shape/Polygon.tsx b/packages/react-node/src/ReactNode/Shape/Polygon.tsx new file mode 100644 index 0000000000..e7527993f5 --- /dev/null +++ b/packages/react-node/src/ReactNode/Shape/Polygon.tsx @@ -0,0 +1,36 @@ +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 = (props) => { + // @ts-ignore + const { children, ...rest } = props; + + return ( +
+ {children} +
+ ); +}; + +export default Polygon; diff --git a/packages/react-node/src/ReactNode/Shape/Rect.tsx b/packages/react-node/src/ReactNode/Shape/Rect.tsx new file mode 100644 index 0000000000..550e4a4561 --- /dev/null +++ b/packages/react-node/src/ReactNode/Shape/Rect.tsx @@ -0,0 +1,46 @@ +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 = (props) => { + // @ts-ignore + const { children, ...rest } = props; + + return ( +
+ {children} +
+ ); +}; + +export default Rect; diff --git a/packages/react-node/src/ReactNode/Shape/Text.tsx b/packages/react-node/src/ReactNode/Shape/Text.tsx new file mode 100644 index 0000000000..164e04ff81 --- /dev/null +++ b/packages/react-node/src/ReactNode/Shape/Text.tsx @@ -0,0 +1,70 @@ +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 = (props) => { + // @ts-ignore + const { children, ...rest } = props; + + return ( +
+ {children} +
+ ); +}; + +export default Text; diff --git a/packages/react-node/src/ReactNode/Shape/common.ts b/packages/react-node/src/ReactNode/Shape/common.ts new file mode 100644 index 0000000000..227c5140d2 --- /dev/null +++ b/packages/react-node/src/ReactNode/Shape/common.ts @@ -0,0 +1,114 @@ +import { LayoutAttrs } from '../../Layout/LayoutEnums'; +import { CSSProperties } from 'react'; + +export interface GShapeProps extends Partial { + /** + * @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; + }; diff --git a/packages/react-node/src/ReactNode/demo.tsx b/packages/react-node/src/ReactNode/demo.tsx new file mode 100644 index 0000000000..521ef12d0f --- /dev/null +++ b/packages/react-node/src/ReactNode/demo.tsx @@ -0,0 +1,78 @@ +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
; +}; diff --git a/packages/react-node/src/ReactNode/index.ts b/packages/react-node/src/ReactNode/index.ts new file mode 100644 index 0000000000..93fd8db42b --- /dev/null +++ b/packages/react-node/src/ReactNode/index.ts @@ -0,0 +1,11 @@ +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'; diff --git a/packages/react-node/src/Register/event.ts b/packages/react-node/src/Register/event.ts new file mode 100644 index 0000000000..cfcfc7aa4c --- /dev/null +++ b/packages/react-node/src/Register/event.ts @@ -0,0 +1,64 @@ +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); + } + }); + }); +} diff --git a/packages/react-node/src/Register/getDataFromReactNode.ts b/packages/react-node/src/Register/getDataFromReactNode.ts new file mode 100644 index 0000000000..1fed76fda2 --- /dev/null +++ b/packages/react-node/src/Register/getDataFromReactNode.ts @@ -0,0 +1,54 @@ +import { ReactElement } from 'react'; +import { LayoutAttrs } from '../Layout/LayoutEnums'; + +export interface RawNode { + type: string; + attrs: { [key: string]: any } & Partial; + 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; diff --git a/packages/react-node/src/Register/register.tsx b/packages/react-node/src/Register/register.tsx new file mode 100644 index 0000000000..9c5c9856dc --- /dev/null +++ b/packages/react-node/src/Register/register.tsx @@ -0,0 +1,133 @@ +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(); + + 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], + ]; + }, + }; +} diff --git a/packages/react-node/src/index.ts b/packages/react-node/src/index.ts new file mode 100644 index 0000000000..a60c15518b --- /dev/null +++ b/packages/react-node/src/index.ts @@ -0,0 +1 @@ +export * from './ReactNode'; diff --git a/packages/react-node/test/react.test.tsx b/packages/react-node/test/react.test.tsx new file mode 100644 index 0000000000..a2325ee25b --- /dev/null +++ b/packages/react-node/test/react.test.tsx @@ -0,0 +1,15 @@ +import { Group, Rect, Text, Circle } from '@antv/g6-react-node'; + +const Node = () => { + return ( + + + + + Text + + + ); +}; diff --git a/packages/react-node/tsconfig.json b/packages/react-node/tsconfig.json new file mode 100644 index 0000000000..7398379f46 --- /dev/null +++ b/packages/react-node/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "importHelpers": true, + "jsx": "react", + "esModuleInterop": true, + "sourceMap": true, + "baseUrl": "./", + "strict": true, + "paths": { + "@/*": ["src/*"], + "@@/*": ["src/.umi/*"] + }, + "allowSyntheticDefaultImports": true + }, + "exclude": [ + "node_modules", + "lib", + "es", + "dist", + "typings", + "**/__test__", + "test" + ] +} diff --git a/packages/react-node/typings.d.ts b/packages/react-node/typings.d.ts new file mode 100644 index 0000000000..71e0e9f4c0 --- /dev/null +++ b/packages/react-node/typings.d.ts @@ -0,0 +1,2 @@ +declare module '*.css'; +declare module '*.less'; diff --git a/packages/site/.dumi/global.ts b/packages/site/.dumi/global.ts new file mode 100644 index 0000000000..f5ec32274b --- /dev/null +++ b/packages/site/.dumi/global.ts @@ -0,0 +1,9 @@ +if (window) { + // window.g6 = require('@antv/g6/es'); // import the source for debugging + // window.g6 = require('@antv/g6/lib'); // import the source for debugging + (window as any).g6 = require('@antv/g6/dist/g6.min.js'); // import the package for webworker + (window as any).insertCss = require('insert-css'); + (window as any).Chart = require('@antv/chart-node-g6'); + (window as any).AntVUtil = require('@antv/util'); + (window as any).GraphLayoutPredict = require('@antv/vis-predict-engine'); +} \ No newline at end of file diff --git a/packages/site/.dumirc.ts b/packages/site/.dumirc.ts new file mode 100644 index 0000000000..ce92de2232 --- /dev/null +++ b/packages/site/.dumirc.ts @@ -0,0 +1,594 @@ +import fs from 'fs' +import path from 'path' +import { defineConfig } from 'dumi'; +import { repository, version, homepage } from './package.json'; +import { Extractor, ExtractorConfig } from '@microsoft/api-extractor'; + +const getExtraLib = () => { + try { + const extractorConfig = ExtractorConfig.loadFileAndPrepare( + path.resolve('./api-extractor.json'), + ); + const extractorResult = Extractor.invoke(extractorConfig, { + localBuild: true, + showVerboseMessages: true, + }); + if (extractorResult.succeeded) { + const typeFilePath = extractorResult.extractorConfig.untrimmedFilePath; + if (typeFilePath) { + return `declare module '${name}'{ + ${fs.readFileSync(typeFilePath, `utf8`)} + }`; + } + } + } catch (e) { + // eslint-disable-next-line no-console + console.warn(`api-extractor warn: ${e.message}`); + } + return ''; +}; + +export default defineConfig({ + locales: [{ id: 'zh', name: '中文' }, { id: 'en', name: 'English' }], + title: 'G6', // 网站header标题 + favicons: ['https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*7svFR6wkPMoAAAAAAAAAAAAADmJ7AQ/original'], // 网站 favicon + metas: [ // 自定义 meta 标签 + { name: 'keywords', content: 'G6' }, + { name: 'description', content: 'A collection of charts made with the Grammar of Graphics' }, + ], + themeConfig: { + title: 'G6', + description: 'A collection of charts made with the Grammar of Graphics', + defaultLanguage: 'zh', // 默认语言 + isAntVSite: false, // 是否是 AntV 的大官网 + siteUrl: homepage, // 官网地址 + githubUrl: repository.url, // GitHub 地址 + showSearch: true, // 是否显示搜索框 + showGithubCorner: true, // 是否显示头部的 GitHub icon + showGithubStars: true, // 是否显示 GitHub star 数量 + showAntVProductsCard: true, // 是否显示 AntV 产品汇总的卡片 + showLanguageSwitcher: true, // 是否显示官网语言切换 + showWxQrcode: true, // 是否显示头部菜单的微信公众号 + showChartResize: true, // 是否在 demo 页展示图表视图切换 + showAPIDoc: true, // 是否在 demo 页展示API文档 + themeSwitcher: 'g2', + versions: { // 历史版本以及切换下拉菜单 + [version]: 'https://g6.antv.antgroup.com', + '3.2.x': 'https://g6-v3-2.antv.vision', + }, + docsearchOptions: { // 头部搜索框配置 + apiKey: '9d1cd586972bb492b7b41b13a949ef30', + indexName: 'antv_g6', + }, + navs: [ + { + slug: 'docs/design/overview', + title: { + zh: '设计体系', + en: 'Design System', + }, + }, + { + slug: 'docs/manual/introduction', + title: { + zh: '教程', + en: 'Manual', + }, + }, + { + slug: 'docs/api/Graph', + title: { + zh: 'API', + en: 'API', + }, + }, + { + title: { + zh: '在线工具', + en: 'Online Tools', + }, + dropdownItems: [ + { + name: { + zh: 'Graphinsight', + en: 'Graphinsight' + }, + url: 'https://graphinsight.antgroup.com/#/workspace' + }, + { + name: { + zh: 'GraphMaker', + en: 'GraphMaker' + }, + url: 'https://render.mybank.cn/p/c/17sfi50vhu80#/home' + }, + ] + }, + { + slug: 'examples', + title: { + zh: '图表示例', + en: 'Examples', + }, + }, + { + slug: 'https://www.yuque.com/antv/g6-blog', + title: { + zh: '博客', + en: 'Blog', + }, + }, + ], + ecosystems: [ // 头部的菜单中的「周边生态」 + + ], + docs: [ + // ===========Design=================== + { + slug: 'design/global', + title: { + zh: '全局规范', + en: 'Global', + }, + order: 3, + }, + { + slug: 'design/component', + title: { + zh: '组件设计', + en: 'Component Design', + }, + order: 4, + }, + { + slug: 'manual/FAQ', + title: { + zh: 'FAQ', + en: 'FAQ', + }, + order: 2, + }, + { + slug: 'manual/tutorial', + title: { + zh: '入门教程', + en: 'Tutorial', + }, + order: 3, + }, + // ===========Concepts=================== + { + slug: 'manual/middle', + title: { + zh: '核心概念', + en: 'Middle', + }, + order: 4, + }, + + { + slug: 'manual/middle/elements', + title: { + zh: '图元素:节点/边/Combo', + en: 'Graph Elements', + }, + order: 2, + }, + + { + slug: 'manual/middle/elements/shape', + title: { + zh: '图形 Shape(选读)', + en: 'Shape', + }, + order: 1, + }, + { + slug: 'manual/middle/elements/nodes', + title: { + zh: '节点', + en: 'Node', + }, + order: 2, + }, + { + slug: 'manual/middle/elements/edges', + title: { + zh: '边', + en: 'Edge', + }, + order: 3, + }, + { + slug: 'manual/middle/elements/combos', + title: { + zh: 'Combo', + en: 'Combo', + }, + order: 4, + }, + + { + slug: 'manual/middle/elements/nodes/built-in', + title: { + zh: '内置节点类型', + en: 'Built-in Nodes', + }, + order: 1, + }, + { + slug: 'manual/middle/elements/edges/built-in', + title: { + zh: '内置边类型', + en: 'Built-in Edges', + }, + order: 1, + }, + { + slug: 'manual/middle/elements/combos/built-in', + title: { + zh: '内置 Combo', + en: 'Built-in Combos', + }, + order: 1, + }, + + { + slug: 'manual/middle/elements/advanced-style', + title: { + zh: '高级样式', + en: 'Advanced Style', + }, + order: 5, + }, + { + slug: 'manual/middle/elements/methods', + title: { + zh: '高级操作', + en: 'Advanced operation', + }, + order: 6, + }, + + { + slug: 'manual/middle/layout', + title: { + zh: '图布局', + en: 'Graph Layouts', + }, + order: 3, + }, + { + slug: 'manual/middle/states', + title: { + zh: '交互与事件', + en: 'Behavior & Event', + }, + order: 4, + }, + { + slug: 'manual/middle/plugins', + title: { + zh: '分析组件', + en: 'Component', + }, + order: 6, + }, + // ============================== + { + slug: 'manual/advanced', + title: { + zh: '拓展阅读', + en: 'Further Reading', + }, + order: 5, + }, + // ==========API==================== + { + slug: 'api/graphLayout', + title: { + zh: '图布局 Graph Layout', + en: 'Graph Layout', + }, + order: 2, + }, + { + slug: 'api/graphFunc', + title: { + zh: 'Graph 实例方法', + en: 'Graph Functions', + }, + order: 1, + }, + { + slug: 'api/treeGraphLayout', + title: { + zh: '树图布局 TreeGraph Layout', + en: 'TreeGraph Layout', + }, + order: 5, + }, + { + slug: 'api/Items', + title: { + zh: '元素方法和配置', + en: 'Item Functions & Options', + }, + order: 6, + }, + ], + tutorials: [ + { + slug: 'manual/about', + title: { + zh: '关于', + en: 'About', + }, + order: 1, + }, + ], + examples: [ + { + slug: 'case', + icon: 'gallery', + title: { + zh: '场景案例', + en: 'Case', + }, + }, + { + slug: 'net', + icon: 'net', + title: { + zh: '布局:一般图', + en: 'Layout:General Graph', + }, + }, + { + slug: 'tree', + icon: 'tree', // 图标名可以去 https://antv.alipay.com/zh-cn/g2/3.x/demo/index.html 打开控制台查看图标类名 + title: { + zh: '布局:树图', + en: 'Layout:Tree Graph', + }, + }, + { + slug: 'item', + icon: 'shape', + title: { + zh: '元素', + en: 'Item', + }, + }, + { + slug: 'interaction', + icon: 'interaction', + title: { + zh: '交互', + en: 'Interaction', + }, + }, + { + slug: 'scatter', + icon: 'scatter', + title: { + zh: '动画', + en: 'Animation', + }, + }, + { + slug: 'tool', + icon: 'tool', + title: { + zh: '组件', + en: 'Component', + }, + }, + { + slug: 'algorithm', + icon: 'gallery', + title: { + zh: '算法', + en: 'Algorithm', + }, + }, + { + slug: 'performance', + icon: 'net', + title: { + zh: '性能测试', + en: 'Performance', + }, + }, + ], + mdPlayground: { + // 第一个分块的大小 + splitPaneMainSize: '62%', + }, + playground: { + extraLib: getExtraLib(), + }, + announcement: { + zh: '', + en: '', + }, + /** 首页技术栈介绍 */ + detail: { + title: { + zh: 'G6 图可视化引擎', + en: 'G6 Graph Visualization Engine', + }, + description: { + zh: 'G6 是一个简单、易用、完备的图可视化引擎,它在高定制能力的基础上,提供了一系列设计优雅、便于使用的图可视化解决方案。能帮助开发者搭建属于自己的图可视化、图分析、或图编辑器应用。', + en: 'G6 is graph visualization engine with simplicity and convenience. Based on the ability of customize, it provides a set of elegant graph visualization solutions, and helps developers to build up applications for graph visualization, graph analysis, and graph editor.' + }, + image: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*j5AqSpmNPdYAAAAAAAAAAABkARQnAQ', + buttons: [ + { + text: { + zh: '图表示例', + en: 'Examples', + }, + link: `/examples`, + }, + { + text: { + zh: '开始使用', + en: 'Getting Started', + }, + link: `/manual/introduction`, + type: 'primary', + }, + ], + }, + /** 新闻公告,优先选择配置的,如果没有配置则使用远程的! */ + news: [ + { + type: { + zh: '推荐', + en: 'News', + }, + title: { + zh: '图可视分析与搭建平台 GraphInsight 开源', + en: 'GraphInsight is opened source!', + }, + date: '2022.11.22', + link: 'https://www.yuque.com/antv/blog/nyl5bkhdkicgm7v8', + }, + { + type: { + zh: '推荐', + en: 'News', + }, + title: { + zh: 'G6 5.0 插件机制与引擎性能蓄势待发', + en: 'G6 5.0: extension mechanism and better performance', + }, + date: '2022.11.22', + link: 'https://www.yuque.com/antv/blog/fm6setn8p9m9lmh5', + }, + ], + /** 首页特性介绍 */ + features: [ + { + icon: 'https://gw.alipayobjects.com/zos/basement_prod/0e03c123-031b-48ed-9050-4ee18c903e94.svg', + title: { + zh: '专注关系,完备基建', + en: 'Dedicated & Complete', + }, + description: { + zh: 'G6 是一个专注于关系数据的、完备的图可视化引擎', + en: 'G6 is a complete graph visualization engine, which focuses on relational data' + }, + }, + { + icon: 'https://gw.alipayobjects.com/zos/basement_prod/42d17359-8607-4227-af93-7509eabb3163.svg', + title: { + zh: '领域深钻,顶尖方案', + en: 'Top Solution', + }, + description: { + zh: '扎根实际具体业务场景、结合业界领先成果,沉淀顶尖解决方案', + en: 'According to practical bussiness scenarios, we found out the top solutions', + }, + }, + { + icon: 'https://gw.alipayobjects.com/zos/basement_prod/acd8d1f3-d256-42b7-8340-27e5d5fde92c.svg', + title: { + zh: '简单易用,扩展灵活', + en: 'Simple & Extendable', + }, + description: { + zh: 'Vivid, 精心设计的简单、灵活、高可拓展的接口,满足你的无限创意', + en: 'Well-designed simple, flexible, and extendable intefaces will satisfy your infinite originality' + } + }, + ], + /** 首页案例 */ + cases: [ + { + logo: 'https://camo.githubusercontent.com/53886f0e306c9f01c96dee2edca3992830b7cbb769118029a7e5d677deb7e67e/68747470733a2f2f67772e616c697061796f626a656374732e636f6d2f7a6f732f616e7466696e63646e2f306234487a4f63454a592f4772617068696e2e737667', + title: { + zh: 'Graphin 图可视分析组件', + en: 'Graphin: Graph Insight', + }, + description: { + zh: 'Graphin 是一款基于 G6 封装的 React 分析组件库,专注在关系可视分析领域,简单高效,开箱即用。', + en: "Graphin stands for Graph Insight. It's a toolkit based on G6 and React, that focuses on relational visual analysis.It's simple, efficient, out of the box." + }, + link: `https://graphin.antv.vision`, + image: + 'https://gw.alipayobjects.com/mdn/rms_00edcb/afts/img/A*LKq7Q5wPA0AAAAAAAAAAAAAAARQnAQ', + }, + { + logo: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ch6rTrCxb6YAAAAAAAAAAABkARQnAQ', + title: { + zh: '基于 G6 的动态决策树', + en: 'Interactive Decision Graph Powered by G6', + }, + description: { + zh: '基于 G6 实现的动态决策树,辅助用户寻找合适的可视化方式。它展示了 G6 强大的自定义节点和动画的能力。', + en: 'It is an interactive graph for users to find out an appropriate visualization method for their requirements. The demo shows the powerful custom node and animation ability of G6.' + }, + link: `/examples/case/graphDemos/#decisionBubbles`, + image: + 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*10b6R5fkyJ4AAAAAAAAAAABkARQnAQ', + }, + { + logo: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*95GYRI0zPx8AAAAAAAAAAABkARQnAQ', + title: { + zh: '基于 G6 的图分析应用', + en: 'Graph Analysis App Powered by G6', + }, + description: { + zh: '社交网络分析是图可视化中一个重要的应用场景。随着社交网络越来越流行,人与人、人与组织之间的关系变得越来越复杂,使用传统的分析手段,已经很难满足我们的分析需求。在这种情况下,图分析及图可视化显得愈发重要。', + en: 'Social network is an important scenario in graph visualization. The relationships become complicate with the development of social network. Graph visualization and analysis do well on these complex cases.' + }, + link: `/manual/cases/relations`, + image: + 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*RYFQSZYewokAAAAAAAAAAABkARQnAQ', + }, + { + logo: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IEQFS5VtXX8AAAAAAAAAAABkARQnAQ', + title: { + zh: '基于 G6 的关系时序分析应用', + en: 'Dynamic Relationships Analysis Powered by G6', + }, + description: { + zh: '基于 G6 的关系时序分析应用,解决应急过程中流程、影响面、应急预案等一系列应急决策辅助信息和手段,快速止血以减少和避免故障升级。', + en: 'This is an application for dynamic relationships analysis based on G6, which helps people deal with the flow, influence, and find out solutions to avoid losses and faults.' + }, + link: `/manual/cases/sequenceTime`, + image: + 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*m41kSpg17ZkAAAAAAAAAAABkARQnAQ', + }, + ], + /** 首页合作公司 */ + companies: [ + { name: '阿里云', img: 'https://gw.alipayobjects.com/mdn/rms_2274c3/afts/img/A*V_xMRIvw2iwAAAAAAAAAAABkARQnAQ' }, + { name: '支付宝', img: 'https://gw.alipayobjects.com/mdn/rms_2274c3/afts/img/A*lYDrRZvcvD4AAAAAAAAAAABkARQnAQ', }, + { name: '天猫', img: 'https://gw.alipayobjects.com/mdn/rms_2274c3/afts/img/A*BQrxRK6oemMAAAAAAAAAAABkARQnAQ', }, + { name: '淘宝网', img: 'https://gw.alipayobjects.com/mdn/rms_2274c3/afts/img/A*1l8-TqUr7UcAAAAAAAAAAABkARQnAQ', }, + { name: '网上银行', img: 'https://gw.alipayobjects.com/mdn/rms_2274c3/afts/img/A*ZAKFQJ5Bz4MAAAAAAAAAAABkARQnAQ', }, + { name: '京东', img: 'https://gw.alipayobjects.com/mdn/rms_2274c3/afts/img/A*yh-HRr3hCpgAAAAAAAAAAABkARQnAQ', }, + { name: 'yunos', img: 'https://gw.alipayobjects.com/mdn/rms_2274c3/afts/img/A*_js7SaNosUwAAAAAAAAAAABkARQnAQ', }, + { name: '菜鸟', img: 'https://gw.alipayobjects.com/mdn/rms_2274c3/afts/img/A*TgV-RZDODJIAAAAAAAAAAABkARQnAQ', }, + ], + internalSite: { + url: 'https://g6.antv.antgroup.com', + name: { + zh: '极速站点', + en: 'Fast Site', + }, + }, + }, + mfsu: false, + alias: { + '@': __dirname + }, + links: [ + ], + scripts: [ + ], + jsMinifier: 'terser', +}); diff --git a/packages/site/.eslintignore b/packages/site/.eslintignore new file mode 100644 index 0000000000..b1fc0c82cd --- /dev/null +++ b/packages/site/.eslintignore @@ -0,0 +1,22 @@ +build/ +coverage/ +lib/ +dist/ +mocks/ +node_modules/ +demos/ +.cache +public +bin +esm/ +es/ +examples/ +tests/ +stories/ +gatsby-browser.js +site/ +webpack.*.js +gatsby-*.js +global.d.ts +jest.config.js +.eslintrc.* diff --git a/packages/site/.eslintrc.js b/packages/site/.eslintrc.js new file mode 100644 index 0000000000..b9c5c5d6d2 --- /dev/null +++ b/packages/site/.eslintrc.js @@ -0,0 +1,47 @@ +module.exports = { + extends: [require.resolve('@umijs/fabric/dist/eslint')], + globals: { + $: true, + _: true, + }, + rules: { + 'no-bitwise': 0, + 'import/order': 0, + 'no-plusplus': 0, + 'no-console': ['error', { allow: ['warn', 'error'] }], + 'operator-assignment': 0, + 'consistent-return': 0, + 'lines-between-class-members': 0, + 'class-methods-use-this': 0, + 'lines-between-class-members': 0, + 'no-multi-assign': 0, + 'no-continue': 0, + 'no-underscore-dangle': 0, + 'no-useless-constructor': 0, + 'prefer-destructuring': 0, + 'guard-for-in': 0, + 'no-restricted-globals': 0, + 'max-classes-per-file': 0, + '@typescript-eslint/no-invalid-this': 0, + '@typescript-eslint/no-this-alias': 0, + '@typescript-eslint/array-type': 0, + 'import/export': 0, + // 后面需要去掉 + 'no-restricted-syntax': 0, + 'prefer-spread': 0, + '@typescript-eslint/camelcase': 0, + 'no-loop-func': 0, + '@typescript-eslint/no-loop-func': 0, + '@typescript-eslint/no-redeclare': 0, + '@typescript-eslint/no-shadow': 0, + '@typescript-eslint/no-unused-vars': 0, + 'no-param-reassign': 0, + 'import/no-extraneous-dependencies': 0, + 'no-unused-expressions': 0, + 'dot-notation': 0, + 'array-callback-return': 0, + 'one-var': 0, + 'no-lonely-if': 0, + 'no-sequences': 0 + }, +}; diff --git a/packages/site/.github/workflows/mirror.yml b/packages/site/.github/workflows/mirror.yml new file mode 100644 index 0000000000..e0791fdc3c --- /dev/null +++ b/packages/site/.github/workflows/mirror.yml @@ -0,0 +1,30 @@ +name: 🤖 Sync to Gitee Mirror + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: 🔁 Sync to Gitee + uses: wearerequired/git-mirror-action@master + env: + # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY + SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} + with: + # 注意替换为你的 GitHub 源仓库地址 + source-repo: 'git@github.com:antvis/G6.git' + # 注意替换为你的 Gitee 目标仓库地址 + destination-repo: 'git@gitee.com:antv-g6/antv-g6.git' + + - name: ✅ Build Gitee Pages + uses: yanglbme/gitee-pages-action@master + with: + # 注意替换为你的 Gitee 用户名 + gitee-username: afc163 + # 注意在 Settings->Secrets 配置 GITEE_PASSWORD + gitee-password: ${{ secrets.GITEE_PASSWORD }} + # 注意替换为你的 Gitee 仓库 + gitee-repo: antv-g6/antv-g6 + # 要部署的分支 + branch: gh-pages diff --git a/packages/site/.gitignore b/packages/site/.gitignore new file mode 100644 index 0000000000..25c52d1663 --- /dev/null +++ b/packages/site/.gitignore @@ -0,0 +1,10 @@ +/node_modules +/dist +/example/node_modules +/example/dist +/example/.dumi/theme +/example/.dumi/tmp +/example/.dumi/tmp-production +.dumi/tmp +.dumi/tmp-production +.DS_Store diff --git a/packages/site/CNAME b/packages/site/CNAME new file mode 100644 index 0000000000..b6bfd10c22 --- /dev/null +++ b/packages/site/CNAME @@ -0,0 +1 @@ +g6-v3-2.antv.vision \ No newline at end of file diff --git a/packages/site/api-extractor.json b/packages/site/api-extractor.json new file mode 100644 index 0000000000..aa8cd9927f --- /dev/null +++ b/packages/site/api-extractor.json @@ -0,0 +1,15 @@ +{ + "mainEntryPointFilePath": "/lib/index.d.ts", + "apiReport": { + "enabled": false + }, + "docModel": { + "enabled": false + }, + "dtsRollup": { + "enabled": true + }, + "tsdocMetadata": { + "enabled": false + } +} diff --git a/packages/site/docs/api/Algorithm.en.md b/packages/site/docs/api/Algorithm.en.md new file mode 100644 index 0000000000..9b0e62db9e --- /dev/null +++ b/packages/site/docs/api/Algorithm.en.md @@ -0,0 +1,858 @@ +--- +title: Graph Algorithm +order: 15 +--- + +Graph algorithms provide one of the most effective methods for analyzing relational data. They describe how to process graphs to discover some qualitative or quantitative measures. Graph algorithms are based on graph theory and use the relationship between them to structure and change complex systems. We can use these algorithms to discover hidden information, verify business assumptions, and change behavior to make predictions. + +If you are interested in data structures and algorithms, you can learn from [javascript-algorithms](https://github.com/trekhleb/javascript-algorithms). + +G6 has added graph algorithms since V3.5. In future versions, we will continue to enrich the built-in algorithms. + + +### GADDI Graph Pattern Macthing + +New Feature in「v4.2.2」 + +[GADDI Graph Pattern Macthing]() supports structure and semantic matching. Give a graph data and a pattern data with specific semantic clustering infomation, it returns the same and similar sub structures on the origin graph data. [DEMO](/en/examples/algorithm/algoDemos#gaddi)。 + +img + +**Parameters** + +| Name | Type | Required | Description | +| ----------- | ------------------- | -------- | ------------------- | +| graphData | GraphData | true | The origin graph data | +| pattern | GraphData | true | The pattern graph data to be matched | +| k | number | false | The parameter for GADDI, it will be calculated automatically when it is `undefined` | +| length | number | false | The parameter for GADDI, it will be calculated automatically when it is `undefined` | +| nodeLabelProp | number | false | The name of the cluster property in the nodes data | +| edgeLabelProp | number | false | The name of the cluster property in the edges data | + + +**Usage** + +```javascript +import G6, { Algorithm } from '@antv/g6' +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500 +}) + +const graphData = { + nodes: [ + { id: 'A', cluster: 'nc1' }, + { id: 'B', cluster: 'nc1' }, + { id: 'C', cluster: 'nc2' }, + { id: 'D', cluster: 'nc1' }, + { id: 'E', cluster: 'nc3' }, + ], + edges: [ + { source: 'A', target: 'B', cluster: 'ec1' }, + { source: 'B', target: 'C', cluster: 'ec2' }, + { source: 'A', target: 'D', cluster: 'ec1' }, + { source: 'A', target: 'E', cluster: 'ec2' }, + ] +} + +graph.data(data) +graph.render() + +const { GADDI } = Algorithm; +const patternData = { + nodes: [ + { id: 'pn1', cluster: 'nc1' }, + { id: 'pn2', cluster: 'nc1' }, + { id: 'pn3', cluster: 'nc3' }, + ], + edges: [ + { source: 'pn1', target: 'pn2', cluster: 'ec1' }, + { source: 'pn1', target: 'pn3', cluster: 'ec2' }, + ] +} +const resultMatches = GADDI(graphData, patternData, true, undefined, undefined, 'cluster', 'cluster'); + +console.log(resultMatches); + // output: + // [{ + // nodes: [ + // { id: 'A', cluster: 'nc1' }, + // { id: 'B', cluster: 'nc1' }, + // { id: 'E', cluster: 'nc3' },], + // edges: [ + // { source: 'A', target: 'B', cluster: 'ec1' }, + // { source: 'A', target: 'E', cluster: 'ec2' } + // ] + // }] +``` + +### depthFirstSearch + +[Depth first search](https://en.wikipedia.org/wiki/Depth-first_search) (DFS) is an algorithm for traversing or searching tree or graph data structures. One starts at the root (selecting some arbitrary node as the root in the case of a graph) and explores as far as possible along each branch before backtracking. + +img + +[Image Source](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/depth-first-search) + +**Parameters** + +| Name | Type | Required | Description | +| ----------- | ------------------- | -------- | -------------------------------- | +| graphData | GraphData | true | Graph data in G6 [data format](/en/docs/manual/tutorial/elements#data-structure). Note that it should be assigned with graph instance to this parameter instead before v4.1.0 | +| startNodeId | string | true | The ID of the node to be started | +| callbacks | IAlgorithmCallbacks | false | The callback function | + +**Usage** + +```javascript +import G6, { Algorithm } from '@antv/g6'; +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); + +const data = { + nodes: [ + { id: 'A' }, + { id: 'B' }, + { id: 'C' }, + { id: 'D' }, + { id: 'E' }, + { id: 'F' }, + { id: 'G' }, + ], + edges: [ + { source: 'A', target: 'B' }, + { source: 'B', target: 'C' }, + { source: 'A', target: 'C' }, + { source: 'D', arget: 'A' }, + { source: 'D', target: 'E' }, + { source: 'E', target: 'F' }, + ], +}; + +graph.data(data); +graph.render(); + +const { depthFirstSearch } = Algorithm; +depthFirstSearch(data, 'A', { + enter: ({ current, previous }) => { + // The callback function for the traversal's begining + }, + leave: ({ current, previous }) => { + // The callback function for the traversal's ending + }, +}); +``` + +### breadthFirstSearch + +[Breadth-first search](https://en.wikipedia.org/wiki/Breadth-first_search) (BFS) is an algorithm for traversing or searching tree or graph data structures. It starts at the tree root (or some arbitrary node of a graph, sometimes referred to as a 'search key') and explores the neighbor nodes first, before moving to the next level neighbors. + +img + +[Image Source](https://camo.githubusercontent.com/b8073f26dfdf1644e8a92312fff100341987a8f5/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f352f35642f427265616474682d46697273742d5365617263682d416c676f726974686d2e676966) + +**Parameters** + +| Name | Type | Required | Description | +| ----------------- | ------------------- | -------- | --------------------------- | +| graphData | GraphData | true | Graph data in G6 [data format](/en/docs/manual/tutorial/elements#data-structure). Note that it should be assigned with graph instance to this parameter instead before v4.1.0 | +| startNodeId | string | true | The ID of the starting node | +| originalCallbacks | IAlgorithmCallbacks | false | The callback function | + +**Usage** + +```javascript +import G6, { Algorithm } from '@antv/g6'; +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); + +const data = { + nodes: [ + { id: 'A' }, + { id: 'B' }, + { id: 'C' }, + { id: 'D' }, + { id: 'E' }, + { id: 'F' }, + { id: 'G' }, + ], + edges: [ + { source: 'A', target: 'B' }, + { source: 'B', target: 'C' }, + { source: 'A', target: 'C' }, + { source: 'D', arget: 'A' }, + { source: 'D', target: 'E' }, + { source: 'E', target: 'F' }, + ], +}; + +graph.data(data); +graph.render(); + +const { breadthFirstSearch } = Algorithm; +breadthFirstSearch(data, 'A', { + enter: ({ current, previous }) => { + // The callback function for the traversal's begining + }, + leave: ({ current, previous }) => { + // The callback function for the traversal's ending + }, +}); +``` + +### labelPropagation + +_Supported after G6 4.0_ Label Propagation compute the clusters for graph data automatically. Compare to LOUVAIN, Label Propagation has lower time complexity. + +References: https://en.wikipedia.org/wiki/Label_propagation_algorithm + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| data | GraphData | true | Graph data | +| directed | Boolean | false | Whether it is a directed graph, false by default | +| weightPropertyName | String | false | The property name of the edge weight, `'weight' by default. If there is no weight in each edge, every edge has unit weight | +| maxIteration | Number | false | Max iteration number, 10000 by default | + +**Return** + +Returns the clustered data with `clusters` and `clusterEdges` arrays. And each node data in the input `data` will be assigned with corresponding `clusterId`. The type of the return value is: + +```typescript +interface ClusterData { + clusters: { + // Clusters array + id: string; // the ID of a cluster + nodes: NodeConfig[]; // the nodes in the cluster + }[]; + clusterEdges: { + // The edges between clusters + source: string; // source cluster ID + target: string; // target cluster ID + count: number; // the real edges number of this cluster edge + }[]; +} +``` + +Example of the return value: + +```javascript +{ + clusters: [ + {id: 'cluster1', nodes: [ {id: 'node1', clusterId: 'cluster1'}, {id: 'node2', clusterId: 'cluster1'} ]}, + {id: 'cluster2', nodes: [ {id: 'node3', clusterId: 'cluster2'} ]}, + ], + clusterEdges: [ + {source: 'cluster1', target: 'cluster2', count: 10}, + {source: 'cluster1', target: 'cluster1', count: 3}, + ] +} +``` + +**Usage** + +```javascript +import G6, { Algorithm } from '@antv/g6'; +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); + +const data = { + nodes: [ + { id: 'A' }, + { id: 'B' }, + { id: 'C' }, + { id: 'D' }, + { id: 'E' }, + { id: 'F' }, + { id: 'G' }, + ], + edges: [ + { source: 'A', target: 'B' }, + { source: 'B', target: 'C' }, + { source: 'A', target: 'C' }, + { source: 'D', arget: 'A' }, + { source: 'D', target: 'E' }, + { source: 'E', target: 'F' }, + ], +}; + +graph.data(data); +graph.render(); + +const { labelPropagation } = Algorithm; + +// result includes clusters array and clusterEdges array. Each node in the data will be assigned with corresponding clusterId +let result = labelPropagation(data); +``` + +### louvain + +_Supported after G6 4.0_ LOUVAIN auto clustering algorithm cluster the nodes according to the edge density between nodes. Compare to Label Propagation, LOUVAIN is more accurate. + +References: https://en.wikipedia.org/wiki/Louvain_method + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| data | GraphData | true | Graph data | +| directed | Boolean | false | Whether it is a directed graph, false by default | +| weightPropertyName | String | false | The property name of the edge weight, `'weight' by default. If there is no weight in each edge, every edge has unit weight | +| threshold | Number | false | The convergence threshold, 0.0001 by default | + +**Return** + +Returns the clustered data with `clusters` and `clusterEdges` arrays. And each node data in the input `data` will be assigned with corresponding `clusterId`. The type of the return value is: + +```typescript +interface ClusterData { + clusters: { + // Clusters array + id: string; // the ID of a cluster + nodes: NodeConfig[]; // the nodes in the cluster + sumTot?: number; // The number of edges in the cluster + }[]; + clusterEdges: { + // The edges between clusters + source: string; // source cluster ID + target: string; // target cluster ID + count: number; // the real edges number of this cluster edge + }[]; +} +``` + +Example of the return value: + +```javascript +{ + clusters: [ + {id: 'cluster1', sumTot: 8, nodes: [ {id: 'node1', clusterId: 'cluster1'}, {id: 'node2', clusterId: 'cluster1'} ]}, + {id: 'cluster2', sumTot: 15, nodes: [ {id: 'node3', clusterId: 'cluster2'} ]}, + ], + clusterEdges: [ + {source: 'cluster1', target: 'cluster2', count: 10}, + {source: 'cluster1', target: 'cluster1', count: 3}, + ] +} +``` + +**Usage** + +```javascript +import G6, { Algorithm } from '@antv/g6'; +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); + +const data = { + nodes: [ + { id: 'A' }, + { id: 'B' }, + { id: 'C' }, + { id: 'D' }, + { id: 'E' }, + { id: 'F' }, + { id: 'G' }, + ], + edges: [ + { source: 'A', target: 'B' }, + { source: 'B', target: 'C' }, + { source: 'A', target: 'C' }, + { source: 'D', arget: 'A' }, + { source: 'D', target: 'E' }, + { source: 'E', target: 'F' }, + ], +}; + +graph.data(data); +graph.render(); + +const { louvain } = Algorithm; + +// result includes clusters array and clusterEdges array. Each node in the data will be assigned with corresponding clusterId +let result = louvain(data); +``` + +### detectDirectedCycle + +In a given directed graph, check whether a ring is included. If at least one ring is included in the given graph, the first ring included is returned. Returns `null` if there is no cycle in the graph. + +References: + +- [detect-cycle-in-a-graph](https://www.geeksforgeeks.org/detect-cycle-in-a-graph/) + +- [detect-cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) + +**Parameters** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | ----------------- | +| graphData | GraphData | true | Graph data in G6 [data format](/en/docs/manual/tutorial/elements#data-structure). Note that it should be assigned with graph instance to this parameter instead before v4.1.0 | + +**Return** + +Returns the detected cycle. Returns `null` if there is no cycle. + +**Usage** + +```javascript +import G6, { Algorithm } from '@antv/g6'; +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); + +const data = { + nodes: [ + { id: 'A' }, + { id: 'B' }, + { id: 'C' }, + { id: 'D' }, + { id: 'E' }, + { id: 'F' }, + { id: 'G' }, + ], + edges: [ + { source: 'A', target: 'B' }, + { source: 'B', target: 'C' }, + { source: 'A', target: 'C' }, + { source: 'D', arget: 'A' }, + { source: 'D', target: 'E' }, + { source: 'E', target: 'F' }, + ], +}; + +graph.data(data); +graph.render(); + +const { detectDirectedCycle } = Algorithm; + +// There is no cycle in the graph, the result is null +let result = detectDirectedCycle(data); + +// There will be a cycle after adding edge F->D +data.edges.push({ + source: 'F', + target: 'D', +}); + +graph.changeData(data); + +// Returns: +/** +* { + D: Node, + F: Node, + E: Node, + } +*/ +result = detectDirectedCycle(data); +``` + +### detectAllCycles(graphData, directed, nodeIds, include) + +Find all simple cycles (elementary circuits) of a directed graph, and for undirected graph, find a list of cycles which form a [basis for cycles](https://en.wikipedia.org/wiki/Cycle_basis) of graph. + +References: + +- [Detect all of the cycles in an undirected graph.](https://www.geeksforgeeks.org/print-all-the-cycles-in-an-undirected-graph/) + +- Detect all of the cycles in a directed graph: [Johnson's algorithm ](https://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF). + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| graphData | GraphData | true | Graph data in G6 [data format](/en/docs/manual/tutorial/elements#data-structure). Note that it should be assigned with graph instance to this parameter instead before v4.1.0 | +| directed | boolean | false | Whether the graph is directed, use the value of `graph.get('directed')` by default. | | +| nodeIds | string[] | false | The nodes that should be included in or excluded from the cycles. If not configured, return all of the cycles.| | +| include | boolean | false | If it is `true`, the returned cycles shuld include one of the nodes in `nodeIds`, otherwise the cycles should not have any nodes in `nodeIds`. `true` by default.| | + +**Return** + +- Type of return value: [{[key: string]: Node}] +- Return a list of cyles. Each cycle is an object, whose key is a node ID and whose value is its next node in the cycle. + +**Usage** + +```javascript +const { detectAllCycles } = Algorithm; + +const allCycles = detectAllCycles(data, true); + +// Find all cycles that includes node B +const allCycleIncludeB = detectAllCycles(data, true, ['B']); + +// Find all cycles that does not includes node B +const allCycleExcludeB = detectAllCycles(data, false, ['B'], false); +``` + +### findShortestPath(graphData, start, end, directed, weightPropertyName) + +Compute the shortest path between two nodes in the graph. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| graphData | GraphData | true | Graph data in G6 [data format](/en/docs/manual/tutorial/elements#data-structure). Note that it should be assigned with graph instance to this parameter instead before v4.1.0 | +| start | INode / string | true | G6 Node Instance or node ID, indicating the start of the path | | +| end | INode / string | true | G6 Node Instance or node ID, indicating the end of the path | | +| directed | boolean | false | Whether the graph is directed, use the value of `graph.get('directed')` by default. | | +| weightPropertyName | string | false | Configure the edge property as the edge weight. If not configured, every edge has weight 1.| | + +**Return** + +- Type of return value: Object, + +``` + { + length: number, // the length of the path + path: string[], // the node IDs that form the path + allPath: string[][] // all the shortest path from the start to the end +} +``` + +**Usage** + +```javascript +const data = { + nodes: [ + { + id: 'A', + label: 'A', + }, + { + id: 'B', + label: 'B', + }, + { + id: 'C', + label: 'C', + }, + { + id: 'D', + label: 'D', + }, + { + id: 'E', + label: 'E', + }, + { + id: 'F', + label: 'F', + }, + { + id: 'G', + label: 'G', + }, + { + id: 'H', + label: 'H', + }, + ], + edges: [ + { + source: 'A', + target: 'B', + }, + { + source: 'B', + target: 'C', + }, + { + source: 'C', + target: 'G', + }, + { + source: 'A', + target: 'D', + }, + { + source: 'A', + target: 'E', + }, + { + source: 'E', + target: 'F', + }, + { + source: 'F', + target: 'D', + }, + { + source: 'D', + target: 'E', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); +graph.data(data); +graph.render(); + +const { findShortestPath } = Algorithm; +// Find the shortest path between node A and node C in this undirected graph +const { length, path, allPath } = findShortestPath(data, 'A', 'C'); +console.log(length, path); +// Expected output: 2, ['A', 'B', 'C'] +``` + +### findAllPath(graphData, start, end, directed) + +Find all paths between two nodes in the graph. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| graphData | GraphData | true | Graph data in G6 [data format](/en/docs/manual/tutorial/elements#data-structure). . Note that it should be assigned with graph instance to this parameter instead before v4.1.0 | +| start | INode / string | true | G6 Node Instance or node ID, indicating the start of the path | | +| end | INode / string | true | G6 Node Instance or node ID, indicating the end of the path | | +| directed | boolean | false | Whether the graph is directed, use the value of `graph.get('directed')` by default. | | + +**Return** + +- Type of return value: string[][] +- Return a list of paths, in which each path is an array of node IDs. + +**Usage** + +```javascript +const data = { + nodes: [ + { + id: 'A', + label: 'A', + }, + { + id: 'B', + label: 'B', + }, + { + id: 'C', + label: 'C', + }, + { + id: 'D', + label: 'D', + }, + { + id: 'E', + label: 'E', + }, + { + id: 'F', + label: 'F', + }, + { + id: 'G', + label: 'G', + }, + { + id: 'H', + label: 'H', + }, + ], + edges: [ + { + source: 'A', + target: 'B', + }, + { + source: 'B', + target: 'C', + }, + { + source: 'C', + target: 'G', + }, + { + source: 'A', + target: 'D', + }, + { + source: 'A', + target: 'E', + }, + { + source: 'E', + target: 'F', + }, + { + source: 'F', + target: 'D', + }, + { + source: 'D', + target: 'E', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); +graph.data(data); +graph.render(); + +const { findAllPath } = Algorithm; +const allPath = findAllPath(data, 'A', 'E'); +console.log(allPath); +// Expected output: [['A', 'D', 'F', 'E'], ['A', 'D', 'E'], ['A', 'E']] +``` + +### getConnectedComponents + +Find the connect component of the graph. In the case of a directed graph, the strongly connected components are returned. + +Translated with www.DeepL.com/Translator (free version) + +Reference: + +- Detect the strongly connected components in a directed graph: [Tarjan's Algorithm](http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) + + **Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| graphData | GraphData | true | Graph data in G6 [data format](/en/docs/manual/tutorial/elements#data-structure). . Note that it should be assigned with graph instance to this parameter instead before v4.1.0 | +| directed | boolean | false | Whether the graph is directed, use the value of `graph.get('directed')` by default. | | + +**Return** + +- Type of return value: INode[][] +- Return a list of connected components or strongly-connect components. Each component is a list of node instances. + +**Usage** + +```javascript +const data = { + nodes: [ + { + id: 'A', + }, + { + id: 'B', + }, + { + id: 'C', + }, + { + id: 'D', + }, + { + id: 'E', + }, + { + id: 'F', + }, + { + id: 'G', + }, + { + id: 'H', + }, + ], + edges: [ + { + source: 'A', + target: 'B', + }, + { + source: 'B', + target: 'C', + }, + { + source: 'A', + target: 'C', + }, + { + source: 'D', + target: 'A', + }, + { + source: 'D', + target: 'E', + }, + { + source: 'E', + target: 'F', + }, + { + source: 'F', + target: 'D', + }, + { + source: 'G', + target: 'H', + }, + { + source: 'H', + target: 'G', + }, + ], +}; +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 400, +}); +graph.data(data); +graph.render(); + +// Connected components +const components = getConnectedComponents(data, false); +components.forEach((component) => { + console.log(component.map((node) => node.get('id'))); +}); +// Expected output: ['A', 'B', 'C', 'D', 'E', 'F'], ['G', 'H'] + +// Strongly-connected components +const components2 = getConnectedComponents(data, true); +components2.forEach((component) => { + console.log(component.map((node) => node.get('id'))); +}); +// Expected output: ['A'], ['B'], ['C'], ['D', 'E', 'F'], ['G', 'H'] +``` + +### pageRank + +The PageRank algorithm assumes that the importance of the current node is determined by the importance of other nodes pointing to it, and that the more inbound links a node receives from other nodes, the more important it is. PageRank is determined by counting the number and quality of links to a node. + +Reference: + +- [PageRank](https://en.wikipedia.org/wiki/PageRank) + + **Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| graphData | GraphData | true | Graph data in G6 [data format](/en/docs/manual/tutorial/elements#data-structure). Note that it should be assigned with graph instance to this parameter instead before v4.1.0 | +| epsilon | number | false | The precision level used to identify whether the calculation is converged. | | +| linkProb | number | false | The the probability that the outgoing links will be visited next, 0.85 by default.| | + +**Return** + +- Type of return value: Object, {[key: string]: number} +- The PageRank value for each node. diff --git a/packages/site/docs/api/Algorithm.zh.md b/packages/site/docs/api/Algorithm.zh.md new file mode 100644 index 0000000000..0028540934 --- /dev/null +++ b/packages/site/docs/api/Algorithm.zh.md @@ -0,0 +1,938 @@ +--- +title: 图算法 Algorithm +order: 15 +--- + +图算法提供了一种最有效的分析关联数据的方法,它们描述了如何处理图以发现一些定性或者定量的结论。图算法基于图论,利用节点之间的关系来推断复杂系统的结构和变化。我们可以使用这些算法来发现隐藏的信息,验证业务假设,并对行为进行预测。 + +如果你对数据结构及算法感兴趣,可以通过 [javascript-algorithms](https://github.com/trekhleb/javascript-algorithms) 来进一步学习。 + +G6 从 V3.5 版本开始加入了图算法,在以后版本更新中,我们会不断丰富内置的算法。 + +### GADDI 图模式匹配 + +「v4.2.2」新特性 + +[GADDI 图模式匹配]()算法是一种支持结构和语义的图模式匹配算法,给定一个模式,可通过在算法在原数据上查找结果和语义相同、相似的结构。[DEMO](/zh/examples/algorithm/algoDemos#gaddi)。 + +img + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----------- | ------------------- | -------- | ------------------- | +| graphData | GraphData | true | 原图数据 | +| pattern | GraphData | true | 需要查找的模式图数据 | +| k | number | false | 匹配算法的参数,设置为 `undefined` 则自动设置 | +| length | number | false | 匹配算法的参数,设置为 `undefined` 则自动设置 | +| nodeLabelProp | number | false | 节点聚类信息的属性名,默认为 `'cluster'` | +| edgeLabelProp | number | false | 边聚类信息的属性名,默认为 `'cluster'` | + + +**用法** + +```javascript +import G6, { Algorithm } from '@antv/g6' +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500 +}) + +const graphData = { + nodes: [ + { id: 'A', cluster: 'nc1' }, + { id: 'B', cluster: 'nc1' }, + { id: 'C', cluster: 'nc2' }, + { id: 'D', cluster: 'nc1' }, + { id: 'E', cluster: 'nc3' }, + ], + edges: [ + { source: 'A', target: 'B', cluster: 'ec1' }, + { source: 'B', target: 'C', cluster: 'ec2' }, + { source: 'A', target: 'D', cluster: 'ec1' }, + { source: 'A', target: 'E', cluster: 'ec2' }, + ] +} + +graph.data(data) +graph.render() + +const { GADDI } = Algorithm; +const patternData = { + nodes: [ + { id: 'pn1', cluster: 'nc1' }, + { id: 'pn2', cluster: 'nc1' }, + { id: 'pn3', cluster: 'nc3' }, + ], + edges: [ + { source: 'pn1', target: 'pn2', cluster: 'ec1' }, + { source: 'pn1', target: 'pn3', cluster: 'ec2' }, + ] +} +const resultMatches = GADDI(graphData, patternData, true, undefined, undefined, 'cluster', 'cluster'); + +console.log(resultMatches); + // output: + // [{ + // nodes: [ + // { id: 'A', cluster: 'nc1' }, + // { id: 'B', cluster: 'nc1' }, + // { id: 'E', cluster: 'nc3' },], + // edges: [ + // { source: 'A', target: 'B', cluster: 'ec1' }, + // { source: 'A', target: 'E', cluster: 'ec2' } + // ] + // }] +``` + +### depthFirstSearch + +[深度优先搜索](https://zh.wikipedia.org/wiki/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2)(Depth First Search,简称 DFS)算法是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。 + +img + +[图片来源](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/depth-first-search) + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----------- | ------------------- | -------- | ------------------- | +| graphData | GraphData | true | 图数据,满足 G6 [数据格式](/zh/docs/manual/getting-started#step-2-数据准备)。注意,4.1 以前的版本该参数请传入图实例 | +| startNodeId | string | true | 开始访问的节点的 ID | +| callbacks | IAlgorithmCallbacks | false | 遍历的回调函数 | + +**用法** + +```javascript +import G6, { Algorithm } from '@antv/g6' +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500 +}) + +const data = { + nodes: [ + { + id: 'A' + }, + { + id: 'B' + }, + { + id: 'C' + }, + { + id: 'D' + }, + { + id: 'E' + }, + { + id: 'F' + }, + { + id: 'G' + }, + ], + edges: [ + { + source: 'A', + target: 'B' + }, + { + source: 'B', + target: 'C' + }, + { + source: 'C', + target: 'G' + }, + { + source: 'A', + target: 'D' + }, + { + source: 'A', + target: 'E' + }, + { + source: 'E', + target: 'F' + }, + { + source: 'F', + target: 'D' + }, + { + source: 'D', + target: 'G' + }, + ] +} + +graph.data(data) +graph.render() + +const { depthFirstSearch } = Algorithm +depthFirstSearch(data, 'A', { + enter: ({ current, previous }) => { + // 开始遍历点的回调 + }, + leave: ({ current, previous }) => { + // 遍历完节点的回调 + }, +}) +``` + +### breadthFirstSearch + +[广度优先搜索](https://zh.wikipedia.org/zh/%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2)算法(Breadth First Search,简称 BFS),又译作宽度优先搜索,或横向优先搜索,是一种图搜索算法。简单的说,BFS 是从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。广度优先搜索的实现一般采用 open-closed 表。 + +img + +[图片来源](https://camo.githubusercontent.com/b8073f26dfdf1644e8a92312fff100341987a8f5/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f352f35642f427265616474682d46697273742d5365617263682d416c676f726974686d2e676966) + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----------------- | ------------------- | -------- | ------------------- | +| graphData | GraphData | true | 图数据,满足 G6 [数据格式](/zh/docs/manual/getting-started#step-2-数据准备)。注意,4.1 以前的版本该参数请传入图实例 | +| startNodeId | string | true | 开始访问的节点的 ID | +| originalCallbacks | IAlgorithmCallbacks | false | 遍历的回调函数 | + +**用法** + +```javascript +import G6, { Algorithm } from '@antv/g6' +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500 +}) + +const data = { + nodes: [ + { + id: 'A' + }, + { + id: 'B' + }, + { + id: 'C' + }, + { + id: 'D' + }, + { + id: 'E' + }, + { + id: 'F' + }, + { + id: 'G' + }, + ], + edges: [ + { + source: 'A', + target: 'B' + }, + { + source: 'B', + target: 'C' + }, + { + source: 'C', + target: 'G' + }, + { + source: 'A', + target: 'D' + }, + { + source: 'A', + target: 'E' + }, + { + source: 'E', + target: 'F' + }, + { + source: 'F', + target: 'D' + }, + { + source: 'D', + target: 'G' + }, + ] +} + +graph.data(data) +graph.render() + +const { breadthFirstSearch } = Algorithm +breadthFirstSearch(data, 'A', { + enter: ({ current, previous }) => { + // 开始遍历点的回调 + }, + leave: ({ current, previous }) => { + // 遍历完节点的回调 + }, +}) +``` + +### labelPropagation + +_G6 4.0 起支持_ 标签传播算法,自动为数据聚类。优势:速度较 LOUVAIN 快。 + +参考资料:https://en.wikipedia.org/wiki/Label_propagation_algorithm + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| data | GraphData | true | 图数据 | +| directed | Boolean | false | 是否是有向图,默认为 false | +| weightPropertyName | String | false | 边权重的属名称,默认为 `'weight',若数据中没有权重,则默认每条边权重为 1 | +| maxIteration | Number | false | 最大迭代次数,默认为 1000 | + +**返回值** + +返回聚合数据,并为输入的 `data` 中的每个节点数据加上 `clusterId` 字段。聚合数据 `ClusterData` 类型如下: + +```typescript +interface ClusterData { + clusters: { + // 聚类数组 + id: string; // 聚类 Id + nodes: NodeConfig[]; // 该聚类包含的节点 + }[]; + clusterEdges: { + // 聚类与聚类之间的边数组 + source: string; // 起点聚类 id + target: string; // 终点聚类 id + count: number; // 该边所包含的真实边个数 + }[]; +} +``` + +返回值示例: + +```javascript +{ + clusters: [ + {id: 'cluster1', nodes: [ {id: 'node1', clusterId: 'cluster1'}, {id: 'node2', clusterId: 'cluster1'} ]}, + {id: 'cluster2', nodes: [ {id: 'node3', clusterId: 'cluster2'} ]}, + ], + clusterEdges: [ + {source: 'cluster1', target: 'cluster2', count: 10}, + {source: 'cluster1', target: 'cluster1', count: 3}, + ] +} +``` + +**用法** + +```javascript +import G6, { Algorithm } from '@antv/g6'; +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); + +const data = { + nodes: [ + { id: 'A' }, + { id: 'B' }, + { id: 'C' }, + { id: 'D' }, + { id: 'E' }, + { id: 'F' }, + { id: 'G' }, + ], + edges: [ + { source: 'A', target: 'B' }, + { source: 'B', target: 'C' }, + { source: 'A', target: 'C' }, + { source: 'D', arget: 'A' }, + { source: 'D', target: 'E' }, + { source: 'E', target: 'F' }, + ], +}; + +graph.data(data); +graph.render(); + +const { labelPropagation } = Algorithm; + +// result 中包含 clusters 与 clusterEdges 数组。data 中的每个节点数据将带有 clusterId 字段 +let result = labelPropagation(data); +``` + +### louvain + +_G6 4.0 起支持_ LOUVAIN 自动聚类算法。优势:根据节点间的紧密程度计算,较之于 Label Propagation 更准确。 + +参考资料:https://en.wikipedia.org/wiki/Louvain_method + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| data | GraphData | true | 图数据 | +| directed | Boolean | false | 是否是有向图,默认为 false | +| weightPropertyName | String | false | 边权重的属名称,默认为 `'weight',若数据中没有权重,则默认每条边权重为 1 | +| threshold | Number | false | 停止迭代的阈值,默认为 0.0001 | + +**返回值** + +返回聚合数据,并为输入的 `data` 中的每个节点数据加上 `clusterId` 字段。聚合数据 `ClusterData` 类型如下: + +```typescript +interface ClusterData { + clusters: { + // 聚类数组 + id: string; // 聚类 Id + nodes: NodeConfig[]; // 该聚类包含的节点 + sumTot?: number; // 该聚类内部边总数 + }[]; + clusterEdges: { + // 聚类与聚类之间的边数组 + source: string; // 起点聚类 id + target: string; // 终点聚类 id + count: number; // 该边所包含的真实边个数 + }[]; +} +``` + +返回值示例: + +```javascript +{ + clusters: [ + {id: 'cluster1', sumTot: 8, nodes: [ {id: 'node1', clusterId: 'cluster1'}, {id: 'node2', clusterId: 'cluster1'} ]}, + {id: 'cluster2', sumTot: 15, nodes: [ {id: 'node3', clusterId: 'cluster2'} ]}, + ], + clusterEdges: [ + {source: 'cluster1', target: 'cluster2', count: 10}, + {source: 'cluster1', target: 'cluster1', count: 3}, + ] +} +``` + +**用法** + +```javascript +import G6, { Algorithm } from '@antv/g6'; +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); + +const data = { + nodes: [ + { id: 'A' }, + { id: 'B' }, + { id: 'C' }, + { id: 'D' }, + { id: 'E' }, + { id: 'F' }, + { id: 'G' }, + ], + edges: [ + { source: 'A', target: 'B' }, + { source: 'B', target: 'C' }, + { source: 'A', target: 'C' }, + { source: 'D', arget: 'A' }, + { source: 'D', target: 'E' }, + { source: 'E', target: 'F' }, + ], +}; + +graph.data(data); +graph.render(); + +const { louvain } = Algorithm; + +// result 中包含 clusters 与 clusterEdges 数组。data 中的每个节点数据将带有 clusterId 字段 +let result = louvain(data); +``` + +### detectDirectedCycle + +在给定的有向图中,检查是否包括圈。如果给定的图中至少包括一个圈,则返回包括的第一个圈,否则返回 null。 + +参考资料: + +- [detect-cycle-in-a-graph](https://www.geeksforgeeks.org/detect-cycle-in-a-graph/) + +- [detect-cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | ------------- | +| graphData | GraphData | true | 图数据,满足 G6 [数据格式](/zh/docs/manual/getting-started#step-2-数据准备)。注意,4.1 以前的版本该参数请传入图实例| + +**返回值** + +返回检测到的圈,否则返回 null。 + +**用法** + +```javascript +import G6, { Algorithm } from '@antv/g6'; +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); + +const data = { + nodes: [ + { id: 'A' }, + { id: 'B' }, + { id: 'C' }, + { id: 'D' }, + { id: 'E' }, + { id: 'F' }, + { id: 'G' }, + ], + edges: [ + { source: 'A', target: 'B' }, + { source: 'B', target: 'C' }, + { source: 'A', target: 'C' }, + { source: 'D', arget: 'A' }, + { source: 'D', target: 'E' }, + { source: 'E', target: 'F' }, + ], +}; + +graph.data(data); +graph.render(); + +const { detectDirectedCycle } = Algorithm; + +// 此时图中没有环,result 为 null +let result = detectDirectedCycle(data); + +// 当数据中加入 F->D 这条边后,图中有一个环 +data.edges.push({ + source: 'F', + target: 'D', +}); + +graph.changeData(data); + +// 返回数据 +/** +* { + D: Node, + F: Node, + E: Node, + } +*/ +result = detectDirectedCycle(data); +``` + +### detectAllCycles(graphData, directed, nodeIds, include) + +提供支持寻找图中所有环路的函数。对有向图来说返回所有简单环,简单环是指路径上的节点都只出现一次的闭合路径;对于无向图来说,返回一组完备的[基本环](https://en.wikipedia.org/wiki/Cycle_basis)。 + +参考资料: + +- [检测无向图中的所有环](https://www.geeksforgeeks.org/print-all-the-cycles-in-an-undirected-graph/) + +- 检测所有有向图中的简单环: [Johnson's algorithm ](https://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF) + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| graphData | GraphData | true | 图数据,满足 G6 [数据格式](/zh/docs/manual/getting-started#step-2-数据准备)。注意,4.1 以前的版本该参数请传入图实例 | +| directed | boolean | false | 是否考虑边的方向性,若不指定,则取图的 `directed` 属性 | | +| nodeIds | string[] | false | 需包含或排除的节点 ID 的数组,若不指定,则返回图中所有的圈 | | +| include | boolean | false | 若为 `true`, 则返回包含参数 `nodeIds` 中指定的节点的圈,否则,返回所有不包含 `nodeIds` 中指定的节点的圈。默认为 `true` | | + +**返回值** + +- 返回值类型:[{[key: string]: Node}] +- 返回一个数组表示检测到的所有符合条件的圈,每个环用一个 Object 表示,其中 key 为节点 id,value 为该节点在环中指向的下一个节点。 + +**用法** + +```javascript +const { detectAllCycles } = Algorithm; + +// 检测有向图中的所有简单环 +const allCycles = detectAllCycles(data, true); + +// 检测有向图中包含节点 B 的所有简单环 +const allCycleIncludeB = detectAllCycles(data, true, ['B']); + +// 检测无向图中所有不包含节点 B 的所有基本环 +const allCycleExcludeB = detectAllCycles(data, false, ['B'], false); +``` + +### findShortestPath(graphData, start, end, directed, weightPropertyName) + +查找两点之间的最短路径。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| graphData | GraphData | true | 图数据,满足 G6 [数据格式](/zh/docs/manual/getting-started#step-2-数据准备)。注意,4.1 以前的版本该参数请传入图实例 | +| start | INode / string | true | G6 Node 实例或 ID,路径起始点 | | +| end | INode / string | true | G6 Node 实例或 ID,路径终点 | | +| directed | boolean | false | 是否考虑边的方向性,若不指定,则取图的 `directed` 属性 | | +| weightPropertyName | string | false | 边的权重属性字段名,若不指定,则认为所有边权重相同 | | + +**返回值** + +- 返回值类型:Object, + +``` + { + length: number, // 最短路径长度 + path: string[], + allPath: string[][] // start 到 end 的所有的最短路径 +} +``` + +- 返回的对象中,length 属性代表最短路径的长度,path 属性为构成一条最短路径的节点数组。 + +**用法** + +```javascript +const data = { + nodes: [ + { + id: 'A', + label: 'A', + }, + { + id: 'B', + label: 'B', + }, + { + id: 'C', + label: 'C', + }, + { + id: 'D', + label: 'D', + }, + { + id: 'E', + label: 'E', + }, + { + id: 'F', + label: 'F', + }, + { + id: 'G', + label: 'G', + }, + { + id: 'H', + label: 'H', + }, + ], + edges: [ + { + source: 'A', + target: 'B', + }, + { + source: 'B', + target: 'C', + }, + { + source: 'C', + target: 'G', + }, + { + source: 'A', + target: 'D', + }, + { + source: 'A', + target: 'E', + }, + { + source: 'E', + target: 'F', + }, + { + source: 'F', + target: 'D', + }, + { + source: 'D', + target: 'E', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); +graph.data(data); +graph.render(); + +const { findShortestPath } = Algorithm; +// 不考虑边的方向性,查找节点 A 和 节点 C 之间的最短路径 +const { length, path, allPath } = findShortestPath(data, 'A', 'C'); +console.log(length, path); +// 期望输出:2, ['A', 'B', 'C'] +``` + +### findAllPath(graphData, start, end, directed) + +查找两点之间的所有路径。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| -------- | -------------- | -------- | --------------------------------------------------------- | +| graphData | GraphData | true | 图数据,满足 G6 [数据格式](/zh/docs/manual/getting-started#step-2-数据准备)。注意,4.1 以前的版本该参数请传入图实例 | +| start | INode / string | true | G6 Node 实例或 ID,路径起始点 | | +| end | INode / string | true | G6 Node 实例或 ID,路径终点 | | +| directed | boolean | false | 是否考虑边的方向性,若不指定,则取图的 `directed` 属性 | | + +**返回值** + +- 返回值类型:string[][] +- 返回包含两个节点之间所有路径的数组,每条路径由节点 ID 数组表示 + +**用法** + +```javascript +const data = { + nodes: [ + { + id: 'A', + label: 'A', + }, + { + id: 'B', + label: 'B', + }, + { + id: 'C', + label: 'C', + }, + { + id: 'D', + label: 'D', + }, + { + id: 'E', + label: 'E', + }, + { + id: 'F', + label: 'F', + }, + { + id: 'G', + label: 'G', + }, + { + id: 'H', + label: 'H', + }, + ], + edges: [ + { + source: 'A', + target: 'B', + }, + { + source: 'B', + target: 'C', + }, + { + source: 'C', + target: 'G', + }, + { + source: 'A', + target: 'D', + }, + { + source: 'A', + target: 'E', + }, + { + source: 'E', + target: 'F', + }, + { + source: 'F', + target: 'D', + }, + { + source: 'D', + target: 'E', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, +}); +graph.data(data); +graph.render(); + +const { findAllPath } = Algorithm; +const allPath = findAllPath(data, 'A', 'E'); +console.log(allPath); +// 期望输出值:[['A', 'D', 'F', 'E'], ['A', 'D', 'E'], ['A', 'E']] +``` + +### getConnectedComponents + +返回图中的连通分量。若为无向图,连通分量指图中的极大连通子图,连通子图中任何两个顶点之间通过路径相互连接;若为有向图,则返回所有强连通分量,强连通分量指有向图中的极大强连通子图,强连通子图中任何两个节点之间都存在一条可达到彼此的有向路径。 + +参考资料: + +- 检测有向图中的强连通分量:[Tarjan's Algorithm](http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) + + **参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| -------- | ------- | -------- | --------------------------------------------------------- | +| graphData | GraphData | true | 图数据,满足 G6 [数据格式](/zh/docs/manual/getting-started#step-2-数据准备)。注意,4.1 以前的版本该参数请传入图实例 | +| directed | boolean | false | 是否考虑边的方向性,若不指定,则取图的 `directed` 属性 | | + +**返回值** + +- 返回值类型:INode[][] +- 返回一个数组表示检测到的所有连通分量,每个连通分量为节点数组。 + +**用法** + +```javascript +const data = { + nodes: [ + { + id: 'A', + }, + { + id: 'B', + }, + { + id: 'C', + }, + { + id: 'D', + }, + { + id: 'E', + }, + { + id: 'F', + }, + { + id: 'G', + }, + { + id: 'H', + }, + ], + edges: [ + { + source: 'A', + target: 'B', + }, + { + source: 'B', + target: 'C', + }, + { + source: 'A', + target: 'C', + }, + { + source: 'D', + target: 'A', + }, + { + source: 'D', + target: 'E', + }, + { + source: 'E', + target: 'F', + }, + { + source: 'F', + target: 'D', + }, + { + source: 'G', + target: 'H', + }, + { + source: 'H', + target: 'G', + }, + ], +}; +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 400, +}); +graph.data(data); +graph.render(); + +// 图中的连通分量 +const components = getConnectedComponents(data, false); +components.forEach((component) => { + console.log(component.map((node) => node.get('id'))); +}); +// 期望输出结果:['A', 'B', 'C', 'D', 'E', 'F'], ['G', 'H'] + +// 有向图中的强连通分量 +const components2 = getConnectedComponents(data, true); +components2.forEach((component) => { + console.log(component.map((node) => node.get('id'))); +}); +// 期望输出结果:['A'], ['B'], ['C'], ['D', 'E', 'F'], ['G', 'H'] +``` + +### pageRank + +PageRank 可以用来度量网络中节点的重要性,最初用于标识网页的重要性,对网页进行排序。PageRank 算法假设当前节点的重要性是由指向它的其他节点的重要性决定的,一个节点接收到的来自其他节点的入链 (inbound) 越多,则越重要,每个入链的权重由提供入链的节点的重要性决定。 因此 PageRank 除了考虑到入链数量,还参考了入链“质量”。PageRank 通过迭代递归计算来更新每个节点的得分,直到得分稳定为止。 + +参考资料: + +- [PageRank](https://en.wikipedia.org/wiki/PageRank) + + **参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| graphData | GraphData | true | 图数据,满足 G6 [数据格式](/zh/docs/manual/getting-started#step-2-数据准备)。注意,4.1 以前的版本该参数请传入图实例 | +| epsilon | number | false | 判断 PageRank 得分是否稳定的精度值,默认 0.000001 | | +| linkProb | number | false | 阻尼系数(dumping factor),指任意时刻,用户访问到某节点后继续访问该节点指向的节点的概率,默认 0.85。 | | + +**返回值** + +- 返回值类型:Object, {[key: string]: number} +- 返回一个对象,表示节点 ID 对应的该节点的 PageRank 值。 diff --git a/packages/site/docs/api/Behavior.en.md b/packages/site/docs/api/Behavior.en.md new file mode 100644 index 0000000000..abd571841f --- /dev/null +++ b/packages/site/docs/api/Behavior.en.md @@ -0,0 +1,202 @@ +--- +title: Behavior and RegisterBehavior +order: 13 +--- + +Behavior is the compound interactions in G6. In general, a Behavior includes one or more event listeners and a set of item operations. + +By default, Behavior has three callbacks: `shouldBegin`, `shouldUpdate`, and `shouldEnd`, representing the beginning of the behavior, whether to update the items, the ending of the behavior respectively. If they return `false`, the default behavior will be prevented. + +This document is going to introduce how to customize a behavior. The infomation about the built-in behaviors can be found in the [Built-in Behaviors](/en/docs/manual/middle/states/defaultBehavior). When the [built-in Behaviors](/en/docs/manual/middle/states/defaultBehavior) cannot satisfy your requirments, custom a type of Behavior by `G6.registerBehavior(behaviorName, behavior)`. See [Behavior API](/en/docs/api/Behavior) for detail. + +```ts +// highlight-start +G6.registerBehavior(behaviorName: string, behavior: BehaviorOption) +// highlight-end + +// Custom a type of Behavior +G6.registerBehavior('behaviorName', { + // Bind the event and its callback + getEvents() { + return { + 'node:click': 'onClick', + mousemove: 'onMousemove', + 'edge:click': 'onEdgeClick', + }; + }, + /** + * Handle the callback for node:click + * @override + * @param {Object} evt The handler + */ + onClick(evt) { + const node = evt.item; + const graph = this.graph; + const point = { x: evt.x, y: evt.y }; + const model = node.getModel(); + // TODO + }, + /** + * Handle the callback for mousemove + * @override + * @param {Object} evt The handler + */ + onMousemove(evt) { + // TODO + }, + /** + * Handle the callback for :click + * @override + * @param {Object} evt The handler + */ + onEdgeClick(evt) { + // TODO + }, +}); +``` + +## Parameters + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| behaviorName | String | true | The name of custom Behavior. | +| behavior | BehaviorOption | true | The configurations of custom Behavior. For more information, please refer to [Behavior API](/en/docs/api/Behavior). | + +### BehaviorOption.getEvents() + +Define and handle events when user customize a Behavior. + +The usage of `getEvents()` can be refered to [Event](/en/docs/api/Event)。 + +**Usage** + +```javascript +G6.registerBehavior('behaviorName', { + getEvents() { + return { + 'node:click': 'onNodeClick', + 'edge:click': 'onEdgeClick', + 'mousemove': 'onMouseMove' + } + } +} +``` + +### BehaviorOption.onNodeClick(evt) + +`onNodeClick`, `onEdgeClick`, and `onMouseMove` are custom events for handling `node:click`, `edge:click`, and `mousemove`. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ----- | -------- | -------------------------------------------------------- | +| evt | Event | false | contains event handler, current target, and coordinates. | + +**The parameter `evt` contains:** + +| Name | Type | Description | +| ---------------- | ---------- | ------------------------------------------ | +| x | Number | x coordinate of view port. | +| y | Number | y coordinate of view port. | +| canvasX | Number | x coordinate of the canvas. | +| canvasY | Number | y coordinate of the canvas. | +| clientX | Number | x coordinate of the client / screen. | +| clientY | Number | y coordinate of the client / screen. | +| event | MouseEvent | Event handler. | +| target | Shape | The target. | +| type | String | Operation type. | +| currentTarget | Object | | +| item | Shape | The target item. | +| removed | Boolean | Whether the target is removed / destroyed. | +| timeStamp | Number | The time stamp. | +| bubbles | Boolean | Whether it is a bubbled event. | +| defaultPrevented | Boolean | Whether to prevent the default event. | +| cancelable | Boolean | Whether it is cancelable. | + +**Usage** + +```javascript +G6.registerBehavior('behaviorName', { + getEvents() { + return { + 'node:click': 'onNodeClick', + 'edge:click': 'onEdgeClick', + mousemove: 'onMouseMove', + }; + }, + onNodeClick(evt) { + // TODO + }, + onEdgeClick(evt) { + // TODO + }, + onMouseMove(evt) { + // TODO + }, +}); +``` + +### BehaviorOption.getDefaultCfg() + +Default configurations while customing a Behavior. The configurations will be mixed by the configurations from user. + +**Tips: This function is not required**. + +**Usage** + +```javascript +G6.registerBehavior('behaviorName', { + getDefaultCfg() { + return { + trigger: 'click' // mouseneter or click + } + } +} +``` + +### BehaviorOption.shouldBegin(evt, self) + +Whether to prevent the behavior. Return `true` by default, which means do not prevent the behavior. User should call it by themselves. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldBegin`. + +**Usage** + +```javascript +G6.registerBehavior('behaviorName', { + shouldBegin(evt, self) { + // Customize it according to your scenario + return true; + }, +}); +``` + +### BehaviorOption.shouldUpdate(evt, self) + +Whether to update the data and the view. Returns `true` by default, which means allow updating. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`. + +**Usage** + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + modes: { + default: [ + 'drag-canvas', + { + type: 'self-behavior', + shouldUpdate: (e, self) => { + if (e.target.type !== 'text') { + return false; + } + return true; + }, + }, + ], + }, +}); +``` + +### BehaviorOption.shouldEnd(evt, self) + +Whether to end the behavior. Returns `true` by default. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldEnd`. diff --git a/packages/site/docs/api/Behavior.zh.md b/packages/site/docs/api/Behavior.zh.md new file mode 100644 index 0000000000..4bacb1bb4d --- /dev/null +++ b/packages/site/docs/api/Behavior.zh.md @@ -0,0 +1,202 @@ +--- +title: 复合交互及其自定义 Behavior +order: 13 +--- + +Behavior 指 G6 中的复合交互,一般 Behavior 包含一个或多个事件的监听与处理以及一系列对图中元素的操作。 + +Behavior 默认包含 `shouldBegin`,`shouldUpdate`,`shouldEnd` 三个回调,代表是否开始行为,是否更新元素,是否进行结束行为,当返回值为 `false` 时阻止默认行为。 + +所有内置 Behavior 及其参数参见 [内置的 Behavior 教程](/zh/docs/manual/middle/states/defaultBehavior)。当 [内置 Behavior](/zh/docs/manual/middle/states/defaultBehavior) 不能满足需求时,使用 `registerBehavior(behaviorName, behavior)` 方法注册自定义的交互行为。详见 [Behavior API](/zh/docs/api/Behavior)。本文将介绍如何自定义 Behavior。 + +```ts +// highlight-start +G6.registerBehavior(behaviorName: string, behavior: BehaviorOption) +// highlight-end + +// Custom a type of Behavior +G6.registerBehavior('behaviorName', { + // Bind the event and its callback + getEvents() { + return { + 'node:click': 'onClick', + mousemove: 'onMousemove', + 'edge:click': 'onEdgeClick', + }; + }, + /** + * Handle the callback for node:click + * @override + * @param {Object} evt The handler + */ + onClick(evt) { + const node = evt.item; + const graph = this.graph; + const point = { x: evt.x, y: evt.y }; + const model = node.getModel(); + // TODO + }, + /** + * Handle the callback for mousemove + * @override + * @param {Object} evt The handler + */ + onMousemove(evt) { + // TODO + }, + /** + * Handle the callback for :click + * @override + * @param {Object} evt The handler + */ + onEdgeClick(evt) { + // TODO + }, +}); +``` + +## 参数 + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| behaviorName | String | true | 自定义 Behavior 的名称。 | +| behavior | BehaviorOption | true | 自定义 behavior 时的配置项,配置项中包括的方法及作用具体请参考:[Behavior API](/zh/docs/api/Behavior)。 | + +### BehaviorOption.getEvents() + +自定义 Behavior 时,定义事件及处理事件的方法。 + +`getEvents()` 方法中可以使用的事件请参考[Event 文档](/zh/docs/api/Event)。 + +**用法** + +```javascript +G6.registerBehavior('behaviorName', { + getEvents() { + return { + 'node:click': 'onNodeClick', + 'edge:click': 'onEdgeClick', + 'mousemove': 'onMouseMove' + } + } +} +``` + +### BehaviorOption.onNodeClick(evt) + +`onNodeClick`、`onEdgeClick` 和 `onMouseMove` 都属于自定义方法,用于处理 `node:click`、`edge:click`、`mousemove` 事件。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ----- | -------- | -------------------------------------- | +| evt | Event | 否 | 包含事件句柄、当前操作对象及各坐标值等 | + +**参数 `evt` 包括以下属性:** + +| 名称 | 类型 | 描述 | +| ---------------- | ---------- | ------------------ | +| x | Number | 元素视口 x 坐标 | +| y | Number | 元素视口 y 坐标 | +| canvasX | Number | 元素 Canvas x 坐标 | +| canvasY | Number | 元素 Canvas y 坐标 | +| clientX | Number | 屏幕/页面 x 坐标 | +| clientY | Number | 屏幕/页面 y 坐标 | +| event | MouseEvent | 事件句柄 | +| target | Shape | 当前对象 | +| type | String | 操作类型 | +| currentTarget | Object | | +| item | Shape | 操作的目标元素 | +| removed | Boolean | 是否删除/销毁 | +| timeStamp | Number | 时间戳 | +| bubbles | Boolean | 是否支持事件冒泡 | +| defaultPrevented | Boolean | 是否阻止默认事件 | +| cancelable | Boolean | 是否取消 | + +**用法** + +```javascript +G6.registerBehavior('behaviorName', { + getEvents() { + return { + 'node:click': 'onNodeClick', + 'edge:click': 'onEdgeClick', + mousemove: 'onMouseMove', + }; + }, + onNodeClick(evt) { + // TODO + }, + onEdgeClick(evt) { + // TODO + }, + onMouseMove(evt) { + // TODO + }, +}); +``` + +### BehaviorOption.getDefaultCfg() + +定义自定义 Behavior 时的默认参数,会与用户传入的参数进行合并。 + +**提示:该方法是可选的**。 + +**用法** + +```javascript +G6.registerBehavior('behaviorName', { + getDefaultCfg() { + return { + trigger: 'click' // mouseneter or click + } + } +} +``` + +### BehaviorOption.shouldBegin(evt, self) + +是否阻止行为发生,默认返回 `true`,不阻止行为,需要在处理逻辑中自行调用。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldBegin` 中访问该实例。 + +**用法** + +```javascript +G6.registerBehavior('behaviorName', { + shouldBegin(evt, self) { + // 这里可以根据业务自定义 + return true + } +} +``` + +### BehaviorOption.shouldUpdate(evt, self) + +是否更新数据及更改视图,默认返回 `true`,允许更新,如果返回 `false`,则不更新数据和视图。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldUpdate` 中访问该实例。 + +**用法** + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + modes: { + default: [ + 'drag-canvas', + { + type: 'self-behavior', + shouldUpdate: (e, self) => { + if (e.target.type !== 'text') { + return false; + } + return true; + }, + }, + ], + }, +}); +``` + +### BehaviorOption.shouldEnd(evt, self) + +是否结束行为,默认返回 `true`。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldEnd` 中访问该实例。 diff --git a/packages/site/docs/api/Event.en.md b/packages/site/docs/api/Event.en.md new file mode 100644 index 0000000000..137d1acafc --- /dev/null +++ b/packages/site/docs/api/Event.en.md @@ -0,0 +1,389 @@ +--- +title: Event +order: 12 +--- + +The events in this chapter can be binded or unbinded to graph by [graph.on](/en/docs/api/graphFunc/on_off#graphoneventname-handler) and [graph.off](/en/docs/api/graphFunc/on_off#graphoffeventname-handler). + +The parameters of callbacks for common events, Node events, and Edge events are described in [Behavior API](/en/docs/api/Behavior). + +## Interaction Events + +Listen to the interaction events such as `click`, `mousemove` by the following way: + +```javascript +graph.on(eventName, evt => { + // some operations +}) +``` + +Where, the event object `evt` has the properties: + +- `type`: The type of the event +- `name`: The name of the event +- `x`: The x coordinate on the canvas +- `y`: The y coordinate on the canvas +- `clientX`: The x coordinate about the client +- `clientY`: The y coordinate about the client +- `canvasX`: The x coordinate about parent DOM of the canvas +- `canvasY`: The y coordinate about parent DOM of the canvas + +(The differences between x/y, clientX/clientY, and canvasX/canvasY can be found in [Coordinate Systems in G6](/en/docs/manual/advanced/coordinate-system)) + +- `item`: The item being manipulated, which can be a node, an edge, or a Combo) +- `target`: The target [Shape](/zh/docs/manual/middle/elements/shape/shape-keyshape) on the `item` being manupulated, or the canvas instance +- `bubbles`: Whether bubbles +- `defaultPrevented`: Whether prevent the original event +- `originalEvent`: The original client event object. where the `button` can be used to distinguish the left/middle/right button of the mouse on some events like `click` or `dblclick` +- `timeStamp`: The time stamp the event triggered +- `propagationStopped`: Wheher stop the propogation +- `propagationPath`: The triggering path + +`eventName` can be refered to the following parts. + +### Common Interaction Event + +| Event Name | Description | +| --- | --- | +| click | Activated by clicking the **left button** of mouse or Enter button. | +| dblclick | Activated by double clicking the **left button** of mouse. | +| mouseenter | Activated when mouse enters an item. **This is not a bubbled event**, which means this event will not be activated when the mouse moves to the descendant items. | +| mousemove | Activated while the mouse is moving inside an item. It cannot be activated by keyboard. | +| mouseout | Activated while the mouse moves out of an item. | +| mouseover | Activated when the mouse moves over an item. | +| mouseleave | Activated when the mouse leaves an item. **This is not a bubbled event**, which means this event will not be activated when the mouse leaves the descendant items. | +| mousedown | Activated when the left or right button is clicked down. It cannot be activated by keyboard. | +| mouseup | Activated when the left or right button is released. It cannot be activated by keyboard. | +| contextmenu | Open the context menu when user clicks the right button of mouse. [Demo](/en/examples/tool/contextMenu). | +| dragstart | Activated when user begins to drag. This event is applied on a dragged item. | +| drag | Activated during the dragging process. This event is applied on a dragged item. | +| dragend | Activated when user stops dragging. This event is applied on a dragged item. | +| dragenter | Activated when user drags an item into a target item. This event is applied on a dragged item. | +| dragleave | Activated when user drags an item out of a target item. This event is applied on the target item. | +| drop | Activated when user drops an item on a target item. This event is applied on the target item. | +| keydown | Activated when user presses down a button on keyboard. | +| keyup | Activated when user releases a button on keyboard. | +| wheel | Activated when user scroll the wheel. | +| touchstart | Activated when a finger touches the screen. If there are fingers on the screen already, it will be activated too. | +| touchmove | Activated during the processes of finger moving on the screen. Call `preventDefault()` to prevent scrolling. | +| touchend | Activated when a finger leaves the screen. | + +### Node Interaction Event + +| Event Name | Description | +| --- | --- | +| node:click | Activated when user clicks the **left button** of the mouse on the node. | +| node:dblclick | Activated when user double clicks the **left button** of the mouse on the node. | +| node:mouseenter | Activated when the mouse enters the node. | +| node:mousemove | Activated while the mouse is moving inside the node. It cannot be activated by keyboard. | +| node:mouseout | Activated while the mouse moves out of the node. | +| node:mouseover | Activated when the mouse moves over the node. | +| node:mouseleave | Activated when the mouse leaves the node. | +| node:mousedown | Activated when the left or right button is clicked down on the node. It cannot be activated by keyboard. | +| node:mouseup | Activated when the left or right button is released on the node. It cannot be activated by keyboard. | +| node:dragstart | Activated when user begins to drag the node. This event is applied on the dragged node. | +| node:drag | Activated during the dragging process on the node. This event is applied on the dragged node. | +| node:dragend | Activated when user stops dragging on the node. This event is applied on the dragged node. | +| node:dragenter | Activated when user drags an item into a target node item. This event is applied on the target node item. | +| node:dragleave | Activated when user drags an item out of a target node item. This event is applied on the target node item. | +| node:dragover | Activated when user drags an item over a target node item. This event is applied on the target node item | +| node:drop | Activated when user drops an item on a target item. This event is applied on the target item. | +| node:touchstart | On touch screen, this event is activated when user begin to touch the node | +| node:touchmove | On touch screen, this event is activated when user is touching the node | +| node:touchend | On touch screen, this event is activated when user finish touching the node | +| node:contextmenu | Open the context menu when user clicks the right button of mouse on the node. [Demo](/en/examples/tool/contextMenu). | + +### Edge Interaction Event + +| Event Name | Description | +| --- | --- | +| edge:click | Activated when user clicks the **left button** of the mouse on the edge. | +| edge:dblclick | Activated when user double clicks the **left button** of the mouse on the edge. | +| edge:mouseenter | Activated when the mouse enters the edge. | +| edge:mousemove | Activated while the mouse is moving inside the edge. It cannot be activated by keyboard. | +| edge:mouseout | Activated while the mouse moves out of the edge. | +| edge:mouseover | Activated when the mouse moves over the edge. | +| edge:mouseleave | Activated when the mouse leaves the edge. | +| edge:mousedown | Activated when the left or right button is clicked down on the edge. It cannot be activated by keyboard. | +| edge:mouseup | Activated when the left or right button is released on the edge. It cannot be activated by keyboard. | +| edge:dragenter | Activated when user drags an item into a target edge item. This event is applied on the target edge item. | +| edge:dragleave | Activated when user drags an item out of a target edge item. This event is applied on the target edge item. | +| edge:dragover | Activated when user drags an item over a target edge item. This event is applied on the target edge item | +| edge:drop | Activated when user drops an item on a target edge item. This event is applied on the target edge item. | +| edge:contextmenu | Open the context menu when user clicks the right button of mouse on the edge. [Demo](/en/examples/tool/contextMenu). | + +### Combo Interaction Event + +Combo inherit all the interaction events of Node. + +### Canvas Interaction Event + +| Event Name | Description | +| --- | --- | +| canvas:click | Activated when user clicks the **left button** of the mouse on the canvas. | +| canvas:dblclick | Activated when user double clicks the **left button** of the mouse on the canvas. | +| canvas:mouseenter | Activated when the mouse enters the canvas. | +| canvas:mousemove | Activated while the mouse is moving inside the canvas. It cannot be activated by keyboard. | +| canvas:mouseout | Activated while the mouse moves out of the canvas. | +| canvas:mouseover | Activated when the mouse moves over the canvas. | +| canvas:mouseleave | Activated when the mouse leaves the canvas. | +| canvas:mousedown | Activated when the left or right button is clicked down on the canvas. It cannot be activated by keyboard. | +| canvas:mouseup | Activated when the left or right button is released on the canvas. It cannot be activated by keyboard. | +| canvas:contextmenu | Open the context menu when user clicks the right button of mouse on the canvas. [Demo](/en/examples/tool/contextMenu). | +| canvas:dragstart | Activated when user begins to drag the canvas. This event is applied on the dragged canvas. | +| canvas:drag | Activated during the dragging process on the canvas. This event is applied on the dragged canvas. | +| canvas:dragend | Activated when user stops dragging on the canvas. This event is applied on the dragged canvas. | +| canvas:dragenter | Activated when user drags the canvas into a target item. This event is applied on the target item. | +| canvas:dragleave | Activated when user drags the canvas out of a target item. This event is applied on the target item. | +| canvas:drop | Activated when user drags and drops an item on the canvas. | +| canvas:touchstart | On touch screen, this event is activated when user begin to touch the canvas | +| canvas:touchmove | On touch screen, this event is activated when user is touching the canvas | +| canvas:touchend | On touch screen, this event is activated when user finish touching the canvas | + +## Timing Events + +Before and after being called some functions, G6 exports the timing events. These timing events can be listened by the following way: + +```javascript +graph.on(timingEventName, evt => { + // some operations +}) +``` + +`timingEventName` is shown below: + +| Event Name | Description | +| --- | --- | +| beforerender | Emitted before `graph.render` / `graph.read` being called. | +| afterrender | Emitted after `graph.render` / `graph.read` being called. | +| beforeadditem | Emitted before `graph.add` / `graph.addItem` being called. | +| afteradditem | Emitted after `graph.add` / `graph.addItem` being called. | +| beforeremoveitem | Emitted before `graph.remove` / `graph.removeItem` being called. | +| afterremoveitem | Emitted after `graph.remove` / `graph.removeItem` being called. | +| beforeupdateitem | Emitted before `graph.update` / `graph.updateItem` being called. | +| afterupdateitem | Emitted after `graph.update` / `graph.updateItem` being called. | +| beforeitemvisibilitychange | Emitted before `graph.showItem` / `graph.hideItem` being called. | +| afteritemvisibilitychange | Emitted after `graph.showItem` / `graph.hideItem` being called. | +| beforeitemstatechange | Emitted before `graph.setItemState` being called. | +| afteritemstatechange | Emitted after `graph.setItemState` being called. | +| beforeitemrefresh | Emitted before `graph.refreshItem` being called. | +| afteritemrefresh | Emitted after `graph.refreshItem` being called. | +| beforeitemstatesclear | Emitted before `graph.clearItemStates` being called. | +| afteritemstatesclear | Emitted after `graph.clearItemStates` being called. | +| beforemodechange | Emitted before `graph.setMode` / `graph.addBehaviors` / `graph.removeBehaviors` being called. | +| aftermodechange | Emitted after `graph.setMode` / `graph.addBehaviors` / `graph.removeBehaviors` being called. | +| beforelayout | Emitted before graph layout. `graph.render` will layout the graph, so `graph.render` will activate this event as well. | +| afterlayout | Emitted after graph layout being done. `graph.render` will layout the graph, so `graph.render` will activate this event as well. | +| beforegraphrefreshposition | Emitted before `graph.refreshPositions` beging called | +| aftergraphrefreshposition | Emitted after `graph.refreshPositions` beging called | +| beforegraphrefresh | Emitted before `graph.refresh` beging called | +| aftergraphrefresh | Emitted after `graph.refresh` beging called | +| beforeanimate | Emitted before global animation | +| afteranimate | Emitted after global animation | +| beforecreateedge | Emitted before an edge is created by the built-in behavior `create-edge` | +| aftercreateedge | Emitted after an edge is created by the built-in behavior `create-edge` | +| beforecollapseexpandcombo | Emitted before an combo is collapsed or expanded, the parameter `action` indicates collapse or expand | +| aftercollapseexpandcombo | Emitted after an combo is collapsed or expanded, the parameter `action` indicates collapse or expand | +| graphstatechange | Emitted after `graph.updateItemState` being called. | +| afteractivaterelations | Emitted while activating a node by `'activate-relations'` Behavior which is assigned to the the instance of Graph. | +| nodeselectchange | Emitted while the selected items are changed by `'brush-select'`, `'click-select'` or `'lasso-select'` Behavior which is assigned to the instance of Graph. | +| itemcollapsed | Emitted while a node is clicked to collapse or expand by `'collapse-expand'` Behavior which is assigned to the instance of TreeGraph. | +| tooltipchange | Emitted after the show/hide state is changed by `'tooltip'` or `'edge-tooltip'` Behavior which is assigned to the instance of Graph. | +| wheelzoom | Emitted after the canvas is zoomed by `'zoom-canvas'` Behavior which is assigned to the instance of Graph. | +| viewportchange | Emitted after the canvas is translated by `graph.moveTo`, `graph.translate`, and `graph.zoom`. | +| dragnodeend | Emitted while drag node end by `'drag-node'` Behavior. | +| stackchange | Emitted while the redo or undo stacks are changed. | + +**Timing Events in the Plugins** + +TimeBar plugin: + +| Event Name | Description | +| --- | --- | +| valuechange | Emitted when the value range of the timebar is chaged. | +| timebarstartplay | Emitted when the timeline starts to play. | +| timebarendplay | Emitted when the timeline ends playing. | + +Tooltip: + +| Event Name | Description | +| --- | --- | +| tooltipchange | Emitted when the Tooltip is changed. | + +### Callback Parameters + +The callback paramters are different from custom events. + +#### beforerender / afterrender + +No parameters. + +#### beforeadditem + +| Name | Type | Description | +| ----- | ------ | --------------------------------------- | +| type | String | The type of the item to be added. | +| model | Object | The data model of the item to be added. | + +#### afteradditem + +| Name | Type | Description | +| ----- | ------ | --------------------------------- | +| item | Item | The added item. | +| model | Object | The data model of the added item. | + +#### beforeremoveitem / afterremoveitem + +| Name | Type | Description | +| ---- | ---- | ----------------- | +| item | Item | The removed item data model. | +| type | 'node' / 'edge' / 'combo' | The type of removed item. | + +#### beforeupdateitem / afterupdateitem + +| Name | Type | Description | +| ----- | ------ | ----------------------------------------- | +| item | Item | The updated item. | +| model | Object | The data model of the item to be updated. | + +#### beforeitemvisibilitychange / afteritemvisibilitychange + +| Name | Type | Description | +| ------- | ------- | ----------------------------------------------------------------------- | +| item | Item | The manipulated item. | +| visible | Boolean | Whether the item is visible. `true` for visible, `false` for invisible. | + +#### beforeitemstatechange / afteritemstatechange + +| Name | Type | Description | +| ------- | ------- | --------------------------------------------------------------------- | +| item | Item | The manipulated item. | +| state | String | The state name. | +| enabled | Boolean | Wheter the state is enabled. `true` for enabled, `false` for unabled. | + +#### beforeitemstatesclear / afteritemstatesclear + +| Name | Type | Description | +| ------ | -------------- | ------------------------- | +| item | Item | The manipulated item. | +| states | Array / String | The states to be cleared. | + +#### beforemodechange / aftermodechange + +| Name | Type | Description | +| ---- | ------ | ------------------------- | +| mode | String | The name of current mode. | + +#### beforeitemrefresh / afteritemrefresh + +| Name | Type | Description | +| ---- | ---- | --------------------- | +| item | Item | The manipulated item. | + +#### beforelayout / afterlayout + +No parameters. + +#### afteractivaterelations + +| Name | Type | Description | +| ------ | ------ | ----------------------------- | +| item | Item | The manipulated item. | +| action | String | The name of the manipulation. | + +#### graphstatechange + +| Name | Type | Description | +| --- | --- | --- | +| states | Object | The items with different states, e.g. `{ hover: [Node, Node], selected: [ Node ] }` | + +#### afteractivaterelations + +| Name | Type | Description | +| ------ | ------ | ------------------------------ | +| item | Item | The manipulated item currently | +| action | String | The current action name | + +#### nodeselectchange + +| Name | Type | Description | +| ------------- | ------ | -------------------------------------------------------------- | +| target | Item | The manipulated item. | +| selectedItems | Object | All selected items, formed as `{ nodes: [...], edges: [...]}`. | + +#### beforecreateedge / aftercreateedge + +`beforecreateedge` has no parameters. The parameters of `aftercreateedge` are: + +| Name | Type | Description | +| ---- | ---- | ---------------- | +| edge | Item | The created edge | + +#### beforecollapseexpandcombo / aftercollapseexpandcombo + +| Name | Type | Description | +| ---- | ---- | ---------------- | +| action | string | The action, `'collapse'` or `'expand'` | +| combo | Item | The manipulated combo | + +#### itemcollapsed + +| Name | Type | Description | +| --------- | ------- | ----------------------------------------------------------------- | +| item | Item | The manipulated item. | +| collapsed | Boolean | The collapsed state of the manipulated item after this operation. | + +#### tooltipchange + +| Name | Type | Description | +| ------ | ------ | ----------------------------------------------- | +| item | Item | The manipulated item. | +| action | String | The `'show'` or `'hide'` state of this tooltip. | + +#### wheelzoom + +| Name | Type | Description | +| --- | --- | --- | +| deltaX | Number | The x-axis direction of the wheel scroll, value is `1`, `0`, or `-1`, where `0` means no scrolling on this direction. | +| deltaY | Number | The y-axis direction of the wheel scroll, value is `1`, `0`, or `-1`, where `0` means no scrolling on this direction. | +| ... Other parameters of wheel event. | | | + +#### viewportchange + +| Name | Type | Description | +| ------ | ----------------------------- | ---------------------------------------------------- | +| action | 'translate' / 'move' / 'zoom' | The action of view port changing. | +| matrix | Array | The matrix of the graph after the view port changed. | + +#### dragnodeend + +| Name | Type | Description | +| --- | --- | --- | +| items | Item[] | The manipulated items. | +| targetItem | null/Node/Combo | The position where the node is placed after dragging, the default is null, that is, placed on the canvas. | + +#### stackchange + +| Name | Type | Description | +| --------- | -------- | --------------- | +| redoStack | Object[] | The redo stack. | +| undoStack | Object[] | The undo stack. | +| action | String | The type of operation. | +| stackType | String | The type of stack. | + +#### valuechange + +| Name | Type | Description | +| --------- | -------- | -------- | +| value | number[] | The current value range, `value[0]` is the start and `value[1]` is the end. | + +#### timelinestart / timelineend + +No parameters. + +#### tooltipchange + +| Name | Type | Description | +| --------- | -------- | -------- | +| item | Item | The item the tooltip related to (a node or an edge). | +| action | 'show' / 'hide' | The current action. | diff --git a/packages/site/docs/api/Event.zh.md b/packages/site/docs/api/Event.zh.md new file mode 100644 index 0000000000..95dfe0ff1b --- /dev/null +++ b/packages/site/docs/api/Event.zh.md @@ -0,0 +1,383 @@ +--- +title: 基础事件 Event +order: 12 +--- + +本章介绍的事件可以通过 [graph.on](/zh/docs/api/graphFunc/on_off#graphoneventname-handler) 与 [graph.off](/zh/docs/api/graphFunc/on_off#graphoffeventname-handler) 进行绑定/解绑监听函数。 + +通用事件、Node 事件、Edge 事件及 Canvas 事件回调的参数请参考 [Behavior API](/zh/docs/api/Behavior)。 + +## 交互事件 + +使用如下形式进行交互事件的监听: + +```javascript +graph.on(eventName, evt => { + // 一些操作 +}) +``` + +其中,事件对象 `evt` 的属性值有: + +- `type`: 事件类型 +- `name`: 事件名称 +- `x`: 画布上的 x 坐标 +- `y`: 画布上的 y 坐标 +- `clientX`: 浏览器窗口上的 x 坐标 +- `clientY`: 浏览器窗口上的 y 坐标 +- `canvasX`: 画布父容器视口上的 x 坐标 +- `canvasY`: 画布父容器视口上的 y 坐标 + +(x/y,clientX/clientY,canvasX/canvasY 三套坐标系详解见 [G6 坐标系深度解析](/zh/docs/manual/advanced/coordinate-system)) + +- `item`: 事件的触发元素(节点/边/ Combo) +- `target`: 事件的触发图形 [Shape](/zh/docs/manual/middle/elements/shape/shape-keyshape) 或画布对象 +- `bubbles`: 是否允许冒泡 +- `defaultPrevented`: 是否阻止了原生事件 +- `originalEvent`: 原始浏览器事件对象,其中的 `button` 可以用于区分 `click` 事件的左/中/右键 +- `timeStamp`: 触发事件的时间 +- `propagationStopped`: 是否阻止传播(向上冒泡) +- `propagationPath`: 触发事件的路径 + +`eventName` 见下方内容。 + +### 通用交互事件 + +| 事件名称 | 描述 | +| --- | --- | +| click | 单击鼠标**左键**或者按下回车键时触发 | +| dblclick | 双击鼠标**左键**时触发,同时会触发两次 click | +| mouseenter | 鼠标移入元素范围内触发,**该事件不冒泡**,即鼠标移到其后代元素上时不会触发 | +| mousemove | 鼠标在元素内部移到时不断触发,不能通过键盘触发 | +| mouseout | 鼠标移出目标元素后触发 | +| mouseover | 鼠标移入目标元素上方,鼠标移到其后代元素上时会触发 | +| mouseleave | 鼠标移出元素范围时触发,**该事件不冒泡**,即鼠标移到其后代元素时不会触发 | +| mousedown | 鼠标按钮被按下(左键或者右键)时触发,不能通过键盘触发 | +| mouseup | 鼠标按钮被释放弹起时触发,不能通过键盘触发 | +| contextmenu | 用户右击鼠标时触发并打开上下文菜单,见 [Demo](/zh/examples/tool/contextMenu) | +| dragstart | 当拖拽元素开始被拖拽的时候触发的事件,此事件作用在被拖曳元素上 | +| drag | 当拖拽元素在拖动过程中时触发的事件,此事件作用于被拖拽元素上 | +| dragend | 当拖拽完成后触发的事件,此事件作用在被拖曳元素上 | +| dragenter | 当拖曳元素进入目标元素的时候触发的事件,此事件作用在目标元素上 | +| dragleave | 当拖曳元素离开目标元素的时候触发的事件,此事件作用在目标元素上 | +| drop | 被拖拽的元素在目标元素上同时鼠标放开触发的事件,此事件作用在目标元素上 | +| keydown | 按下键盘键触发该事件 | +| keyup | 释放键盘键触发该事件 | +| wheel | 鼠标滚轮滚动时触发该事件 | +| touchstart | 当手指触摸屏幕时候触发,即使已经有一个手指放在屏幕上也会触发 | +| touchmove | 当手指在屏幕上滑动的时候连续地触发。在这个事件发生期间,调用 `preventDefault()` 事件可以阻止滚动。 | +| touchend | 当手指从屏幕上离开的时候触发 | + +### Node 交互事件 + +| 事件名称 | 描述 | +| --- | --- | +| node:click | 鼠标**左键**单击节点时触发 | +| node:dblclick | 鼠标双击**左键**节点时触发,同时会触发两次 node:click | +| node:mouseenter | 鼠标移入节点时触发 | +| node:mousemove | 鼠标在节点内部移到时不断触发,不能通过键盘触发 | +| node:mouseout | 鼠标移出节点后触发 | +| node:mouseover | 鼠标移入节点上方时触发 | +| node:mouseleave | 鼠标移出节点时触发 | +| node:mousedown | 鼠标按钮在节点上按下(左键或者右键)时触发,不能通过键盘触发 | +| node:mouseup | 节点上按下的鼠标按钮被释放弹起时触发,不能通过键盘触发 | +| node:dragstart | 当节点开始被拖拽的时候触发的事件,此事件作用在被拖曳节点上 | +| node:drag | 当节点在拖动过程中时触发的事件,此事件作用于被拖拽节点上 | +| node:dragend | 当拖拽完成后触发的事件,此事件作用在被拖曳节点上 | +| node:dragenter | 当拖曳节点进入目标元素的时候触发的事件,此事件作用在目标元素上 | +| node:dragleave | 当拖曳节点离开目标元素的时候触发的事件,此事件作用在目标元素上 | +| node:dragover | 当拖曳节点在另一目标元素上移动时触发此事件,此事件作用在目标元素上 | +| node:drop | 被拖拽的节点在目标元素上同时鼠标放开触发的事件,此事件作用在目标元素上 | +| node:touchstart | 在触控屏上,当节点开始被触碰的时候触发的事件 | +| node:touchmove | 在触控屏上,当节点开始被触碰过程中触发的事件 | +| node:touchend | 在触控屏上,当节点开始被触碰结束的时候触发的事件 | +| node:contextmenu | 用户在节点上右击鼠标时触发并打开右键菜单,见 [Demo](/zh/examples/tool/contextMenu) | + +### Edge 交互事件 + +| 事件名称 | 描述 | +| --- | --- | +| edge:click | 鼠标**左键**单击边时触发 | +| edge:dblclick | 鼠标双击**左键**边时触发,同时会触发两次 edge:click | +| edge:mouseenter | 鼠标移入边时触发 | +| edge:mousemove | 鼠标在边上移到时不断触发,不能通过键盘触发 | +| edge:mouseout | 鼠标移出边后触发 | +| edge:mouseover | 鼠标移入边上方时触发 | +| edge:mouseleave | 鼠标移出边时触发 | +| edge:mousedown | 鼠标按钮在边上按下(左键或者右键)时触发,不能通过键盘触发 | +| edge:mouseup | 边上按下的鼠标按钮被释放弹起时触发,不能通过键盘触发 | +| edge:dragenter | 当拖曳元素进入目标边元素的时候触发的事件,此事件作用在目标边元素上 | +| edge:dragleave | 当拖曳元素离开目标边元素的时候触发的事件,此事件作用在目标边元素上 | +| edge:dragover | 当拖曳元素在另一目标边上移动时触发此事件,此事件作用在目标边元素上 | +| edge:drop | 被拖拽的元素在目标边元素上同时鼠标放开触发的事件,此事件作用在目标边元素上 | +| edge:contextmenu | 用户在边上右击鼠标时触发并打开右键菜单,见 [Demo](/zh/examples/tool/contextMenu) | + +### Combo 交互事件 + +Combo 继承所有 Node 事件。 + +### Canvas 交互事件 + +| 事件名称 | 描述 | +| --- | --- | +| canvas:click | 鼠标**左键**单击画布时触发 | +| canvas:dblclick | 鼠标双击**左键**画布时触发 | +| canvas:mouseenter | 鼠标移入画布时触发 | +| canvas:mousemove | 鼠标在画布内部移到时不断触发,不能通过键盘触发 | +| canvas:mouseout | 鼠标移出画布后触发 | +| canvas:mouseover | 鼠标移入画布上方时触发 | +| canvas:mouseleave | 鼠标移出画布时触发 | +| canvas:mousedown | 鼠标按钮在画布上按下(左键或者右键)时触发,不能通过键盘触发 | +| canvas:mouseup | 画布上按下的鼠标按钮被释放弹起时触发,不能通过键盘触发 | +| canvas:contextmenu | 用户在画布上右击鼠标时触发并打开右键菜单,见 [Demo](/zh/examples/tool/contextMenu) | +| canvas:dragstart | 当画布开始被拖拽的时候触发的事件,此事件作用在被拖曳画布上 | +| canvas:drag | 当画布在拖动过程中时触发的事件,此事件作用于被拖拽画布上 | +| canvas:dragend | 当拖拽完成后触发的事件,此事件作用在被拖曳画布上 | +| canvas:dragenter | 当拖曳画布进入目标元素的时候触发的事件,此事件作用在目标画布上 | +| canvas:dragleave | 当拖曳画布离开目标元素的时候触发的事件,此事件作用在目标画布上 | +| canvas:drop | 被拖拽的元素在空白画布上同时鼠标放开触发的事件,此事件作用在目标画布上 | +| canvas:touchstart | 在触控屏上,当画布开始被触碰的时候触发的事件 | +| canvas:touchmove | 在触控屏上,当画布开始被触碰过程中触发的事件 | +| canvas:touchend | 在触控屏上,当画布开始被触碰结束的时候触发的事件 | + +## 时机事件 + +用于监听图的某方法调用前后的时机。使用如下形式进行交互事件的监听: + +```javascript +graph.on(timingEventName, evt => { + // 一些操作 +}) +``` + +`timingEventName` 见下方内容。 + +| 事件名称 | 描述 | +| --- | --- | +| beforerender | 调用 `graph.render` / `graph.read` 方法之前触发 | +| afterrender | 调用 `graph.render` / `graph.read` 方法之后触发 | +| beforeadditem | 调用 `graph.add` / `graph.addItem` 方法之前触发 | +| afteradditem | 调用 `graph.add` / `graph.addItem` 方法之后触发 | +| beforeremoveitem | 调用 `graph.remove` / `graph.removeItem` 方法之前触发 | +| afterremoveitem | 调用 `graph.remove` / `graph.removeItem` 方法之后触发 | +| beforeupdateitem | 调用 `graph.update` / `graph.updateItem` 方法之前触发 | +| afterupdateitem | 调用 `graph.update` / `graph.updateItem` 方法之后触发 | +| beforeitemvisibilitychange | 调用 `graph.showItem` / `graph.hideItem` 方法之前触发 | +| afteritemvisibilitychange | 调用 `graph.showItem` / `graph.hideItem` 方法之后触发 | +| beforeitemstatechange | 调用 `graph.setItemState` 方法之前触发 | +| afteritemstatechange | 调用 `graph.setItemState` 方法之后触发 | +| beforeitemrefresh | 调用 `graph.refreshItem` 方法之前触发 | +| afteritemrefresh | 调用 `graph.refreshItem` 方法之后触发 | +| beforeitemstatesclear | 调用 `graph.clearItemStates` 方法之前触发 | +| afteritemstatesclear | 调用 `graph.clearItemStates` 方法之后触发 | +| beforemodechange | 调用 `graph.setMode` / `graph.addBehaviors` / `graph.removeBehaviors` 方法之前触发 | +| aftermodechange | 调用 `graph.setMode` / `graph.addBehaviors` / `graph.removeBehaviors` 方法之后触发 | +| beforelayout | 布局前触发。调用 `graph.render` 时会进行布局,因此 `render` 时会触发。或用户主动调用图的 `graph.layout` 时触发。 | +| afterlayout | 布局完成后触发。调用 `graph.render` 时会进行布局,因此 `render` 时布局完成后会触发。或用户主动调用图的 `graph.layout` 时布局完成后触发。 | +| beforegraphrefreshposition | `graph.refreshPositions` 被调用前触发 | +| aftergraphrefreshposition | `graph.refreshPositions` 被调用后触发 | +| beforegraphrefresh | `graph.refresh` 被调用前触发 | +| aftergraphrefresh | `graph.refresh` 被调用后触发 | +| beforeanimate | 全局动画发生前触发 | +| afteranimate | 全局动画发生后触发 | +| beforecreateedge | 使用内置交互 `create-edge`,创建边之前触发 | +| aftercreateedge | 使用内置交互 `create-edge`,创建边之后触发 | +| beforecollapseexpandcombo | 当一个 combo 被收起或展开之前被触发,参数 `action` 指明了是收起还是展开 | +| aftercollapseexpandcombo | 当一个 combo 被收起或展开之后被触发,参数 `action` 指明了是收起还是展开 | +| graphstatechange | 调用 `graph.updateItemState` 方法之后触发 | +| afteractivaterelations | 使用了 `'activate-relations'` Behavior 并触发了该行为后,该事件被触发 | +| nodeselectchange | 使用了 `'brush-select'` , `'click-select'` 或 `'lasso-select'` Behavior 且选中元素发生变化时,该事件被触发 | +| itemcollapsed | 在 TreeGraph 上使用了 `'collapse-expand'` Behavior 并触发了该行为后,该事件被触发 | +| tooltipchange | 使用了 `'tooltip'` 或 `'edge-tooltip'` Behavior 且 tooltip 的显示/隐藏被改变后,该事件被触发 | +| wheelzoom | 使用了 `'zoom-canvas'` Behavior 并用滚轮对图进行缩放后,该事件被触发 | +| viewportchange | 调用 `graph.moveTo`,`graph.translate`,或 `graph.zoom` 均会触发该事件 | +| dragnodeend | 使用了 `'drag-node'` Behavior,当拖动结束时,该事件被触发 | +| stackchange | 撤销/重做栈发生变化时,该事件触发 | + +**插件中的时机事件** + +TimeBar 插件: + +| 事件名称 | 描述 | +| --- | --- | +| valuechange | 时间轴的时间范围发生变化时触发 | +| timebarstartplay | 时间轴开始播放时触发 | +| timebarendplay | 时间轴播放结束时触发 | + +Tooltip 插件: + +| 事件名称 | 描述 | +| --- | --- | +| tooltipchange | Tooltip 发生变化时触发 | + +### 回调参数 + +不同时机监听事件的回调参数不同,下面针对各个自定义事件的回调参数进行说明。 + +#### beforerender / afterrender + +无参数 + +#### beforeadditem + +| 名称 | 类型 | 描述 | +| ----- | ------ | -------------- | +| type | String | 当前添加的类型 | +| model | Object | item 数据模型 | + +#### afteradditem + +| 名称 | 类型 | 描述 | +| ----- | ------ | -------------------- | +| item | Item | 已经添加的 item 实例 | +| model | Object | item 数据模型 | + +#### beforeremoveitem / afterremoveitem + +| 名称 | 类型 | 描述 | +| ---- | ---- | ------------------ | +| item | Item | 被删除的 item 实例数据 | +| type | 'node' / 'edge' / 'combo' | 被删除的 item 类型 | + +#### beforeupdateitem / afterupdateitem + +| 名称 | 类型 | 描述 | +| ----- | ------ | ------------------ | +| item | Item | 要更新的 item 实例 | +| model | Object | item 数据模型 | + +#### beforeitemvisibilitychange / afteritemvisibilitychange + +| 名称 | 类型 | 描述 | +| ------- | ------- | ----------------------------------------- | +| item | Item | 当前操作的 item 实例 | +| visible | Boolean | 是否可见,`true` 为可见,`false` 为不可见 | + +#### beforeitemstatechange / afteritemstatechange + +| 名称 | 类型 | 描述 | +| ------- | ------- | ----------------------------------------- | +| item | Item | 当前操作的 item 实例 | +| state | String | 状态 | +| enabled | Boolean | 状态是否可用,`true` 可用,`false` 不可用 | + +#### beforeitemstatesclear / afteritemstatesclear + +| 名称 | 类型 | 描述 | +| ------ | -------------- | -------------------- | +| item | Item | 当前操作的 item 实例 | +| states | Array / String | 需要批量清除的状态 | + +#### beforemodechange / aftermodechange + +| 名称 | 类型 | 描述 | +| ---- | ------ | -------------- | +| mode | String | 当前的模式名称 | + +#### beforeitemrefresh / afteritemrefresh + +| 名称 | 类型 | 描述 | +| ---- | ---- | -------------------- | +| item | Item | 当前操作的 item 实例 | + +#### beforelayout / afterlayout + +无参数 + +#### graphstatechange + +| 名称 | 类型 | 描述 | +| ------ | ------ | ---------------------------------------------------------------------------- | +| states | Object | 当前各个状态下的元素,格式举例 `{ hover: [Node, Node], selected: [ Node ] }` | + +#### afteractivaterelations + +| 名称 | 类型 | 描述 | +| ------ | ------ | -------------------- | +| item | Item | 当前操作的 item 实例 | +| action | String | 当前操作名 | + +#### nodeselectchange + +| 名称 | 类型 | 描述 | +| ------------- | ------ | ---------------------------------------------------------------- | +| target | Item | 当前操作的 item 实例 | +| selectedItems | Object | 当前被选中的所有 item 实例,形如 `{ nodes: [...], edges: [...]}` | + +#### beforecreateedge / aftercreateedge + +`beforecreateedge` 无参数。`aftercreateedge` 参数如下: + +| 名称 | 类型 | 描述 | +| ---- | ---- | ------------------ | +| edge | Item | 当前被创建的边实例 | + +#### beforecollapseexpandcombo / aftercollapseexpandcombo + +| 名称 | 类型 | 描述 | +| ---- | ---- | ---------------- | +| action | string | 具体的操作, `'collapse'` 或 `'expand'` | +| combo | Item | 被操作的 combo item | + +#### itemcollapsed + +| 名称 | 类型 | 描述 | +| --------- | ------- | ------------------------------------- | +| item | Item | 当前操作的 item 实例 | +| collapsed | Boolean | 当前操作后,操作对象的 collapsed 状态 | + +#### tooltipchange + +| 名称 | 类型 | 描述 | +| ------ | ------ | --------------------------------------------- | +| item | Item | 当前操作的 item 实例 | +| action | String | tooltip 当前是显示 `'show'` 还是隐藏 `'hide'` | + +#### wheelzoom + +| 名称 | 类型 | 描述 | +| --- | --- | --- | +| deltaX | Number | 滚动的 x 方向,取值 `1`,`0`,`-1`,`0` 代表没有该方向的滚动 | +| deltaY | Number | 滚动的 y 方向,取值 `1`,`0`,`-1`,`0` 代表没有该方向的滚动 | +| ... 其他滚轮事件的回调参数 | | | + +#### viewportchange + +| 名称 | 类型 | 描述 | +| --- | --- | --- | +| action | 'translate' / 'move' / 'zoom' | 视窗变换的类型,`'translate'`、`'move'`、`'zoom'` 分别标识该时机是由 `graph.translate`、`graph.move`、还是 `graph.zoom` 函数的调用而产生 | +| matrix | Array | 视窗变换后的图的矩阵 | + +#### dragnodeend + +| 名称 | 类型 | 描述 | +| ---------- | --------------- | ------------------------------------------------------- | +| items | Item[] | 当前操作的 item 实例 | +| targetItem | null/Node/Combo | 拖动节点结束后,节点是放到 canvas、Node 还是 Combo 上面 | + +#### stackchange + +| 名称 | 类型 | 描述 | +| --------- | -------- | -------- | +| redoStack | Object[] | 重做堆栈 | +| undoStack | Object[] | 撤销堆栈 | +| action | String | 操作类型 | +| stackType | String | 栈变更类型,撤销 undo / 重做 redo | + + +#### valuechange + +| 名称 | 类型 | 描述 | +| --------- | -------- | -------- | +| value | number[] | 时间轴当前时间范围,`value[0]` 为起始值,`value[1]` 为结束值 | + +#### timelinestart / timelineend + +无参数 + +#### tooltipchange + +| 名称 | 类型 | 描述 | +| --------- | -------- | -------- | +| item | Item | tooltip 所关联的元素(节点/边) | +| action | 'show' / 'hide' | tooltip 当前的变化时显示还是隐藏 | \ No newline at end of file diff --git a/packages/site/docs/api/Graph.en.md b/packages/site/docs/api/Graph.en.md new file mode 100644 index 0000000000..b9b926741f --- /dev/null +++ b/packages/site/docs/api/Graph.en.md @@ -0,0 +1,258 @@ +--- +title: G6.Graph(cfg) +order: 0 +redirect_from: + - /en/docs/api +--- + +Graph is the carrier of G6. All the operations about events, behaviors, items are mounted on the instance of Graph. + +```ts +// highlight-start +new Graph(cfg: GraphOptions) => Graph +// highlight-end + +const graph = new G6.Graph({ + container: '', + width: 500, + height: 500, + modes: { + default: ['drag-canvas'], + }, + layout: { + type: 'radial', + unitRadius: 50, + center: [500, 300], + }, +}); +``` + +### GraphOptions.container + + _string | HTMLElement_ **required** + +The DOM container of graph, it can be the id of a DOM element or the an HTML node. + +### GraphOptions.width + + _Number_ **optional** + +The width of the canvas for graph with the unit 'px'. + +### GraphOptions.height + + _Number_ **optional** + +The height of the canvas for graph with the unit 'px'. + +### GraphOptions.fitView + + _Boolean_ **optional** _default:_ `false` + +Whether to fit the canvas to the view port. + +### GraphOptions.fitViewPadding + + _Array | Number_ **optional** _default:_ `0` + +Takes effect only when `fitView: true`. It is the padding between canvas and the border of view port.
- It can be a value, e.g. `fitViewPadding: 20`, which means the padding to the top, left, right, bottom are the same.
- Or an array, e.g. `fitViewPadding: [ 20, 40, 50, 20 ]`, the four values in the array indicate the padding to the top, right, bottom, left respectively. + +### GraphOptions.fitCenter + + _Boolean_ **optional** _default:_ `false` + +_Supported by v3.5.1._ Whether to translate the graph to align its center with the canvas. Its priority is lower than `fitView`. + +### GraphOptions.linkCenter + + _Boolean_ **optional** _default:_ `false` + +Whether to connect the edges to nodes' center. + +### GraphOptions.groupByTypes + + _Boolean_ **optional** _default:_ `true` + +Whether to group the nodes and edges separately. When it is false, all the items (including nodes and edges) are in the same group, and the order/zindex of them are determined according to the order of their generation. When you are using Combo, **MUST** set `groupByTypes` to `false`. + +### GraphOptions.autoPaint + + _Boolean_ **optional** _default:_ `true` + +Whether to paint the graph automatically while item updated or view port changed. In order to enhance the performance, we recommend to turn off `antoPaint` when you are doing bulk operation on nodes or edges. This can be refered to [`setAutoPaint()`](#setautopaintauto). + +### GraphOptions.modes + + _Object_ **optional** _default:_ `{}` + +The interaction modes of this graph. Please refer to [Interaction Mode](/en/docs/manual/middle/states/mode) for detail. + +#### GraphOptions.modes.default + + _Object_ **optional** _default:_ `[]` + +The default modes of this graph. Please refer to [Default Behavior](/en/docs/manual/middle/states/defaultBehavior) for detail. + +### GraphOptions.nodeStateStyles + + _Object_ **optional** _default:_ `{}` + +The node styles on different states, e.g. hover, selected. It is a new feature of G6 3.1. + +⚠️ Note: If you are using version 3.1 or below, just change `nodeStyle` to `nodeStateStyles` and `edgeStyle` to `edgeStateStyles` and keep the configuration unchanged. + +### GraphOptions.edgeStateStyles + + _Object_ **optional** _default:_ `{}` + +The edge styles on different states, e.g. hover, selected. It is a new feature of G6 3.1. + +### GraphOptions.comboStateStyles + + _Object_ **optional** _default:_ `{}` + +The combo styles on different states, e.g. hover, selected. It is a new feature of G6 3.5. + +### GraphOptions.defaultNode + + _Object_ **optional** _default:_ `{}` + +Default node configurations in global, including type, size, color and so on. Its priority is lower than the configurations in data. + +### GraphOptions.defaultEdge + + _Object_ **optional** _default:_ `{}` + +Default edge configurations in global, including type, size, color and so on. Its priority is lower than the configurations in data. + +### GraphOptions.defaultCombo + + _Object_ **optional** _default:_ `{}` + +Default combo configurations in global, including type, size, color and so on. Its priority is lower than the configurations in data. It is a new feature of G6 3.5. + +### GraphOptions.plugins + + _Array _ **optional** _default:_ `[]` + +Plugins for graph. Please refer to [Plugin](/en/docs/manual/tutorial/plugins##plugin) for detail. + +### GraphOptions.animate + + _Boolean _ **optional** _default:_ `false` + +Wheter activate the global animation. Which will take effect while changing layouts, changing data, and other global operations. + +### GraphOptions.animateCfg + + _Object_ **optional** _default:_ `{}` + +The configurations for global animation. Takes effect only when `animate: true`. For more detail about animateCfg, see [Basic Animation Docs](/en/docs/manual/middle/animation). + +#### GraphOptions.animateCfg.onFrame + + _Function_ **optional** _default:_ `null` + +The callback function for every frame of animation. The path of custom animation for node can be defined here. The nodes will move linearly when `onFrame` is null. + +#### GraphOptions.animateCfg.duration + + _Number_ **optional** _default:_ `500` + +Duration of animation with unit millisecond. + +#### GraphOptions.animateCfg.easing + + _string_ **optional** _default:_ `easeLinear` + +The easing function name of animation. Please refer to ease in d3. + +### GraphOptions.minZoom + + _Number_ **optional** _default:_ `0.02` + +The minimum zoom ratio. If the ratio to be scaled in `fitView`, `zoom`, or `zoomTo` is smaller than the minZoom, the minZoom will take effect and the current funcion will return false. + +### GraphOptions.maxZoom + + _Number_ **optional** _default:_ `10` + +The maximum zoom ratio. If the ratio to be scaled in `fitView`, `zoom`, or `zoomTo` is bigger than the maxZoom, the maxZoom will take effect and the current funcion will return false. + +### GraphOptions.layout + + _Object_ **optional** _default:_ `{}` + +Configurations for layout. The `type` in it is the name of layout method with the options: `'random'`, `'radial'`, `'mds'`, `'circular'`, `'fruchterman'`, `'force'`, `'dagre'`, `'concentric'`, `'grid'`. When `layout` is not assigned on graph: + +- If there are `x` and `y` in node data, the graph will render with these information; +- If there is no positions information in node data, the graph will arrange nodes with Random Layout by default. + +For more configurations for different layout methods, please refer to [Layout API](/en/docs/api/graphLayout/guide). + +#### GraphOptions.layout.pipes + +**Sublayout Pipeline** *Supports by v4.3.0 and latter versions* + +Sublayout pipeline supports several sublayouts on different subgraphs by configuring `GraphOptions.layout`. + +img + +You can configure `layout.pipes` array when initializing the graph instance. Each item in the array is a sublayout pipe, and it contains the infomation about the layout type(`type`), configurations for this layout type, and node filtering function (`nodesFilter`). NOTICE that, if some nodes belong to several sublayouts in the same time, the result positions of these nodes will follow the last sublayout. + +The format of the `layout.pipes`: + +```javascript +type Pipes = + { + // the name of the layout method for this subgraph + type: 'random' | 'radial' | 'mds' | 'circular' | 'fruchterman' | 'force' | 'gForce' | 'dagre' | 'concentric' | 'grid' | 'forceAtlas2', + // node filtering function, the parameter is the node data, and it returns a boolean to indicate if the node belongs to this subgraph + nodesFilter: (node: NodeData) => boolean; + ... // the configurations for this layout method, refer to the docs for different layout method pls + }[]; +``` + +Usage demo: + +```javascript +// configure the layout.pipes when initializing the graph instance +const graph = new G6.Graph({ + // ... // other graph configurations + layout: { + pipes: [ + { + // the name of the layout method for this subgraph + type: 'circular', + // indicate if the node belongs to the subgraph + nodesFilter: (node) => node.subGraphId === '1', + // ... other configurations for this layout method + }, + { + type: 'grid', + nodesFilter: (node) => node.subGraphId === '2', + // other configurations for this layout method + begin: [100, 0], + } + ] + }, +}); +``` + +### GraphOptions.renderer + + _'canvas' / 'svg' _ **optional** _default:_ `'canvas'` + +Render the graph with Canvas or SVG. It is supported expecting V3.3.x. + +### GraphOptions.enabledStack + + _boolean_ **optional** _default:_ `false` + +Whether to enable stack,that is, whether to support redo & undo operation. Support by V3.6 and latter versions. + +### GraphOptions.maxStep + + _number_ **optional** _default:_ `10` + +The max step number of redo & undo, works only when the `enabledStack` is `true`. Support by V3.6 and latter versions. diff --git a/packages/site/docs/api/Graph.zh.md b/packages/site/docs/api/Graph.zh.md new file mode 100644 index 0000000000..ac7b8c4371 --- /dev/null +++ b/packages/site/docs/api/Graph.zh.md @@ -0,0 +1,253 @@ +--- +title: 图配置 G6.Graph(cfg) +order: 0 +redirect_from: + - /en/docs/api +--- + +Graph 是 G6 图表的载体,所有的 G6 节点实例操作以及事件,行为监听都在 Graph 实例上进行。Graph 的初始化通过 new 进行实例化,实例化时需要传入需要的参数。 + +```ts +// highlight-start +new G6.Graph(cfg: GraphOptions) => Graph +// highlight-end + +const graph = new G6.Graph({ + container: '', + width: 500, + height: 500, + modes: { + default: ['drag-canvas'], + }, + layout: { + type: 'radial', + unitRadius: 50, + center: [500, 300], + }, +}); +``` + +### GraphOptions.container + + _string | HTMLElement_ **required** + +图的 DOM 容器,可以传入该 DOM 的 id 或者直接传入容器的 HTML 节点对象。 + +### GraphOptions.width + + _Number_ **optional** + +指定画布宽度,单位为 'px',默认为画布容器宽度。 + +### GraphOptions.height + + _Number_ **optional** + +指定画布高度,单位为 'px',默认为画布容器高度。 + +### GraphOptions.fitView + + _Boolean_ **optional** _default:_ `false` + +是否开启画布自适应。开启后图自动适配画布大小。 + +### GraphOptions.fitViewPadding + + _Array | Number_ **optional** _default:_ `0` + +`fitView` 为 `true` 时生效。图适应画布时,指定四周的留白。
- 可以是一个值, 例如:`fitViewPadding: 20`
- 也可以是一个数组,例如:`fitViewPadding: [ 20, 40, 50, 20 ]`
当指定一个值时,四边的边距都相等,当指定数组时,数组内数值依次对应 上,右,下,左四边的边距。 + +### GraphOptions.fitCenter + + _Boolean_ **optional** _default:_ `false` + +*v3.5.1 后支持。*开启后,图将会被平移,图的中心将对齐到画布中心,但不缩放。优先级低于 fitView。 + +### GraphOptions.linkCenter + + _Boolean_ **optional** _default:_ `false` + +指定边是否连入节点的中心。 + +### GraphOptions.groupByTypes + + _Boolean_ **optional** _default:_ `true` + +各种元素是否在一个分组内,决定节点和边的层级问题,默认情况下所有的节点在一个分组中,所有的边在一个分组中,当这个参数为 false 时,节点和边的层级根据生成的顺序确定。当使用 Combo 时,**必须**将其设置为 `false` 。 + +### GraphOptions.autoPaint + + _Boolean_ **optional** _default:_ `true` + +当图中元素更新,或视口变换时,是否自动重绘。建议在批量操作节点时关闭,以提高性能,完成批量操作后再打开,参见后面的 setAutoPaint() 方法。 + +### GraphOptions.modes + + _Object_ **optional** _default:_ `{}` + +设置画布的交互模式。详情可见 [交互模式 Mode](/zh/docs/manual/middle/states/mode) 文档。 + +#### GraphOptions.modes.default + + _Object_ **optional** _default:_ `[]` + +画布默认的模式。详情可参见 [内置的 Behavior](/zh/docs/manual/middle/states/defaultBehavior) 文档。 + +### GraphOptions.nodeStateStyles + + _Object_ **optional** _default:_ `{}` + +各个状态下节点的样式,例如 `hover`、`selected`,3.1 版本新增。 + +⚠️ 注意: G6 3.1 版本中实例化 Graph 时,新增了 `nodeStateStyles` 及 `edgeStateStyles` 两个配置项,删除了 `nodeStyle` 和 `edgeStyle` ,使用 3.1 以下版本的同学,只需要将 `nodeStyle` 改成 `nodeStateStyles` ,将 `edgeStyle` 改成 `edgeStateStyles` ,配置内容保持不变。 + +### GraphOptions.edgeStateStyles + + _Object_ **optional** _default:_ `{}` + +各个状态下边的样式,例如 `hover`、`selected`,3.1 版本新增。 + +### GraphOptions.comboStateStyles + + _Object_ **optional** _default:_ `{}` + +各个状态下 Combo 的样式,例如 `hover`、`selected`,3.5 版本新增。 + +### GraphOptions.defaultNode + + _Object_ **optional** _default:_ `{}` + +默认状态下节点的配置,比如 `type`, `size`, `color`。会被写入的 data 覆盖。 见 [节点的通用属性](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性) 。 + +### GraphOptions.defaultEdge + + _Object_ **optional** _default:_ `{}` + +默认状态下边的配置,比如 `type`, `size`, `color`。会被写入的 data 覆盖。 见 [边的通用属性](/zh/docs/manual/middle/elements/edges/defaultEdge#边的通用属性) 。 + +### GraphOptions.defaultCombo + + _Object_ **optional** _default:_ `{}` + +默认状态下 Combo 的配置,比如 `type`, `color`。会被写入的 data 覆盖。3.5 版本新增。 见 [Combo 的通用属性](/zh/docs/manual/middle/elements/combos/defaultCombo#combo-的通用属性) 。 + +### GraphOptions.plugins + + _Array _ **optional** _default:_ `[]` + +向 graph 注册插件。插件机制请见:[插件](/zh/docs/manual/tutorial/plugins#插件) 。 + +### GraphOptions.animate + + _Boolean _ **optional** _default:_ `false` + +是否启用全局动画。 + +### GraphOptions.animateCfg + + _Object_ **optional** _default:_ `{}` + +动画配置项,仅在 `animate` 为 `true` 时有效。关于 `animateCfg` 的更多配置项参见[基础动画教程](/zh/docs/manual/middle/animation)。 + +#### GraphOptions.animateCfg.onFrame + + _Function_ **optional** _default:_ `null` + +回调函数,用于自定义节点运动路径,为空时线性运动。 + +#### GraphOptions.animateCfg.duration + + _Number_ **optional** _default:_ `500` + +动画时长,单位为毫秒。 + +#### GraphOptions.animateCfg.easing + + _string_ **optional** _default:_ `easeLinear` + +动画动效,可参见 d3 ease。 + +### GraphOptions.minZoom + + _Number_ **optional** _default:_ `0.02` + +最小缩放比例。若 fitView、zoom、zoomTo 等操作导致图的缩放比例小于该值,则将使用该值进行缩放,并返回 false。 + +### GraphOptions.maxZoom + + _Number_ **optional** _default:_ `10` + +最大缩放比例。若 fitView、zoom、zoomTo 等操作导致图的缩放比例大于该值,则将使用该值进行缩放,并返回 false。 + +### GraphOptions.layout + + _Object_ **optional** _default:_ `{}` + +布局配置项,使用 type 字段指定使用的布局方式,type 可取以下值:random, radial, mds, circular, fruchterman, force, gForce, forceAtlas2, dagre, concentric, grid。当实例化图时没有配置 `layout` 时: + +- 若数据中节点有位置信息(`x` 和 `y`),则按照数据的位置信息进行绘制; +- 若数据中节点没有位置信息,则默认使用 Random Layout 进行布局。 + +每种布局方法的配置项不尽相同,具体参见 [Graph 内置布局](/zh/docs/api/graphLayout/guide)。 + +#### GraphOptions.layout.pipes + +**流水线子图布局** *v4.3.0 新增* + +在 `GraphOptions.layout` 中可配置 `pipes` 达到同时对不通过子图使用不同布局的目的。 + +img + +在实例化图时配置 `layout.pipes` 数组,指定多个子图布局的布局类型(`type`)、布局参数、节点过滤函数(`nodesFilter`)。值得注意的是,若某些节点同时属于不同的子图(即这些节点在不同的子图的 `nodesFilter` 配置都返回为 `true`),则这些节点位置的计算将按照 `pipes` 数组顺序后者覆盖前者。 + +`pipes` 的数据类型为: + +```javascript +type Pipes = + { + // 该子图所使用的布局类型 + type: 'random' | 'radial' | 'mds' | 'circular' | 'fruchterman' | 'force' | 'gForce' | 'dagre' | 'concentric' | 'grid' | 'forceAtlas2', + // 节点的筛选器,参数为节点数据,返回布尔值代表该节点是否在该子图中 + nodesFilter: (node: NodeData) => boolean; + ... // 布局对应的参数,详见各个布局的参数 + }[]; +``` + +使用方法如下: + +```javascript +layout: { + pipes: [ + { + // 该子图所使用的布局类型 + type: 'circular', + // 根据节点的某个字段判断是否属于该子图 + nodesFilter: (node) => node.subGraphId === '1' + }, + { + type: 'grid', + // 该 grid 布局的其他参数 + begin: [100, 0], + nodesFilter: (node) => node.subGraphId === '2' + } + ], +}, +``` + +### GraphOptions.renderer + + _'canvas' / 'svg' _ **optional** _default:_ `'canvas'` + +渲染方式,该配置项除 V3.3.x 外其他版本均支持。 + +### GraphOptions.enabledStack + + _boolean_ **optional** _default:_ `false` + +是否启用 stack,即是否开启 redo & undo 功能,该配置项 V3.6 及以上版本支持。 + +### GraphOptions.maxStep + + _number_ **optional** _default:_ `10` + +redo & undo 最大步数, 只有当 enabledStack 为 true 时才起作用,该配置项 V3.6 及以上版本支持。 diff --git a/packages/site/docs/api/Group.en.md b/packages/site/docs/api/Group.en.md new file mode 100644 index 0000000000..9c8d75759b --- /dev/null +++ b/packages/site/docs/api/Group.en.md @@ -0,0 +1,263 @@ +--- +title: Graphics Group +order: 10 +--- + +Graphics Group (hereinafter referred to as Group) in G6 is similar to `` tag in SVG : Group a container of a group of graphics. The transformations on a Group such as clipping, rotating, zooming, and translating will be applied to all the children of the Group. The properties like color and position will also be inherited by its children. Besides, Group can be nested for complicated objects. For more information about Group, please refer to [Graphics Group](/en/docs/manual/middle/elements/shape/graphics-group) document. + +## Get group of item + +```javascript +// Find the graphics group of the item +const group = item.getContainer(); + +// equal to +const group = item.get('group'); +``` + +## Methods + +### group.addGroup(cfg) + +Add a new group to the group. + +**Parameters** + +| Name | Type | Description | +| ---- | ------ | --------------------------------------------------- | +| cfg | Object | Not required. It is the configurations of the group | + +The `cfg` above is not required, and it contains: + +| Name | Type | Description | +| --- | --- | --- | +| id | String | The unique id of this group | +| name | String | Required, and the name of the shape which **must be unique** in a custom node/edge/combo type. Besides, `name` can be used for searching this shape, e.g. `const shape = group.find(element => element.get('name') === 'shape-name')`. The usage of find can be found at [find(fn)](#findfn) | +| visible | Boolean | Whether the group is visible | +| capture | Boolean | Whether the group is capturable | +| draggable | Boolean | Whether the group is allowed to response `dragstart`, `drag`, and `dragend` events. E.g. when user add a group into a custom node with `draggable: true`, the group will response the dragging events on the node, and the `'drag-node'` in the `modes` of the graph instance will take effect on the group | +| zIndex | Number | The visual index of the shape, similar to z-index of DOM. It is not required. `group.sort()` will sort the visual index of the shapes inside the group according to their zIndex | + +**Usage** + +```javascript +// No configurations +group.addGroup(); + +// Configured +group.addGroup({ + id: 'groupId', + draggable: true, + // other configurations +}); +``` + +### group.addShape(type, cfgs) + +Add a new shape into the group
⚠️Attention: the clip and transform operations will affect all the shapes in the group. The graphics and their properties are introduced in [Shape Doc](/en/docs/manual/middle/elements/shape/shape-keyshape). + +**Parameters** + +| Name | Type | Description | +| --- | --- | --- | +| type | String | The type of the shape. Options: `'rect'`, `'circle'`, `'fan'`, `'ellipse'`, `'marker'`, `'image'`, and so on. Please refer to [Graphics Shape Properties](/en/docs/manual/middle/elements/shape/shape-and-properties) document | +| cfg | Object | The configurations of the shape. | + +The `cfg` above contains: + +| Name | Type | Description | +| --- | --- | --- | +| attrs | Object | The style configurations for the shape. e.g. `{x: 0, y: 10, fill: '#0f0'}` | +| name | String | Required, and the name of the shape which **must be unique** in a custom node/edge/combo type. Besides, `name` can be used for searching this shape, e.g. `const shape = group.find(element => element.get('name') === 'shape-name')`. The usage of find can be found at [find(fn)](#findfn) | +| visible | Boolean | Whether the shape is visible | +| capture | Boolean | Whether the shape is capturable by mouse events | +| draggable | Boolean | Whether the shape is allowed to response `dragstart`, `drag`, and `dragend` events. E.g. when user add a shape into a custom node with `draggable: true`, the shape will response the dragging events on the node, and the `'drag-node'` in the `modes` of the graph instance will take effect | +| zIndex | Number | The visual index of the shape, similar to z-index of DOM. It is not required. `group.sort()` will sort the visual index of the shapes inside the group according to their zIndex | + +**Usage** + +```javascript +group.addShape('rect', { + attrs: { + x: 0, // required + y: 0, // required + fill: 'red', + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowColor: 'blue', + shadowBlur: 10, + opacity: 0.8, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', + zIndex: 1, +}); +``` + +### group.contain(child) + +Whether the group contains the child.
The type of the return value: Boolean. + +**Parameters** + +| Name | Type | Description | +| ----- | ------------- | ------------------------------------ | +| child | Group / Shape | A sub group or an instance of shape. | + +**Usage** + +```javascript +const has = group.contain(child); +``` + +### group.find(fn) + +Find **the first** element that matches the rule. + +**Parameters** + +| Name | Type | Description | +| ---- | -------- | ----------------------------- | +| fn | Function | Customized callback function. | + +**Usage** + +```javascript +const child = group.find(function (item) { + return item.attr('fill') === 'red'; // Find the first graphics filled with red +}); +``` + +### group.findById(id) + +Find the element by its id.
The type of the return value: Object。 + +**Parameters** + +| Name | Type | Description | +| ---- | ------ | -------------------- | +| id | String | The id of the group. | + +**Usage** + +```javascript +const group1 = group.findById('group1'); +``` + +### group.findAll(fn) + +Find all the elements that match the rule.
The type of the return value: [ Object ] + +**Parameters** + +| Name | Type | Description | +| ---- | -------- | ----------------------------- | +| fn | Function | Customized callback function. | + +**Usage** + +```javascript +const children = group.findAll(function (item) { + return item.get('id') < 10; // get all the elements with the id smaller than 10 +}); +``` + +### group.getShape(x,y) + +Get the top shape which is on (x, y).
The type of the return value: Object + +**Parameters** + +| Name | Type | Description | +| ---- | ------ | ------------ | +| x | number | x coordinate | +| y | number | y coordinate | + +**Usage** + +```javascript +// Get the top element on (10, 30) +const element = group.getShape(10, 30); +``` + +### group.getFirst() + +Get **the first** element of the group.
The type of the return value: Object + +**Usage** + +```javascript +const child = group.getFirst(); + +// Equal to +const childrens = group.get('children'); +const child = childrens[0]; +``` + +### group.getLast() + +Get the last element of the group.
The type of the return value: Object + +**Usage** + +```javascript +const child = group.getLast(); + +// Equal to +const childrens = group.get('children'); +const child = childrens[childrens.length - 1]; +``` + +### group.getChildByIndex(index) + +Get the `index`-th child of the group started from `0`.
The type of the return value: Object + +**Parameters** + +| Name | Type | Description | +| ----- | ------ | ------------------------------------- | +| index | number | The index of the child. 0 by default. | + +**Usage** + +```javascript +const child = group.getChildByIndex(2); +``` + +### group.removeChild(child) + +Remove a group or a graphics from the group. + +**Parameters** + +| Name | Type | Description | +| ----- | ------------- | ------------------------------------ | +| child | Group / Shape | A sub group or an instance of Shape. | + +**Usage** + +```javascript +group.removeChild(child); +``` + +### group.sort() + +Sort method.
In general, it is called for ordering the children of the group. + +Typical scenerio: we set index for each `shape` when add `shape` by `group.addShape()`. After adding, sort the shapes by calling `group.sort()`. + +**Usage** + +```javascript +group.sort(); +``` + +### group.clear() + +Clear all the children in the group. + +**Usage** + +```javascript +group.clear(); +``` diff --git a/packages/site/docs/api/Group.zh.md b/packages/site/docs/api/Group.zh.md new file mode 100644 index 0000000000..b58f559fc0 --- /dev/null +++ b/packages/site/docs/api/Group.zh.md @@ -0,0 +1,264 @@ +--- +title: 图形分组 Graphics Group +order: 10 +--- + +图形分组 Graphics Group(下文简称 Group) 类似于 SVG 中的 `` 标签:Group  是用来组合图形对象的容器。在 Group  上添加变换(例如剪裁、旋转、放缩、平移等)会应用到其所有的子元素上。在 Group  上添加属性(例如颜色、位置等)会被其所有的子元素继承。此外, Group 可以多层嵌套使用,因此可以用来定义复杂的对象。关于 Group 更详细的介绍请参考 [图形分组 Group](/zh/docs/manual/middle/elements/shape/graphics-group) 文档。 + +## 获取元素的 group + +```javascript +// 获取元素(节点/边/Combo)的图形对象的容器 +const group = item.getContainer(); + +// 等价于 +const group = item.get('group'); +``` + +## group 实例方法 + +### group.addGroup(cfg) + +向分组中添加新的分组。 + +**参数** + +| 名称 | 类型 | 说明 | +| ---- | ------ | -------------------- | +| cfg | Object | 分组到配置项,非必须 | + +其中,`cfg` 不是必须指定到,它包括以下字段: + +| 名称 | 类型 | 说明 | +| --- | --- | --- | +| id | String | 图形分组的唯一标识,非必须指定,指定则必须唯一 | +| name | String | 图形分组的标识,必须指定,**在同一元素类型中必须唯一**。另外,`name` 可以用于组内搜索到该元素:`const shape = group.find(element => element.get('name') === 'shape-name')`,find 函数用法见 [find(fn)](#findfn) | +| capture | Boolean | 非必须指定,该图形分组是否可以被鼠标事件捕捉到,即是否能够响应各鼠标事件。非必须指定 | +| visible | Boolean | 非必须指定,该图形分组是否可见。非必须指定,默认为 `true` | +| draggable | Boolean | 非必须指定,该图形分组是否允许被拖拽。例如,自定义节点通过 `addGroup` 添加图形分组,当该图形分组的 `draggable` 值为 `true` 时,鼠标在该自定义节点的这个图形分组上才能够响应 `dragstart`,`drag`,与 `dragend` 事件;在实例化图时的 `modes` 中配置的 `'drag-node'` 交互才可以在该图形分组上进行拖拽时生效 | +| zIndex | Number | 非必须指定,该图形分组的视觉层次 z-index。非必须指定。指定后,调用 `group.sort()` 可以对组内所有图形根据各自 zIndex 进行视觉层次的排序 | + +**用法** + +```javascript +// No configurations +group.addGroup(); + +// Configured +group.addGroup({ + id: 'groupId', + draggable: true, + // other configurations +}); +``` + +### group.addShape(type, cfgs) + +向分组中添加新的图形。
⚠️ 注意: 在分组上添加的 clip, transform 等操作会影响到该分组中的所有图形。所有图形及其绘图属性请见 [Shape Doc](/zh/docs/manual/middle/elements/shape/shape-keyshape)。 + +**参数** + +| 名称 | 类型 | 说明 | +| --- | --- | --- | +| type | String | 图元素类型,值可以为:`'rect'`、`'circle'`、`'fan'`、`'ellipse'`、`'marker'`、`'image'` 等,具体参考 [Shape 的类型及属性](/zh/docs/manual/middle/elements/shape/shape-and-properties) 教程 | +| cfg | Object | 图元素的属性 | + +其中,`cfg` 包括以下字段: + +| 名称 | 类型 | 说明 | +| --- | --- | --- | +| attrs | Object | 图形样式,必须配置,例如:`{x: 0, y: 10, fill: '#0f0'}` | +| name | String | 图形的标识,必须指定,**在同一元素类型中必须唯一**。另外,`name` 可以用于组内搜索到该元素:`const shape = group.find(element => element.get('name') === 'shape-name')`,find 函数用法见 [find(fn)](#findfn) | +| capture | Boolean | 该图形是否可以被鼠标事件捕捉到,即是否能够响应各鼠标事件。非必须指定 | +| visible | Boolean | 该图形是否可见。非必须指定,默认为 `true` | +| draggable | Boolean | 该图形是否允许被拖拽。例如,自定义节点通过 `addShape` 添加图形,当该图形的 `draggable` 值为 `true` 时,鼠标在该自定义节点的这个图形上才能够响应 `dragstart`,`drag`,与 `dragend` 事件;在实例化图时的 `modes` 中配置的 `'drag-node'` 交互才可以在该图形上进行拖拽时生效 | +| zIndex | Number | 该图形的视觉层次 z-index。非必须指定。指定后,调用 `group.sort()` 可以对组内所有图形根据各自 zIndex 进行视觉层次的排序 | + +**用法** + +```javascript +group.addShape('rect', { + attrs: { + x: 0, // 必须配置 + y: 0, // 必须配置 + fill: 'red', + stroke: 'red', + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowColor: 'blue', + shadowBlur: 10, + opacity: 0.8, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'rect-shape', + zIndex: 1, +}); +``` + +### group.contain(child) + +该分组是否包含此元素。
返回值: Boolean + +**参数** + +| 名称 | 类型 | 说明 | +| ----- | ------------- | ---------------------- | +| child | Group / Shape | 子 Group 或 Shape 实例 | + +**用法** + +```javascript +const has = group.contain(child); +``` + +### group.find(fn) + +根据指定条件返回对应元素,**只返回符合条件的第一个元素**。 + +**参数** + +| 名称 | 类型 | 说明 | +| ---- | -------- | -------------- | +| fn | Function | 自定义回调函数 | + +**用法** + +```javascript +const child = group.find(function (item) { + return item.attr('fill') === 'red'; // 找到首个填充为红色的图形 +}); +``` + +### group.findById(id) + +根据元素 ID 返回对应的实例。
返回值:Object。 + +**参数** + +| 名称 | 类型 | 说明 | +| ---- | ------ | ------------- | +| id | String | Group 实例 ID | + +**用法** + +```javascript +const group1 = group.findById('group1'); +``` + +### group.findAll(fn) + +返回所有符合条件的元素。
返回值: [ Object ] + +**参数** + +| 名称 | 类型 | 说明 | +| ---- | -------- | -------------- | +| fn | Function | 自定义回调函数 | + +**用法** + +```javascript +const children = group.findAll(function (item) { + return item.get('id') < 10; // 获取所有id小于10的元素 +}); +``` + +### group.getShape(x,y) + +返回该坐标点最上层的元素。
返回值: Object + +**参数** + +| 名称 | 类型 | 说明 | +| ---- | ------ | ------ | +| x | Number | x 坐标 | +| y | Number | y 坐标 | + +**用法** + +```javascript +// 获取 (10, 30) 坐标点上层的元素 +const element = group.getShape(10, 30); +``` + +### group.getFirst() + +获取该分组的第一个子元素。
返回值: Object + +**用法** + +```javascript +const child = group.getFirst(); + +// 等价于 +const childrens = group.get('children'); +const child = childrens[0]; +``` + +### group.getLast() + +获取该分组的最后一个子元素。
返回值: Object + +**用法** + +```javascript +const child = group.getLast(); + +// 等价于 +const childrens = group.get('children'); +const child = childrens[childrens.length - 1]; +``` + +### group.getChildByIndex(index) + +返回第 `index`  个子元素,从 `0` 开始计数。
返回值: Object + +**参数** + +| 名称 | 类型 | 说明 | +| ----- | ------ | ------------------------ | +| index | Number | 子元素的序号,默认为 `0` | + +**用法** + +```javascript +const child = group.getChildByIndex(2); +``` + +### group.removeChild(child) + +从分组中删除一个分组或一个图形。 + +**参数** + +| 名称 | 类型 | 说明 | +| ----- | ------------- | ---------------------- | +| child | Group / Shape | 子 group 或 Shape 实例 | + +**用法** + +```javascript +group.removeChild(child); +``` + +### group.sort() + +排序方法。
一般用于在设置子元素层叠顺序时使用。 + +典型使用场景:通过 `group.addShape()` 添加 `shape` 时,添加的每个 `shape` 都设置了 `index`,在最后调用 `group.sort()` 可以对添加的 `shape` 进行排序。 + +**用法** + +```javascript +group.sort(); +``` + +### group.clear() + +清除该组的所有子元素。 + +**用法** + +```javascript +group.clear(); +``` diff --git a/packages/site/docs/api/Items/comboMethods.en.md b/packages/site/docs/api/Items/comboMethods.en.md new file mode 100644 index 0000000000..e936cc8075 --- /dev/null +++ b/packages/site/docs/api/Items/comboMethods.en.md @@ -0,0 +1,139 @@ +--- +title: combo.* +order: 3 +--- + +Combo inherits from Node. The functions of Node are also available for Combo. This document will only introduce the common functions for Combo Class. All the built-in combos can be found in [Built-in Combos Doc](/en/docs/manual/middle/elements/combos/defaultCombo) and [demo](/en/examples/item/defaultCombos), Custom Combo can be found in [Custom Combo Doc](/en/docs/manual/middle/elements/combos/custom-combo) and [demo](/en/examples/item/customCombo). + +### combo.getChildren() + +Get all children including sub nodes and sub combos. + +**Return** + +- Return the collection of Node and Combo: `{ nodes: INode[], combos: ICombo[] }` + +**Usage** + +```javascript +const elements = combo.getChildren(); +``` + +### combo.getNodes() + +Get sub nodes of the combo。 + +**Return** + +- The type of return value: `INode[]`. + +### combo.getCombos() + +Get sub combos of the combo。 + +**Return** + +- The type of return value: `ICombo[]`. + +### combo.addChild(item: INode | ICombo) + +Add the `item` (Node or Combo) into the Combo as its child. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | -------------- | -------- | ------------------------- | +| item | INode / ICombo | true | The item of node or combo | + +**Return** + +- The type of return value: `boolean`; +- Return `true` to indicate successful executed. + +**Usage** + +```javascript +const node = graph.findById('node1'); + +// Return true to indicate successful executed. +const result = combo.addChild(node); +``` + +### combo.addNode(node: string | INode) + +Add the Node to the Combo. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | -------------- | -------- | ------------------------------- | +| node | string / INode | true | Node ID or the item of the node | + +**Return** + +- The type of return value: `boolean`; +- Return `true` to indicate successful executed. + +### combo.addCombo(combo: ICombo) + +Add a sub combo into the combo as the its child. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | --------------------- | +| combo | ICombo | true | The item of the combo | + +**Return** + +- The type of return value: `boolean`; +- Return `true` to indicate successful executed. + +### combo.removeChild(item: ICombo | INode) + +Remove the child item (a Node or a Combo). + +**Parameters** + +| Name | Type | Required | Description | +| ---- | -------------- | -------- | ------------------------- | +| item | INode / ICombo | true | The item of node or combo | + +**Return** + +- The type of return value: `boolean`; +- Return `true` to indicate successful executed. + +### combo.removeCombo(combo: ICombo) + +Remove a sub combo from the parent. The `combo` will still exist on the graph but it is not belong to the parent combo any more. If you want to delete the combo from the graph, call [graph.removeItem](/en/docs/api/Graph#removeitemitem) instead + +**Parameters** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | ----------------- | +| combo | ICombo | true | The item of Combo | + +**Return** + +- The type of return value: `boolean`; +- Return `true` to indicate successful executed. + +### combo.removeNode(node: INode) + +Remove a child node from the combo. The node will still exist on the graph but it is not belong to the combo any more. If you want to delete the node from the graph, call [graph.removeItem](/en/docs/api/Graph#removeitemitem) instead + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ----- | -------- | --------------------------- | +| node | INode | true | Node ID or the item of Node | + +**Return** + +- The type of return value: `boolean`; +- Return `true` to indicate successful executed. + +### Comparison for Combo and Hull + +combo-hull \ No newline at end of file diff --git a/packages/site/docs/api/Items/comboMethods.zh.md b/packages/site/docs/api/Items/comboMethods.zh.md new file mode 100644 index 0000000000..033a30c3a6 --- /dev/null +++ b/packages/site/docs/api/Items/comboMethods.zh.md @@ -0,0 +1,139 @@ +--- +title: combo 实例方法 +order: 3 +--- + +Combo 继承自 Node,具有 Node 的所有特性。本文仅介绍 Combo 类的通用方法,内置节点见 [内置 Combo 文档](/zh/docs/manual/middle/elements/combos/defaultCombo) 和 [demo](/zh/examples/item/defaultCombos),自定义节点见 [自定义 Combo 文档](/zh/docs/manual/middle/elements/combos/custom-combo) 和 [demo](/zh/examples/item/customCombo)。 + +### combo.getChildren() + +获取 Combo 中所有的子元素,包括 Combo 和 Node。 + +**返回值** + +- 返回值为子 node 和 combo 的集合:`{ nodes: INode[], combos: ICombo[] }` + +**用法** + +```javascript +const elements = combo.getChildren(); +``` + +###combo. getNodes() + +获取 Combo 中所有子节点。 + +**返回值** + +- 返回值类型为 `INode[]`。 + +###combo. getCombos() + +获取 Combo 中所有子 combo。 + +**返回值** + +- 返回值类型为 `ICombo[]`。 + +###combo. addChild(item: INode | ICombo) + +向 Combo 中添加子 Node 或子 Combo。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | -------------- | -------- | ------------------- | +| item | INode / ICombo | 是 | 节点或 Combo 的实例 | + +**返回值** + +- 类型: `boolean`; +- 含义:返回 `true` 表示添加成功。 + +**用法** + +```javascript +const node = graph.findById('node1'); + +// 如果返回结果为 true,则说明添加成功 +const result = combo.addChild(node); +``` + +###combo. addNode(node: string | INode) + +向 combo 中添加节点。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | -------------- | -------- | ------------------ | +| node | string / INode | 是 | 节点 ID 或节点实例 | + +**返回值** + +- 类型: `boolean`; +- 含义:返回 `true` 表示添加成功。 + +###combo. addCombo(combo: ICombo) + +向 combo 中添加 combo。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | ---------- | +| combo | ICombo | 是 | combo 实例 | + +**返回值** + +- 类型: `boolean`; +- 含义:返回 `true` 表示添加成功。 + +###combo. removeChild(item: ICombo | INode) + +移除子元素(子节点或子 combo)。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | -------------- | -------- | ------------------- | +| item | INode / ICombo | 是 | 节点或 Combo 的实例 | + +**返回值** + +- 类型: `boolean`; +- 含义:返回 `true` 表示移除成功。 + +###combo. removeCombo(combo: ICombo) + +移除指定的子 combo。注意:移除后 `combo` 不再属于该父 Combo,但没有被删除。需要删除 `combo` 请调用 [graph.removeItem](/zh/docs/api/Graph#removeitemitem) + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | ------------ | +| combo | ICombo | 是 | Combo 的实例 | + +**返回值** + +- 类型: `boolean`; +- 含义:返回 `true` 表示移除成功。 + +###combo. removeNode(node: INode) + +移除指定的子 Node。注意:移除后该节点不再属于该 Combo,但没有被删除。需要删除节点请调用 [graph.removeItem](/zh/docs/api/Graph#removeitemitem) + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ----- | -------- | ------------------ | +| node | INode | 是 | 节点 ID 或节点实例 | + +**返回值** + +- 类型: `boolean`; +- 含义:返回 `true` 表示移除成功。 + +### 比较 Combo 与 Hull + +combo-hull diff --git a/packages/site/docs/api/Items/comboProperties.en.md b/packages/site/docs/api/Items/comboProperties.en.md new file mode 100644 index 0000000000..6c5be92fb0 --- /dev/null +++ b/packages/site/docs/api/Items/comboProperties.en.md @@ -0,0 +1,63 @@ +--- +title: Combo Model Properties +order: 8 +--- + +Except for the common properties, there are special configurations for each type of built-in Combos. The `style`s of them depend on their keyShape. The common properties for built-in Combos can be refered to: + +### id + + _String_ **required** + +The id of the Combo, **Must be a unique string**. + +### parentId + + _String_ **optional** + +The ID of the parent Combo + +### size + + _Number | Array_ **optional** + +The minimum size of the combo. The default value for 'circle' type Combo is 20, [20, 5] for 'rect' type. + +### padding + + _Number | Number[]_ **optional** + +The padding of the Combo. The default value for 'circle' type Combo is 25, [25, 20, 15, 20] for 'rect'. + +### style + + _Object_ **optional** + +The Combo style. `style` is an object to configure the filling color, stroke color, shadow, and so on. The complete configurations are listed in [Overview of Combos -- style](/en/docs/manual/middle/elements/combos/defaultCombo#style)。 + +### label + + _String_ **optional** + +The label text of the combo. + +### labelCfg + + _Object_ **optional** + +The configurations of the combo. Please refer to [Overview of Combos -- label and label configure](/en/docs/manual/middle/elements/combos/defaultCombo#label-and-labelcfg) for detailed configurations. + +### type + + _Object_ **optional** + +The shape type of the Combo. It can be the type of built-in Combo, or the custom Combo. 'circle' by default. The table below shows the built-in Combos and their special properties: + +| Name | Description | Default | +| --- | --- | --- | +| circle | Circle Combo:
- `size` is a number representing the diameter
- The circle is centered at the combo position
- `color` takes effect on the stroke
- The label is placed on the top of the circle by default
- More properties are described in [circle](/en/docs/manual/middle/elements/combos/built-in/circle)
- Demo | img | +| rect | Rect Combo:
- `size` is an array, e.g. [100, 50]
- The rect in centered at the combo position
- `color` takes effect on the stroke
- The label is placed on the left top of the circle by default
- More properties are described in [rect](/en/docs/manual/middle/elements/combos/built-in/rect)
- Demo | img | + +## Properties for Specific Built-in Combos + +The special properties for each built-in Combos can be found in the subdocuments of [Built-in Combos](/en/docs/manual/middle/elements/combos/defaultCombo). diff --git a/packages/site/docs/api/Items/comboProperties.zh.md b/packages/site/docs/api/Items/comboProperties.zh.md new file mode 100644 index 0000000000..ab801d336e --- /dev/null +++ b/packages/site/docs/api/Items/comboProperties.zh.md @@ -0,0 +1,61 @@ +--- +title: Combo 配置项 +order: 8 +--- + +### id + + _String_ **required** + +一个 Combo 的唯一标识,**必须是 string 类型,必须唯一**。 + +### parentId + + _String_ **optional** + +该 Combo 的父 Combo 的 ID。 + +### size + + _String_ **optional** + +Combo 的最小大小,默认 'circle' 类型 Combo 的 size 为 20,'rect' 类型的为 [20, 5]。 + +### padding + + _Number | Number[]_ **optional** + +该 Combo 内边距。 + +### style + + _Object_ **optional** + +该 Combo 的样式配置项,详见[内置 Combo 配置文档](/zh/docs/manual/middle/elements/combos/defaultCombo#样式属性-style)。 + +### label + + _String_ **optional** + +该 Combo 的文本标签。 + +### labelCfg + + _Object_ **optional** + +该 Combo 的文本标签样式配置项,详见[内置 Combo 配置文档](/zh/docs/manual/middle/elements/combos/defaultCombo#标签文本-label-及其配置-labelcfg)及各类型 Combo 的文档。 + +### type + + _Object_ **optional** + +指定该 Combo 的类型,可以是内置 Combo 的类型名,也可以是自定义 Combo 的类型名。默认是 'circle'。 + +| 名称 | 描述 | 默认示例 | +| --- | --- | --- | +| circle | 圆形:
- `size` 是单个数字,表示直径
- 圆心位置对应 Combo 的位置
- `color` 字段默认在描边上生效
- 标签文本默认在 Combo 正上方
- 更多字段见 [Circle](/zh/docs/manual/middle/elements/combos/built-in/circle) Combo 教程
- Demo | img | +| rect | 矩形:
- `size` 是数组,例如:[100, 50]
- 矩形的中心位置是 COmbo 的位置,而不是左上角
- `color` 字段默认在描边上生效
- 标签文本默认在 Combo 左上角
- 更多字段见 [Rect](/zh/docs/manual/middle/elements/combos/built-in/rect) Combo 教程
- Demo | img | + +## 内置 Combo 的特有属性 + +内置各个内置 Combo 的特有属性见 [内置 Combo](/zh/docs/manual/middle/elements/combos/defaultCombo) 目录下各文档。 diff --git a/packages/site/docs/api/Items/edgeMethods.en.md b/packages/site/docs/api/Items/edgeMethods.en.md new file mode 100644 index 0000000000..cc0b28294f --- /dev/null +++ b/packages/site/docs/api/Items/edgeMethods.en.md @@ -0,0 +1,76 @@ +--- +title: edge.* +order: 2 +--- + +Edge inherits from item. The functions of Item are also available for Edge. This document will only introduce the common functions for Edge class. All the built-in edges can be found in [Built-in Edges Doc](/en/docs/manual/middle/elements/edges/defaultEdge) and [demo](/en/examples/item/defaultEdges), Custom Edge can be found in [Custom Edge Doc](/en/docs/manual/middle/elements/edges/custom-edge) and [demo](/en/examples/item/customEdge). + +### edge.setSource(source) + +Set the source item (node) of the edge. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | ---- | -------- | ------------------------ | +| source | Node | true | The item of source node. | + +**Usage** + +```javascript +const edge = new Edge({ + // ... +}); + +const node = new Node({ + // ... +}); + +edge.setSource(node); +``` + +### edge.setTarget(target) + +Set the target item (node) of the edge. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | ---- | -------- | ------------------------ | +| target | Node | true | The item of target node. | + +**Usage** + +```javascript +edge.setTarget(node); +``` + +### edge.getSource() + +Get the current source item (node) of the edge. + +**Return** + +- The type of return value: Node; +- Returns the item of source node. + +**Usage** + +```javascript +const node = edge.getSource(); +``` + +### edge.getTarget() + +Get the current target item (node) of the edge. + +**Return** + +- The type of return value: Node; +- Returns the item of target node. + +**Usage** + +```javascript +const node = edge.getTarget(); +``` diff --git a/packages/site/docs/api/Items/edgeMethods.zh.md b/packages/site/docs/api/Items/edgeMethods.zh.md new file mode 100644 index 0000000000..1cd013cc97 --- /dev/null +++ b/packages/site/docs/api/Items/edgeMethods.zh.md @@ -0,0 +1,76 @@ +--- +title: edge 实例方法 +order: 2 +--- + +Edge 继承自 Item。所以 Item 的方法在 Edge 实例中都可以使用。本文仅介绍 Edge 类的通用方法,内置边见 [内置边文档](/zh/docs/manual/middle/elements/edges/defaultEdge) 和 [demo](/zh/examples/item/defaultEdges),自定义节点见 [自定义边文档](/zh/docs/manual/middle/elements/edges/custom-edge) 和 [demo](/zh/examples/item/customEdge)。 + +### edge.setSource(source) + +设置边的起始节点。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | ---- | -------- | ------------ | +| source | Node | true | 起始节点实例 | + +**用法** + +```javascript +const edge = new Edge({ + // ... +}); + +const node = new Node({ + // .. +}); + +edge.setSource(node); +``` + +### edge.setTarget(target) + +设置边的终止节点。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | ---- | -------- | ------------ | +| target | Node | true | 终止节点实例 | + +**用法** + +```javascript +edge.setTarget(node); +``` + +### edge.getSource() + +获取当前边的起始节点。 + +**返回值** + +- 返回值类型:Node; +- 返回值为起始节点的实例。 + +**用法** + +```javascript +const node = edge.getSource(); +``` + +### edge.getTarget() + +获取当前边的终止节点。 + +**返回值** + +- 返回值类型:Node; +- 返回值为终止节点的实例。 + +**用法** + +```javascript +const node = edge.getTarget(); +``` diff --git a/packages/site/docs/api/Items/edgeProperties.en.md b/packages/site/docs/api/Items/edgeProperties.en.md new file mode 100644 index 0000000000..6f58e33545 --- /dev/null +++ b/packages/site/docs/api/Items/edgeProperties.en.md @@ -0,0 +1,72 @@ +--- +title: Edge Model Properties +order: 7 +--- + +## Common Property + +Except for the common properties, there are special configurations for each type of built-in Edges. The `style`s of them depend on their keyShape. The common properties for built-in Edges can be refered to: + +### id + + _String_ **required** + +The id of the edge, **MUST** be a unique string + +### source + + _String | Number_ **optional** + +The id of the source node. + +### target + + _String | Number_ **optional** + +The id of the target node. + +### type + + _String_ **optional** _default:_ `'line'` + +The type of the edge. It can be the type of a Built-in Edge, or a custom Edge. 'line' by default + +### sourceAnchor + + _Number_ **optional** + +The index of link points on the source node. The link point is the intersection of the edge and related node. + +### targetAnchor + + _Number_ **optional** + +The index of link points on the target node. The link point is the intersection of the edge and related node. + +### style + + _Object_ **optional** + +The edge style. `style` is an object to configure the stroke color, shadow, and so on. The complete configurations is listed in: [Shape Properties](/en/docs/api/shapeProperties). + +### label + + _String_ **optional** + +label is a string which indicates the content of the label. + +### labelCfg + +labelCfg is an object to configure the label. The commonly used configurations of labelCfg: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| refX | false | Number | x offset of the label | +| refY | false | Number | y offset of the label | +| position | false | String | The relative position to the edge. Options: `start`, `middle`, and `end`. `middle` by default. | +| autoRotate | false | Boolean | Whether to activate ratating according to the edge automatically. `false` by default | +| style | false | Object | The style property of the label. The complete configurations for the label style is listed in [Shape Style Properties - Text](/en/docs/api/shapeProperties/#text) | + +## Properties for Specific Built-in Edges + +The special properties for each built-in Edges can be found in the subdocuments of [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge). diff --git a/packages/site/docs/api/Items/edgeProperties.zh.md b/packages/site/docs/api/Items/edgeProperties.zh.md new file mode 100644 index 0000000000..d9949c4bbb --- /dev/null +++ b/packages/site/docs/api/Items/edgeProperties.zh.md @@ -0,0 +1,70 @@ +--- +title: 边配置项 +order: 7 +--- + +## 边通用属性 + +### id + + _String_ **required** + +边唯一 ID,**必须**是唯一的 string。 + +### source + + _String | Number_ **optional** + +起始点 id 。 + +### target + + _String | Number_ **optional** + +结束点 id 。 + +### type + + _String_ **optional** _default:_ `'line'` + +指定边的类型,可以是内置边的类型名称,也可以是自定义边的名称。默认为 `'line'`。 + +### sourceAnchor + + _Number_ **optional** + +边的起始节点上的锚点的索引值。 + +### targetAnchor + + _Number_ **optional** + +边的终止节点上的锚点的索引值。 + +### style + + _Object_ **optional** + +通过 `style` 配置来修改边的填充色、边框颜色、阴影等属性,具体配置属性见:[图形样式属性](/zh/docs/api/shapeProperties)。 + +### label + + _String_ **optional** + +文本文字,如果没有则不会显示。 + +### labelCfg + +`label` String 类型。标签文本的文字内容。
`labelCfg` Object 类型。配置标签文本。下面是 `labelCfg` 对象中的常用配置项: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| refX | false | Number | 标签在 x 方向的偏移量 | +| refY | false | Number | 标签在 y 方向的偏移量 | +| position | false | String | 文本相对于边的位置,目前支持的位置有:`'start'`,`'middle'`,`'end'`。默认为`'middle'`。 | +| autoRotate | false | Boolean | 标签文字是否跟随边旋转,默认 `false` | +| style | false | Object | 标签的样式属性,具体配置项参见统一整理在 [图形样式属性 - Text 图形](/zh/docs/api/shapeProperties/#文本-text) | + +## 内置边的特有属性 + +各个内置节点的特有属性见 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge) 目录下各文档。 diff --git a/packages/site/docs/api/Items/itemMethods.en.md b/packages/site/docs/api/Items/itemMethods.en.md new file mode 100644 index 0000000000..cb34aa8f4c --- /dev/null +++ b/packages/site/docs/api/Items/itemMethods.en.md @@ -0,0 +1,438 @@ +--- +title: item.* +order: 0 +--- + +Item is the object of node / edge in G6. + +## Update + +### item.update(model) + +Update the item according to the data model. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | --------------------------- | +| model | Object | true | The data model of the item. | + +Tips: `model` contains: + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| id | String | true | The unique id of the item. | +| style | Object | false | The style of the item. | +| type | String | false | The type of the item. The default type for edge is 'line', the default type for node is 'circle'. The default shapes will take effect when `type` is null. | +| label | String | false | The label of the item. A label will be rendered if it exists. | + +**Usage** + +```javascript +const model = { + id: 'node', + type: 'rect', + label: 'node', + style: { + fill: 'red', + }, +}; + +item.update(model); +``` + +### item.refresh() + +Refresh the item with its positions and style in the item's data model. This operation will clear the cache in the same time. + +It is usually called after: + +- The data model of item is changed; +- The positions of endpoints of an edge is changed. + +**Usage** + +```javascript +item.refresh(); +``` + +### item.updatePosition(cfg) + +Update the position of the item. We recommend to call this function for single item to avoid repainting the whole canvas. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| cfg | Object | true | The configurations of the item, including `x` and `y`. The x and y value in source data model will take effect if there are no `x` and `y` in `cfg`. | + +**Usage** + +```javascript +const cfg = { + x: 100, + y: 200, +}; +// There are x and y in cfg, so the operation below will update the position +item.updatePosition(cfg); + +const cfg1 = { + name: 'abc', + dept: 'antv', +}; +// There are no x and y in cfg, the operation below will use x and y in item.getModel() +item.updatePosition(cfg1); +``` + +## Destroy + +### item.destroy() + +Destroy an item, including stopping the animation, deleting the items in a group, clearing the configurations, setting the `destroyed` to be `true`, and so on. + +**Usage** + +```javascript +item.destroy(); +``` + +## Common Usage + +### item.getBBox() + +Get the **bounding box** of the item. + +**Return** + +- The type of return value: Object. + +The return value includes: + +| Name | Type | Description | +| ------- | ------ | ------------------------------------------- | +| x | number | The x coordinate of view port. | +| y | number | The y coordinate of view port. | +| width | number | The width of the bbox. | +| height | number | The height of the bbox. | +| centerX | number | The x coordinate of the center of the bbox. | +| centerY | number | The y coordinate of the center of the bbox. | + +**Usage** + +```javascript +item.getBBox(); +``` + +### item.getContainer() + +Get the container of the item. + +**Return** + +- The type of return value: G.Group; +- Return the graphics group where the item in. + +**Usage** + +```javascript +// Get the container of the item +const group = item.getContainer(); + +// Equals to +const group = item.get('group'); +``` + +### item.getKeyShape() + +Get the key shape of the item. `keyShape` is used for calculating the node size, edge length, and so on. + +**Return** + +- The type of return value: G.Shape; +- Return the `keyShape` of the item. + +**Usage** + +```javascript +// Get the keyShape of the item +const keyShape = item.getKeyShape(); + +// Equals to +const keyShape = item.get('keyShape'); +``` + +### item.getModel() + +Get the data model of the item. + +**Return** + +- The type of return value: Object; +- Return the data model of the item. + +**Usage** + +```javascript +// Get the data model of the item +const model = item.getModel(); + +// Equals to +const model = item.get('model'); +``` + +### item.getType() + +Get the type of the item. + +**Return** + +- The type of return value: String; +- Return the type of the item. It might be `'node'` or `'edge'`. + +**Usage** + +```javascript +// Get the type of the item +const type = item.getType(); + +// Equals to +const type = item.get('type'); +``` + +### item.enableCapture(enable) + +Whether to enable the item to be picked and enable its interaction events. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | ------- | -------- | ----------------------------------- | +| enable | Boolean | true | The flag to enable if it is `true`. | + +**Usage** + +```javascript +// Do not allow the item response interaction events +item.enableCapture(false); + +// Allow the item to response the interaction events +item.enableCapture(true); +``` + +### item.clearCache() + +Clear the cache. It is usually called after updating or refreshing operation. + +**Usage** + +```javascript +// Clear the cache +item.clearCache(); +``` + +## State + +### item.show() + +Show the item. Show the item itself. Different from that, when the item is a node, [graph.showItem(item)](/en/docs/api/Graph#showitemitem) will show the node and its related edges in the same time. + +**Usage** + +```javascript +item.show(); +``` + +### item.hide() + +Hide the item. Hide the item itself. Different from that, when the item is a node, [graph.hideItem(item)](/en/docs/api/Graph#hideitemitem) will hide the node and its related edges in the same time. + +**Usage** + +```javascript +item.hide(); +``` + +### item.changeVisibility(visible) + +Change the visibility of the item. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| visible | Boolean | true | The flag to hide or show the item. `true` means show the item, `false` means hide the item. | + +**Usage** + +```javascript +// Show the item +item.changeVisibility(true); + +// Hide the item +item.changeVisibility(false); +``` + +### item.isVisible() + +Query the visibility of the item. + +**Return** + +- The type of return value: Boolean; +- `true` means the item is visibile. The item is invisible otherwise. + +**Usage** + +```javascript +const visible = item.isVisible(); +``` + +### item.toFront() + +Set the visual level / zindex to the front to avoid being overlapped by other items. + +**Usage** + +```javascript +item.toFront(); +``` + +### item.toBack() + +Set the visual level / zindex to the back. + +**Usage** + +```javascript +item.toBack(); +``` + +### item.setState(state, enable) + +Update the state of the item. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | ------- | -------- | --------------------------------------------------------- | +| state | String | true | The state name of the item, e.g. `'selected'`, `'hover'`. | +| enable | Boolean | true | The flag to enable the state if it is `true`. | + +**Usage** + +```javascript +item.setState('selected', true); +item.setState('actived', false); +``` + +### item.clearStates(states) + +Clear all the states of the item. If the `states` is null, this operatcion will clear **the first** state of the item by default. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | -------------- | -------- | -------------------------------------- | +| states | String / Array | true | The names of the states to be cleared. | + +**Usage** + +```javascript +// Clear the state 'selected' +item.clearStates('selected'); + +// Clear the states 'active' and 'hover' +item.clearStates(['actived', 'hover']); +``` + +### item.getStates() + +Get all the states of the item. + +**Return** + +- The type of return value: Array; +- Returns an array of strings, which are the states of the item. + +**Usage** + +```javascript +// Get all the states of the item +const states = item.getStates(); +``` + +### item.hasState(state) + +Query the `state` value of the node. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | --------------- | +| state | String | true | The state name. | + +**Return** + +- The type of return value: Boolean; +- Returns `true` if the item has the `state`. `false` otherwise. + +**Usage** + +```javascript +// Query value of state 'hover' +const state = item.hasState('hover'); +``` + +## Style + +### item.getStateStyle(state) + +Get the style of the item. The global style, default style, and custom style will be mixed in the return value. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | --------------- | +| state | String | true | The state name. | + +**Return** + +- The type of return value: Object; +- The global style, default style, and custom style will be mixed in the return value. + +**Usage** + +```javascript +// Get the item's style of state 'selected' +const style = item.getStateStyle('selected'); +``` + +### item.getOriginStyle() + +Get the keyShape's style of the item. + +**Return** + +- The type of return value: Object | undefined; +- Returns the style of the `keyShape` if it exists. Returns `undefined` otherwise. + +**Usage** + +```javascript +const style = item.getOriginStyle(); +``` + +### item.getCurrentStatesStyle() + +Get the item's styles of all the states. + +**Return** + +- The type of return value: Object; +- Returns the item's styles of all the states. + +**Usage** + +```javascript +const styles = item.getCurrentStatesStyle(); +``` diff --git a/packages/site/docs/api/Items/itemMethods.zh.md b/packages/site/docs/api/Items/itemMethods.zh.md new file mode 100644 index 0000000000..236f301203 --- /dev/null +++ b/packages/site/docs/api/Items/itemMethods.zh.md @@ -0,0 +1,446 @@ +--- +title: 元素实例方法 +order: 0 +--- + +Item 是 G6 中绘图元素实例,目前包含节点和边的实例。对于实例的变更建议在 graph 上进行。 + + +## 更新 + +### item.update(model) + + +根据元素数据项,更新元素。 + +**参数** + + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | -------------------------- | +| model | Object | true | 元素描述项,包括数据和样式 | + + + 提示: + +其中参数 model 可包括以下属性,下面属性的详细描述参见 [元素配置项](/zh/docs/api/Items/itemProperties): + + +| 名称 | 类型 | 是否必选 | 描述 | +| -------- | ------ | -------- | ------------------------------------------ | +| style | Object | false | 元素样式 | +| type | String | false | 元素的类型,不传则使用默认值 | +| label | String | false | 元素的文本标签,有该字段时默认会渲染 label | +| labelCfg | Object | false | 元素文本标签的样式 | + + +**用法** + +```javascript +const model = { + id: 'node', + type: 'rect', + label: 'node', + style: { + fill: 'red', + }, +}; + +item.update(model); +``` + +### item.refresh() + +刷新元素,包括更新元素位置,更新元素样式,清除之前的缓存。 + +一般在以下情况时,会刷新元素: + +- item model 被改变; +- 边的位置发生改变,需要重新计算边。 + +**用法** + +```javascript +item.refresh(); +``` + +### item.updatePosition(cfg) + +更新元素位置,避免整体重新绘制。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| cfg | Object | true | 元素配置项,包括 `x`、`y` 属性,如果参数中无 `x`、`y` 属性,则更新时使用数据项中的值。 | + +**用法** + +```javascript +const cfg = { + x: 100, + y: 200, +}; +// 由于 cfg 中存在 x 与 y,则下面操作将会使用 cfg 中的 x、y 坐标 +item.updatePosition(cfg); + +const cfg1 = { + name: 'abc', + dept: 'antv', +}; +// 由于 cfg 中不存在 x 与 y,下面才做将会使用 item.getModel() 中的 x、y 坐标值 +item.updatePosition(cfg1); +``` + +## 销毁 + +### item.destroy() + +销毁元素,主要包括停止动画、删除 group 中的所有元素、清空配置项、设置 `destroyed` 为 `true` 等操作。 + +**用法** + +```javascript +item.destroy(); +``` + +## 通用 + +### item.getBBox() + +获取元素的包围盒。 + +**返回值** + +- 返回值类型:Object。 + +返回值对象包括以下属性: + +| 名称 | 类型 | 描述 | +| ------- | ------ | ------------- | +| x | number | 视口 x 坐标 | +| y | number | 视口 y 坐标 | +| width | number | bbox 宽度 | +| height | number | bbox 高度 | +| centerX | number | 中心点 x 坐标 | +| centerY | number | 中心点 y 坐标 | + +**用法** + +```javascript +item.getBBox(); +``` + +### item.getContainer() + +获取元素的容器。 + +**返回值** + +- 返回值类型:G.Group; +- 返回元素所在的 graphics group。 + +**用法** + +```javascript +// 获取元素的容器 +const group = item.getContainer(); + +// 等价于 +const group = item.get('group'); +``` + +### item.getKeyShape() + +获取元素的关键形状,用于计算节点大小、连线截距等。 + +**返回值** + +- 返回值类型:G.Shape; +- 返回元素的 keyShape。 + +**用法** + +```javascript +// 获取元素的 keyShape +const keyShape = item.getKeyShape(); + +// 等价于 +const keyShape = item.get('keyShape'); +``` + +### item.getModel() + +获取元素的数据模型。 + +**返回值** + +- 返回值类型:Object; +- 返回元素的数据模型。 + +**用法** + +```javascript +// 获取元素的数据模型 +const model = item.getModel(); + +// 等价于 +const model = item.get('model'); +``` + +### item.getType() + +获取元素的类型。 + +**返回值** + +- 返回值类型:String; +- 返回元素的类型,可能是 `'node'` 或 `'edge'`。 + +**用法** + +```javascript +// 获取元素的类型 +const type = item.getType(); + +// 等价于 +const type = item.get('type'); +``` + +### item.enableCapture(enable) + +是否拾取及触发该元素的交互事件。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | ------- | -------- | --------------------------------------------------------------- | +| enable | Boolean | true | 是否允许该元素响应事件的标识,如果为 `true`,则允许,否则不允许 | + +**用法** + +```javascript +// 不允许元素响应事件 +item.enableCapture(false); + +// 允许元素响应事件 +item.enableCapture(true); +``` + +### item.clearCache() + +更新或刷新等操作后,清除缓存。 + +**用法** + +```javascript +// 清除缓存 +item.clearCache(); +``` + +## 状态 + +### item.show() + +显示元素。只显示 item 自身,若需要在显示节点的同时显示相关边,应调用 [graph.showItem(item)](/zh/docs/api/Graph#showitemitem)。 + +**用法** + +```javascript +item.show(); +``` + +### item.hide() + +隐藏元素。只隐藏 item 自身,若需要在隐藏节点的同时隐藏相关边,应调用 [graph.hideItem(item)](/zh/docs/api/Graph#hideitemitem)。 + +**用法** + +```javascript +item.hide(); +``` + +### item.changeVisibility(visible) + +更改元素是否显示。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------- | ------- | -------- | ------------------------------------------- | +| visible | Boolean | true | 是否显示元素,`true` 为显示,`false` 为隐藏 | + +**用法** + +```javascript +// 显示元素 +item.changeVisibility(true); + +// 隐藏元素 +item.changeVisibility(false); +``` + +### item.isVisible() + +查询元素显示状态。 + +**返回值** + +- 返回值类型:Boolean; +- 返回值为 true,则表示当前元素处于显示状态,否则处于隐藏状态。 + +**用法** + +```javascript +const visible = item.isVisible(); +``` + +### item.toFront() + +将元素的层级设置到最上层,即当有元素重叠时,将元素置于顶层。 + +**用法** + +```javascript +item.toFront(); +``` + +### item.toBack() + +将元素的层级设置到最下层,即当有元素重叠时,将元素置于底层。 + +**用法** + +```javascript +item.toBack(); +``` + +### item.setState(state, enable) + +更新元素的状态。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | ------- | -------- | ---------------------------------------------------------- | +| state | String | true | 元素的状态名,如 `'selected'`、`'hover'` | +| enable | Boolean | true | 是否启用状态的标识,为 `true` 表示启用该状态,否则不启用。 | + +**用法** + +```javascript +item.setState('selected', true); +item.setState('actived', false); +``` + +### item.clearStates(states) + +清除指定的状态,如果不传 states ,则默认清除**第一个**状态。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | -------------- | -------- | ------------------ | +| states | String / Array | true | 要清除的元素状态名 | + +**用法** + +```javascript +// 清除 'selected' 状态 +item.clearStates('selected'); + +// 清除 'active' 与 'hover' 状态 +item.clearStates(['actived', 'hover']); +``` + +### item.getStates() + +获取当前元素的所有状态。 + +**返回值** + +- 返回值类型:Array; +- 返回当前元素的所有状态,是一个字符串数组,数组中值表示元素的状态。 + +**用法** + +```javascript +// 获取元素的所有状态 +const states = item.getStates(); +``` + +### item.hasState(state) + +判断元素是否具有某种指定的状态。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | ------------ | +| state | String | true | 元素的状态名 | + +**返回值** + +- 返回值类型:Boolean; +- 返回值表示是否具有指定的状态,如果返回 `true`,则说明元素有指定的状态,否则没有。 + +**用法** + +```javascript +// 获取元素的 'hover' 状态值 +const state = item.hasState('hover'); +``` + +## 样式 + +### item.getStateStyle(state) + +获取元素指定状态的样式,返回的样式会将全局样式、默认样式和元素自定义样式合并。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | ------------ | +| state | String | true | 元素的状态名 | + +**返回值** + +- 返回值类型:Object; +- 返回的样式会将全局样式、默认样式和元素自定义样式合并。 + +**用法** + +```javascript +// 获取元素的指定状态的样式 +const style = item.getStateStyle('selected'); +``` + +### item.getOriginStyle() + +获取元素 keyShape 的样式。 + +**返回值** + +- 返回值类型:Object | undefined; +- 如果存在 keyShape ,则返回 `keyShape` 的样式,否则返回 `undefined` 。 + +**用法** + +```javascript +const style = item.getOriginStyle(); +``` + +### item.getCurrentStatesStyle() + +获取当前元素的所有状态的样式。 + +**返回值** + +- 返回值类型:Object; +- 返回值表示当前元素所有状态的样式。 + +**用法** + +```javascript +const styles = item.getCurrentStatesStyle(); +``` diff --git a/packages/site/docs/api/Items/itemProperties.en.md b/packages/site/docs/api/Items/itemProperties.en.md new file mode 100644 index 0000000000..f6afb5511a --- /dev/null +++ b/packages/site/docs/api/Items/itemProperties.en.md @@ -0,0 +1,36 @@ +--- +title: Item Model Properties +order: 5 +--- + +In a graph of G6, there are Node, Edge, and Combo items. Each [item](/en/docs/api/Items/itemMethods) is an instance with a data `model` which defines the styles and configurations of the item. The [Tutorial-Configure the Items](/en/docs/manual/tutorial/elements#configure-the-properties) introduces two ways to configure the items: Configure the items globally when instantiating the Graph; Configure the items in the source data. No matter which way to configure the items, it is configuring the data `model` for each item. This document introduces the configurations of the models of Node, Edge, and Combo. + +### id + + _String_ **required** + +The ID of the item, **MUST** be unique and string + +### style + + _Object_ **optional** + +The style of the item's [keyShape](/en/docs/manual/middle/elements/shape/shape-keyshape). Its properites are related to the type of the keyShape. Refer to [Shape Style Properties](/en/docs/api/shapeProperties) + +### type + + _String_ **optional** + +The type name of the item. 'line' is the default value for Edge, 'circle' for Node, and 'circle' for Combo + +### label + + _String_ **optional** + +The text of the item's label. The text will be rendered if the `label` property exist + +### labelCfg + + _Object_ **optional** + +The configurations for the label. It is different for Node, Combo, and Edge. Check out the following content for more detail diff --git a/packages/site/docs/api/Items/itemProperties.zh.md b/packages/site/docs/api/Items/itemProperties.zh.md new file mode 100644 index 0000000000..5941af924b --- /dev/null +++ b/packages/site/docs/api/Items/itemProperties.zh.md @@ -0,0 +1,36 @@ +--- +title: 元素配置项 +order: 5 +--- + +G6 图上的元素包括节点、边、节点分组 Combo。每一个元素是一个 [item](/zh/docs/api/Items/itemMethods) 实例,而实例中的数据模型 `model` 定义了该元素的样式、配置等。在 [入门教程-元素及其配置](/zh/docs/manual/tutorial/elements#配置属性) 中,我们知道配置元素属性有两种方式:实例化图时全局配置;在数据中配置。无论何种方式,都是在配置每个元素的数据模型 `model`。本文介绍节点、边、节点分组 Combo 的 `model` 配置项。 + +### id + + _String_ **required** + +说明: 元素的标识 ID,**必须**是唯一的 string + +### style + + _Object_ **optional** + +元素 [keyShape](/zh/docs/manual/middle/elements/shape/shape-keyshape) 的样式属性,可配置内容与该 keyShape 的图形类型相关,各图形的具体属性参见[各图形样式属性](/zh/docs/api/shapeProperties) | + +### type + + _String_ **optional** + +元素的类型,不传则使用默认值,节点默认类型为 'circle',边默认类型为 'line',Combo 默认类型为 `circle`。 + +### label + + _String_ **optional** + +元素的文本标签,有该字段时默认会渲染 label 。 + +### labelCfg + + _Object_ **optional** + +元素文本标签的配置项,节点、Combo 与 边的配置不同,详见各子模块内容。 diff --git a/packages/site/docs/api/Items/nodeMethods.en.md b/packages/site/docs/api/Items/nodeMethods.en.md new file mode 100644 index 0000000000..d58784c369 --- /dev/null +++ b/packages/site/docs/api/Items/nodeMethods.en.md @@ -0,0 +1,220 @@ +--- +title: node.* +order: 1 +--- + +Node inherits from item. The functions of Item are also available for Node. This document will only introduce the common functions for Node class. All the built-in nodes can be found in [Built-in Nodes Doc](/en/docs/manual/middle/elements/nodes/defaultNode) and [demo](/en/examples/item/defaultNodes), Custom Node can be found in [Custom Node Doc](/en/docs/manual/middle/elements/nodes/custom-node) and [demo](/en/examples/item/customNode). + +### node.lock() + +Lock the current node. The locked node will not response the drag event any more. + +Tips: the locked node still can be moved while dragging and zooming the canvas. If you want to fix the node in these two situations, please refer to [Fix the Locked Node While Dragging](/en/docs/manual/middle/elements/methods/lock-node#fix-the-locked-node-while-dragging) and [Fix the Locked Node while Zooming](/en/docs/manual/middle/elements/methods/lock-node#fix-the-locked-node-while-zooming) to register a custom Behavior. + +**Usage** + +```javascript +const node = graph.findById('node'); +node.lock(); +``` + +### node.unlock() + +Unlock the locked node. + +**Usage** + +```javascript +const node = graph.findById('node'); +node.unlock(); +``` + +### node.hasLocked() + +Query the lock state of the node. + +**Return** + +- The type of return value: Boolean; +- The node is locked if it returns `true`, unlocked otherwise. + +**Usage** + +```javascript +const node = graph.findById('node'); +const hasLocked = node.hasLocked(); +``` + +### node.getNeighbors(type) + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| type | 'source' / 'target' / undefined | false | The type of the neighbors, 'source': only return the source nodes; 'target': only return the target nodes, undefined: return all of the neighbors | + +**Return** + +- Type of the return value: Array; +- Return a list of node items. + +**Usage** + +```javascript +const neighbors = node.getNeighbors('source'); +``` + +### node.getEdges() + +Get the related edges (the node is the source or the target of the edge) of the node. + +**Return** + +- The type of return value: Edge[]; +- Returns the set of related edge items. + +**Usage** + +```javascript +// Get the related edges +const edges = node.getEdges(); +``` + +### node.getInEdges() + +Get the related in-edges, whose target is the node. + +**Return** + +- The type of return value: Edge[]; +- Returns the set of related in-edges. + +**Usage** + +```javascript +// Get the related in-edges +const edges = node.getInEdges(); +``` + +### node.getOutEdges() + +Get the related out-edges, whose source is the node. + +**Return** + +- The type of return value: Edge[]; +- Return the set of related out-edges. + +**Usage** + +```javascript +// Get the related out-edges +const edges = node.getOutEdges(); +``` + +### node.getAnchorPoints() + +Get all the anchor points of the node. + +**Return** + +- The type of return value: Array; +- The data structure of the return value: + +```javascript +[ + [100, 105], + [200, 105] +]; +``` + +**Usage** + +```javascript +// Get the anchor points of the node +const anchor = node.getAnchorPoints(); +``` + +### node.getLinkPoint(point) + +Get the nearest anchor point of the node to `point`. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | ------------------------------------- | +| point | Object | true | A point with x and y ouside the node. | + +**Return** + +- The type of return value: Object; +- Returns (x, y) of the found anchor point. If there is no anchor point found, returns the center of the node. + +**Usage** + +```javascript +const point = { + x: 100, + y: 105, +}; +// Get the anchor point which is nearest to the point +const linkPoint = node.getLinkPoint(point); +``` + +### node.getLinkPointByAnchor(index) + +Get the (x, y) of the anchor point with the `index`. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | ------------------------------ | +| index | Number | true | The index of the anchor point. | + +**Return** + +- The type of return value: Object; +- Returns the (x, y) of found anchor point. + +**Usage** + +```javascript +// Get the first anchor point of the node +const anchor = node.getLinkPointByAnchor(0); +``` + +### node.addEdge(edge) + +Add the `edge` to the node. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ---- | -------- | --------------------- | +| edge | Edge | true | The item of the edge. | + +**Usage** + +```javascript +const edge = new Edge({ + // TODO +}); +node.addEdge(edge); +``` + +### node.removeEdge(edge) + +Remove the `edge` from the node. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------------- | +| edge | Edge | true | The item of Edge. | + +**Usage** + +```javascript +const edge = graph.findById('edge1'); +node.removeEdge(edge); +``` diff --git a/packages/site/docs/api/Items/nodeMethods.zh.md b/packages/site/docs/api/Items/nodeMethods.zh.md new file mode 100644 index 0000000000..c8497fe5e1 --- /dev/null +++ b/packages/site/docs/api/Items/nodeMethods.zh.md @@ -0,0 +1,226 @@ +--- +title: node 实例方法 +order: 1 +--- + +Node 继承自 Item。所以 Item 上面的方法在 Node 实例中都可以调用。本文仅介绍 Node 类的通用方法,内置节点见 [内置节点文档](/zh/docs/manual/middle/elements/nodes/defaultNode) 和 [demo](/zh/examples/item/defaultNodes),自定义节点见 [自定义节点文档](/zh/docs/manual/middle/elements/nodes/custom-node) 和 [demo](/zh/examples/item/customNode)。 + +### node.lock() + +> 3.1.4 版本新增 + +锁定当前节点,锁定节点后,该节点不再响应拖动节点的事件。 + +提示:锁定节点后,拖动画布和缩放画布的操作依然对该节点有效。如果想在锁定节点后,不响应拖动画布和缩放的事件,需要自定义拖动画布和缩放的 Behavior,具体可参考 [锁定节点不响应拖动画布的事件](/zh/docs/manual/middle/elements/methods/lock-node#拖动画布时候不处理锁定的节点) 和 [锁定节点不响应缩放事件](/zh/docs/manual/middle/elements/methods/lock-node#缩放画布时不处理锁定的节点)。 + +**用法** + +```javascript +const node = graph.findById('node'); +node.lock(); +``` + +### node.unlock() + +> 3.1.4 版本新增 + +解锁锁定的节点。 + +**用法** + +```javascript +const node = graph.findById('node'); +node.unlock(); +``` + +### node.hasLocked() + +> 3.1.4 版本新增 + +检测节点是否处于锁定状态。 + +**返回值** + +- 返回值类型:Boolean; +- 返回 true 表示当前解锁处于锁定状态,否则表示未锁定。 + +**用法** + +```javascript +const node = graph.findById('node'); +const hasLocked = node.hasLocked(); +``` + +### node.getNeighbors(type) + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| type | 'source' / 'target' / undefined | false | 邻居类型, 'source' 只获取当前节点的源节点,'target' 只获取当前节点指向的目标节点, 若不指定则返回所有类型的邻居 | + +**返回值** + +- 返回值类型:Array; +- 返回值符合要求的节点实例数组。 + +**用法** + +```javascript +const neighbors = node.getNeighbors('source'); +``` + +### node.getEdges() + +获取与当前节点有关联的所有边。 + +**返回值** + +- 返回值类型:Edge[]; +- 返回边实例的集合。 + +**用法** + +```javascript +// 获取与 node 关联的所有边 +const edges = node.getEdges(); +``` + +### node.getInEdges() + +获取与当前节点关联的所有入边。 + +**返回值** + +- 返回值类型:Edge[]; +- 返回入边实例的集合。 + +**用法** + +```javascript +// 获取与 node 关联的所有入边 +const edges = node.getInEdges(); +``` + +### node.getOutEdges() + +获取与当前节点关联的所有出边。 + +**返回值** + +- 返回值类型:Edge[]; +- 返回出边实例的集合。 + +**用法** + +```javascript +// 获取与 node 关联的所有出边 +const edges = node.getOutEdges(); +``` + +### node.getAnchorPoints() + +获取节点上面定义的锚点。 + +**返回值** + +- 返回值类型:Array; +- 返回值的数据结构: + +```javascript +[ + [100, 105], + [200, 105] +]; +``` + +**用法** + +```javascript +// 获取定义在节点上的锚点数据 +const anchor = node.getAnchorPoints(); +``` + +### node.getLinkPoint(point) + +获取距离指定坐标最近的一个锚点。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | ------------------------------------------ | +| point | Object | true | 节点外部的一个点,用于计算交点及最近的锚点 | + +**返回值** + +- 返回值类型:Object; +- 返回值表示连接点的坐标 (x, y),如果没有合适的锚点和连接点,则返回中心点。 + +**用法** + +```javascript +const point = { + x: 100, + y: 105, +}; +// 获取连接点 +const linkPoint = node.getLinkPoint(point); +``` + +### node.getLinkPointByAnchor(index) + +根据锚点索引获取连接点的 x、y 坐标。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | ---------- | +| index | Number | true | 锚点的索引 | + +**返回值** + +- 返回值类型:Object; +- 返回值表示连接点的坐标 (x, y)。 + +**用法** + +```javascript +// 获取定义在节点上的第一个锚点 +const anchor = node.getLinkPointByAnchor(0); +``` + +### node.addEdge(edge) + +添加指定的边到当前节点上。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ---- | -------- | --------- | +| edge | Edge | true | Edge 实例 | + +**用法** + +```javascript +const edge = new Edge({ + // TODO +}); +node.addEdge(edge); +``` + +### node.removeEdge(edge) + +移除与当前节点相关的指定边。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ---- | -------- | --------- | +| edge | Edge | true | Edge 实例 | + +**用法** + +```javascript +const edge = graph.findById('edge1'); +node.removeEdge(edge); +``` diff --git a/packages/site/docs/api/Items/nodeProperties.en.md b/packages/site/docs/api/Items/nodeProperties.en.md new file mode 100644 index 0000000000..ceff1adcbf --- /dev/null +++ b/packages/site/docs/api/Items/nodeProperties.en.md @@ -0,0 +1,68 @@ +--- +title: Node Model Properties +order: 6 +--- + +Except for the common properties, apart from the [Item Model Properities](/en/docs/api/Items/itemProperties.zh.md), there are special configurations for Nodes. The `style`s of them depend on their keyShape. + +## Common Property + +### id + + _String_ **required** + +The ID of the node, **MUST** be a unique string. + +### x + + _Number_ **optional** + +x coordinate. + +### y + + _Number_ **optional** + +y coordinate. + +### type + + _String_ **optional** _default:_: `'circle'` + +The shape type of the node. It can be the type of built-in Node, or the custom Node. `'circle'` by default. + +### size + + _Number | Array_ **optional** _default:_: `20` + +The size of the node. + +### anchorPoints + + _Array_ **optional** + +The interactions of the node and related edges. It can be null. [0, 0] represents the anchor on the left top; [1, 1] represents the anchor ont he right bottom. + +### style + + _Object_ **optional** + +The node style. `style` is an object to configure the filling color, stroke color, shadow, and so on. Please refer to: [Shape Properties](/en/docs/api/shapeProperties)。 + +### label + + _String_ **optional** + +The label text of the node. + +### labelCfg + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| position | false | String | The relative positions to the node. Options: `'center'`, `'top'`, `'left'`, `'right'`, `'bottom'`. `'center'` by default. | +| offset | false | Number | The offset value of the label. When the `position` is `'bottom'`, the value is the top offset of the node; When the `position` is `'left'`, the value is the right offset of the node; it is similar with other `position`. | +| style | false | Object | The style property of the label. The complete configurations for the label style is listed in [Shape Style Properties - Text](/en/docs/api/shapeProperties/#text) | + +## Properties for Specific Built-in Nodes + +The special properties for each built-in Nodes can be found in the subdocuments of [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode). diff --git a/packages/site/docs/api/Items/nodeProperties.zh.md b/packages/site/docs/api/Items/nodeProperties.zh.md new file mode 100644 index 0000000000..aa7f8be2d1 --- /dev/null +++ b/packages/site/docs/api/Items/nodeProperties.zh.md @@ -0,0 +1,68 @@ +--- +title: 节点配置项 +order: 6 +--- + +节点继承自元素类。除了[元素通用属性](/zh/docs/api/Items/itemProperties.zh.md)外,每种节点拥有一些特有的属性。 + +## 内置节点的通用属性 + +### id + + _String_ **required** + +节点唯一 ID,**必须**是唯一的 string。 + +### x + + _Number_ **optional** + +x 坐标。 + +### y + + _Number_ **optional** + +y 坐标。 + +### type + + _String_ **optional** _default:_: `'circle'` + +指定节点类型,内置节点类型名称或自定义节点的名称。默认为 `'circle'`。 + +### size + + _Number | Array_ **optional** _default:_: `20` + +节点的大小。 + +### anchorPoints + + _Array_ **optional** + +指定边连入节点的连接点的位置(相对于该节点而言),可以为空。例如: `[0, 0]`,代表节点左上角的锚点,`[1, 1]`,代表节点右下角的锚点。 + +### style + + _Object_ **optional** + +通过 `style` 配置来修改节点关键图形的填充色、边框颜色、阴影等属性,具体配置属性见:[图形样式属性](/zh/docs/api/shapeProperties)。 + +### label + + _String_ **optional** + +文本文字。 + +### labelCfg + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| position | false | String | 文本相对于节点的位置,目前支持的位置有:`'center'`,`'top'`,`'left'`,`'right'`,`'bottom'`。默认为 `'center'`。modelRect 节点不支持该属性 | +| offset | false | Number | 文本的偏移,`position` 为 `'bottom'` 时,文本的上方偏移量;`position` 为 `'left'` 时,文本的右方偏移量;以此类推在其他 `position` 时的情况。modelRect 节点的 `offset` 为左边距 | +| style | false | Object | 标签的样式属性,具体配置项参见统一整理在 [图形样式属性 - Text 图形](/zh/docs/api/shapeProperties/#文本-text) | + +## 内置节点的特有属性 + +各个内置节点的特有属性见 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 目录下各文档。 diff --git a/packages/site/docs/api/Plugins.en.md b/packages/site/docs/api/Plugins.en.md new file mode 100644 index 0000000000..4a97c87ddf --- /dev/null +++ b/packages/site/docs/api/Plugins.en.md @@ -0,0 +1,1007 @@ +--- +title: Plugins +order: 14 +--- + +There are several plugins in G6 which can be used for G6's graph or other applications. + +- [Legend](#legend) *supported by v4.3.0 and later versions* +- [SnapLine](#snapline) *supported by v4.3.0 and later versions* +- [Grid](#grid) +- [Minimap](#minimap) +- [Edge Bundling](#edge-bundling) +- [Menu](#menu) +- [ToolBar](#toolbar) +- [TimeBar](#timebar) +- [Tooltip](#tooltip) +- [Fisheye](#fisheye-lens) +- [EdgeFilterLens](#edge-filter-lens) + +## Configure to Graph + +Instantiate the plugin and configure the minimap onto the instance of Graph: + +```javascript +// Instantialize the Grid plugin +const grid = new G6.Grid(); +// Instantialize the Minimap plugin +const minimap = new G6.Minimap(); +const graph = new G6.Graph({ + //... Other configurations + plugins: [grid, minimap], // Configure Grid and Minimap to the graph +}); +``` + + +## Legend + +Legend is a built-in legend plugin for G6. It is useful for npde/edge type demonstration, and the end-users are able to interact with the legend to highlight and filter the items on the graph. *supported after v4.3.0*. + +img + +### Configuration + +| Name | Type | Description | +| --- | --- | --- | +| data | GraphData | The data for the legend, not related to the data of the graph. The legend for nodes currently supports `'circle'`, `'rect'`, and `'ellipse'`. The legend for edges currently supports `'line'`, `'cubic'`, and `'quadratic'`. `type` for each data means the type of the legend item, and the `order` could be assigned to each node/edge data for ordering in a legend group | +| position | 'top' / 'top-left' / 'top-right' / 'right' / 'right-top' / 'right-bottom' / 'left' / 'left-top' / 'left-bottom' / 'bottom' / 'bottom-left' / 'bottom-right' | The relative of the position to the canvas. `'top'` by default, which means the legend area is on the top of the canvas | +| padding | number / number[] | The inner distance between the content of the legend to the border of the legend area. Array with four numbers means the padding to the top, right, bottom, and left responsively | +| margin | number / number[] | The outer distance between the legend area to the border of the canvas. Array with four numbers means the distance to the top, right, bottom, and left responsively. Only the top distance takes effect when `position:'top'`, situations for other `position` configurations are similar to it | +| offsetX | number | The x-axis offset for the legend area, it is useful when you want to adjust the position of the lenged slightly | +| offsetY | number | The y-axis offset for the legend area, it is useful when you want to adjust the position of the lenged slightly | +| containerStyle | ShapeStyle | The style for the background rect, the format is similar as [rect shape style](/en/docs/api/shapeProperties#rect) | +| horiSep | number | The horizontal seperation of the legend items | +| vertiSep | number | The vertical seperation of the legend items | +| layout | 'vertical' / 'horizontal' | The layout of the legend items. `'horizontal'` by default | +| align | 'center' / 'right' / 'left' | The alignment of the legend items. `'center'` by default | +| title | string | The title string for the legend, the style of the title could be configured by `titleConfig` | +| titleConfig | object | The style of the legend title, detail configurations are shown in following lines | +| titleConfig.position | 'center' / 'right' / 'left' | The alignment of the title to the legend content. `'center'` by default | +| titleConfig.offsetX | number | The x-axis offset for the legend title, it is useful when you want to adjust the position of the title slightly | +| titleConfig.offsetY | number | The y-axis offset for the legend title, it is useful when you want to adjust the position of the title slightly | +| titleConfig[key] | ShapeStyle | Other styles for the text, configurations are same as [text shape style](/en/docs/api/shapeProperties#text) | +| filter | object | Configurations for the graph item filtering while the end-user interacting with the legend items. Detials are shown in the following lines | +| filter.enable | boolean | Whether allow filtering the items in the main graph while the end-user interaction with the legend items. `false` by default | +| filter.multiple | boolean | Whether support active multiple types of legend items, `false` by default, which means only one type of legend item will be activated in the same time. If it is `true`, multiple items could be activated only when the `filter.trigger` is `'click'` | +| filter.trigger | 'click' / 'mouseenter' | The interaction way to the legend items. `click` by default, which means while the end-user clicking a legend item, the legend item and corresponding filtered items on the main graph will be activated | +| filter.legendStateStyles | { active?: ShapeStyle, inactive?: ShapeStyle | The state styles for the legend items while filtering, inluding `filter.legendStateStyles.active` and `filter.legendStateStyles.inactive`. The type of each one is `ShapeStyle`. Similar to the `nodeStateStyles` of Graph | +| filter.graphActiveState | string | The activate state name for the items on the main graph. When a lenged item is activated, the corresponding items of the main graph will be set to `filter.graphActiveState`, `'active'` by default. And you should assign the state style for this state name on Graph | +| filter.graphInactiveState | string | The inactivate state name for the items on the main graph. When a lenged item is inactivated, the corresponding items of the main graph will be set to `filter.graphInactiveState`, `'inactive'` by default. And you should assign the state style for this state name on Graph | +| filter.filterFunctions | { [key: string]: (d) => boolean; } | Since the data of the legend is not related to the main graph, you should configure filtering functions for each legend item type. The `key` is corresponding to the `type` of the legend item, and the value is a function. For the function, the parameter is the item data of the main graph, and the return value is a boolean which means whether the item of the main graph should be activated | + +## SnapLine + +SnapLine is a built-in components in G6. *supported by v4.3.0 and later versions*. + +### Configuration + +| Name | Type | Required | Description | +| ------------- | --------------------------------------------- | -------- | --------------------- | +| line | ShapeStyle | false | the style of SnapLine | +| itemAlignType | boolean、'horizontal' 、'vertical'、'center'; | false | the type of SnapLine | + +## Grid + +Grid plugin draws grids on the canvas. + +img + +Use the code in [Configure to Graph](#configure-to-graph) to instantiate grid plugin with the following configurations. + +### Configuration + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ------------------------------------------ | +| img | Srting | false | base64 formatted string for the grid image | + +## Minimap + +Minimap is a tool for quick preview and exploration on large graph. + +img + +It can be configured to adjust the styles and functions. + +### Configuration + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| container | Object | false | The DOM container of Minimap. The plugin will generate a new one if `container` is not defined | +| className | String | false | The className of the DOM element of the Minimap | +| viewportClassName | String | false | The className of the DOM element of the view port on the Minimap | +| type | String | false | Render type. Options: `'default'`: Render all the graphics shapes on the graph; `'keyShape'`: Only render the keyShape of the items on the graph to reach better performance; `'delegate'`: Only render the delegate of the items on the graph to reach better performance. Performance: `'default'` < `'keyShape'` < `'delegate'`. `'default'` by default | +| size | Array | false | The size of the Minimap | +| delegateStyle | Object | false | Takes effect when `type` is `'delegate'`. The style of the delegate of the items on the graph | +| hideEdge | Boolean | false | **Supported by v4.7.16** Whether to hide the edges on minimap to enhance the performance | + +The `delegateStyle` has the properties: + +| Name | Type | Required | Description | +| ----------- | ------ | -------- | ----------------------- | +| fill | String | false | Filling color | +| stroke | String | false | Stroke color | +| lineWidth | Number | false | The width of the stroke | +| opacity | Number | false | Opacity | +| fillOpacity | Number | false | Filling opacity | + +## Image Minimap + +The theory of the [Minimap](#minimap) is copy the graphics from the main graph onto the canvas of the minimap, which will lead to double rendering cost. To alleviate this problem, G6 provides another Image Minimap which is drawn by one `` instead of canvas. But you have to provide the `graphImg` which is the url or base64 string of the main graph's screenshot image, and the image is controlled by yourself totally, which means you might need to update the image by calling `minimap.updateGraphImg` manually when the content of the main graph is changed. + +img + +Configure the Image Minimap when instantiating the minimap. + +### Configuration + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| graphImg | String | true | The src or base64 string of the minimap | +| width | Number | false | The width of the minimap. The aspect ratio of the minimap will always be the same as the main graph. The `width`'s priority is higher than `height`, that is, if the `width`is assigned, the `height` will be adjusted to meet the aspect ratio | +| height | Number | false | The height of the minimap. The aspect ratio of the minimap will always be the same as the main graph. If the `width` is not assigned while the `height` is assigned, the `width` will be equal to `height` \* aspect ratio | +| container | Object | false | The DOM container of Minimap. The plugin will generate a new one if `container` is not defined | +| className | String | false | The className of the DOM element of the Minimap | +| viewportClassName | String | false | The className of the DOM element of the view port on the Minimap | +| delegateStyle | Object | false | Takes effect when `type` is `'delegate'`. The style of the delegate of the items on the graph | + +The `delegateStyle` has the properties: + +| Name | Type | Required | Description | +| ----------- | ------ | -------- | ----------------------- | +| fill | String | false | Filling color | +| stroke | String | false | Stroke color | +| lineWidth | Number | false | The width of the stroke | +| opacity | Number | false | Opacity | +| fillOpacity | Number | false | Filling opacity | + +### API + +#### updateGraphImg(img) + +Update the `graphImg` for the minimap. We recommand you to update the graphImg when the main graph is updated. + +Parameters: + +| Name | Type | Required | Description | +| ---- | ------ | -------- | -------------------------------- | +| img | String | true | minimap 的图片地址或 base64 文本 | + +### Usage + +`graphImg` is required when instantiating the Image Minimap. + +```javascript +// Instantiating the Image Minimap +const imageMinimap = new G6.ImageMinimap({ + width: 200, + graphImg: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*eD7nT6tmYgAAAAAAAAAAAABkARQnAQ' +}); +const graph = new G6.Graph({ + //... Other configurations + plugins: [imageMinimap], // configure the imageMinimap +}); + +graph.data(data); +graph.render() + +... // Some operations which update the main graph +imageMinimap.updateGraphImg(img); // Update the minimap's image (generated by yourself) + +``` + +## Edge Bundling + +In complex graph with large number of edges, edge bundling helps you to improve the visual clutter. + +img + +> Edge bundling on American airline graph. Demo Link. Demo Document. + +The edge bundling plugin can be configured to adjust the styles and functions. + +### Configuration + +| Name | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| K | Number | false | 0.1 | The strength of the bundling | +| lambda | Number | false | 0.1 | The initial step length | +| divisions | Number | false | 1 | The initial number of division on each edge. It will be multipled by `divRate` in each cycle | +| divRate | Number | false | 2 | The rate of the divisions increasement. Large number means smoother result, but the performance will be worse when the number is too large | +| cycles | Number | false | 6 | The number of outer interations | +| iterations | Number | false | 90 | The initial number of inner interations. It will be multiplied by `iterRate` in each cycle | +| iterRate | Number | false | 0.6666667 | The rate of the iterations decreasement | +| bundleThreshold | Number | false | 0.6 | The edge similarity threshold for bundling. Large number means the edges in one bundle have smaller similarity, in other words, more edges in one bundle | + +## Menu + +Menu is used to configure the right-click menu on the node. + +### Configuration + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| className | string | null | the class name of the menu dom | +| getContent | (evt?: IG6GraphEvent) => HTMLDivElement / string | img | the menu content,supports DOM or string | +| handleMenuClick | (target: HTMLElement, item: Item, graph?: IGraph) => void | undefined | the callback function when click the menu | +| shouldBegin | (evt: G6Event) => boolean | undefined | whether allow the menu show up, you can return `true` or `false` according to the `evt.item` or `evt.target` | +| offsetX | number | 6 | The x offset of the menu to the parent container | +| offsetY | number | 6 | The y offset of the menu to the parent container | +| itemTypes | string[] | ['node', 'edge', 'combo'] | which types of items the menu takes effect on. E.g. if you want the menu shows up only on node, assign `itemTypes` with ['node'] | +| trigger | 'click' / 'contextmenu' | 'contextmenu' | the trigger for the menu, `'contextmenu'` by default, which means the menu will show up when the end user right click on some item. `'click'` means left click. *'click' is supported by v4.3.2 and later versions* | + +### Usage + +Use G6 build-in menu by default. + +```javascript +// Instantiate Menu plugin +const menu = new G6.Menu(); +const graph = new G6.Graph({ + //... other Configuration + plugins: [menu], +}); +``` + +#### DOM Menu + +```javascript +const menu = new G6.Menu({ + getContent(e) { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = `
    +
  • menu01
  • +
  • menu01
  • +
  • menu01
  • +
  • menu01
  • +
  • menu01
  • +
` + return outDiv + }, + handleMenuClick(target, item) { + console.log(target, item) + }, +}); + +const graph = new G6.Graph({ + //... other Configuration + plugins: [menu], // the Menu plugin +}); +``` + +#### String Menu + +```javascript +const menu = new G6.Menu({ + getContent(evt) { + return `
    +
  • menu02
  • +
  • menu02
  • +
  • menu02
  • +
  • menu02
  • +
  • menu02
  • +
`; + }, + handleMenuClick(target, item) { + console.log(target, item) + }, +}); + +const graph = new G6.Graph({ + //... other Configuration + plugins: [menu], // The Menu plugin +}); +``` + +## ToolBar + +ToolBar has the following operations by default: + +- Undo; +- Redo; +- Zoom-in; +- Zoom-out; +- Fit the View; +- Actual Size. + +### Configuration + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | The container of the ToolBar. It will take use the DOM of the canvas by default | +| className | string | null | The class name of the sub DOM nodes of the ToolBar | +| getContent | (graph?: IGraph) => HTMLDivElement / string | img | The content of the ToolBar | +| handleClick | (code: string, graph: IGraph) => void | undefined | The callback functions for the icons of the ToolBar | +| position | Point | null | The position of the ToolBar | + +### Usage + +#### Default Usage + +ToolBar provides some default operations above. + +```javascript +const toolbar = new G6.ToolBar(); + +const graph = new G6.Graph({ + //... Other configurations + plugins: [toolbar], // Use the ToolBar plugin +}); +``` + +#### Custom ToolBar by String + +```javascript +const tc = document.createElement('div'); +tc.id = 'toolbarContainer'; +document.body.appendChild(tc); + +const toolbar = new G6.ToolBar({ + container: tc, + getContent: () => { + return ` +
    +
  • Add Node
  • +
  • Undo
  • +
+ ` + }, + handleClick: (code, graph) => { + if (code === 'add') { + graph.addItem('node', { + id: 'node2', + label: 'node2', + x: 300, + y: 150 + }) + } else if (code === 'undo') { + // redefine undo operator + toolbar.undo() + toolbar.autoZoom() + } else { + // Other operations remain default + toolbar.handleDefaultOperator(code) + } + } +}); + +const graph = new G6.Graph({ + //... Other configurations + plugins: [toolbar], // Use the ToolBar plugin +}); +``` + +#### Custom ToolBar by DOM + +```javascript +const toolbar = new G6.ToolBar({ + getContent: () => { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = `
    +
  • example 01
  • +
  • example 02
  • +
  • example 03
  • +
  • example 04
  • +
  • example 05
  • +
` + return outDiv + }, + handleClick: (code, graph) => { + + } +}); + +const graph = new G6.Graph({ + //... Other configurations + plugins: [toolbar], // Use the ToolBar plugin +}); +``` +## ToolTip + +ToolTip helps user to explore detail infomations on the node and edge. Do note that, This Tooltip Plugins will replace the tooltip in the built-in behavior after G6 4.0. + +### Configuration + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| className | string | null | Tge class name of the tooltip's container | +| container | HTMLDivElement | null | The container of the Tooltip. The canvas DOM will be used by default | +| getContent | (evt?: IG6GraphEvent) => HTMLDivElement / string | img | The content of the Tooltip | +| shouldBegin | (evt: G6Event) => boolean | undefined | Whether allow the tooltip show up. You can return true or false according to the content of the `evt.item` (current item of the event) or `evt.target` (current shape of the event) | +| offsetX | number | 6 | the offset of tooltip along x axis, the padding of the parent container should be take into consider | +| offsetY | number | 6 | the offset of tooltip along y axis, the padding of the parent container should be take into consider | +| itemTypes | string[] | ['node', 'edge', 'combo'] | the item types that allow the tooltip show up. e.g. if you only want the node tooltip, set the `itemTypes` to be ['node'] | +| trigger | 'mouseenter' / 'click' | 'mouseenter' | Supported by v4.2.1. The trigger to show the tooltip. By default, the tooltip shows up when the mouse enter a node/edge/combo, where the trigger is `'mouseebter'`. If the trigger is assigned to `'click'`, the tooltip shows up when the user click a node/edge/combo | +| fixToNode | boolean / [number, number] | false | Supported by v4.2.1. Whether fix the position of the tooltip when mouse moving on the node. By default, the `fixToNode` is `false`, which means the tooltip follows the position of the mouse. If the `fixToNode` is assigned to an array as `[number, number]`, it means fixing the tooltip to a relative position to the target node. e.g. `[1, 0.5]` means the tooltip will be fixed to the right of the node after showing up, and do not follow the mouse when mouse move on the node. The meaning of the array is similar to the [Anchor Point](/en/docs/manual/middle/elements/nodes/anchorpoint). `fixToNode` is only available for tooltip on node | + +### Usage + +The content of the Tooltip is the type and id of the item by default. Users are free to custom the content of the Tooltip by configuring `getContent`: + +#### Dom Tooltip + +```javascript +const tooltip = new G6.Tooltip({ + offsetX: 10, + offsetY: 20, + getContent(e) { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = ` +

Custom Tooltip

+
    +
  • Label: ${e.item.getModel().label || e.item.getModel().id}
  • +
` + return outDiv + }, + itemTypes: ['node'] +}); + +const graph = new G6.Graph({ + //... Other configurations + plugins: [tooltip], // Use Tooltip plugin +}); +``` + +#### String Tooltip + +```javascript +const tooltip = new G6.Tooltip({ + getContent(e) { + return `
+ +
`; + }, +}); + +const graph = new G6.Graph({ + //... Other configurations + plugins: [tooltip], // Use Tooltip plugin +}); +``` +### Event Listener + +TimeBar Plugin exposes several timing events. They could be listened by `graph.on('eventname', e => {})`. + +| Event Name | Description | +| --- | --- | +| tooltipchange | Emitted when the Tooltip is changed. | + +## Fisheye Lens + +Fisheye is designed for focus_context exploration, it keeps the context and the relationships between context and the focus while magnifing the focus area. + +### Configuration + +| Name | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| trigger | 'mousemove' / 'click' | false | 'mousemove' | The trigger for the lens | +| d | Number | false | 1.5 | Magnify coefficient. Larger the value, larger the focus area will be magnified | +| r | Number | false | 300 | The radius of the focus area | +| delegateStyle | Object | false | { stroke: '#000', strokeOpacity: 0.8, lineWidth: 2, fillOpacity: 0.1, fill: '#ccc' } | The style of the lens's delegate | +| showLabel | Boolean | false | false | If the label is hidden, whether to show the label of nodes inside the focus area | +| maxR | Number | The height of the graph | The maximum radius scaled by the wheel | +| minR | Number | 0.05 \* The height of the graph | The minimum radius scaled by the wheel | +| maxD | Number | 5 | when `trigger` is `'mousemove'` or `'click'`, minimap allow users to adjust the magnifying coefficient `d` by dragging left / right on the lens. `maxD` is the maximum magnifying coefficient that limits this interaction. The suggested range for `maxD` is [0, 5]. Note that updating the configurations by `minimap.updateParam` will not be limited by `maxD` | +| minD | Number | 0 | when `trigger` is `'mousemove'` or `'click'`, minimap allow users to adjust the magnifying coefficient `d` by dragging left / right on the lens. `minD` is the minimum magnifying coefficient that limits this interaction. The suggested range for `minD` is [0, 5]. Note that updating the configurations by `minimap.updateParam` will not be limited by `minD` | +| scaleRBy | 'wheel'/'drag'/'unset'/undefined | false | 'unset' | The trigger for end users to scale the range of the lens | +| scaleDBy | 'wheel'/'drag'/'unset'/undefined | false | 'unset' | The trigger for end users to scale the magnification factor of the lens | +| showDPercent | Boolean | false | true | Whether show the percent of current magnification factor on the bottom of the lens, where the percent is about the D, minD, and maxD | + +### Member Function + +#### updateParams(cfg) + +Update partial of the configurations of the FishEye instance, including `trigger`, `d`, `r`, `maxR`, `minR`, `maxD`, `minD`, `scaleRBy`, and `scaleDBy`. E.g. + +```javascript +const fisheye = new G6.Fisheye({ + trigger: 'mousemove' +}); + +... // Other operations + +fisheye.updateParams({ + d: 2, + r: 500, + // ... +}) +``` + +### Usage + +```javascript +const fisheye = new G6.Fisheye({ + trigger: 'mousemove', + d: 1.5, + r: 300, + delegateStyle: clone(lensDelegateStyle), + showLabel: false +}); + +const graph = new G6.Graph({ + //... Other graph configurations + plugins: [fisheye], // configuring fisheye plugin +}); +``` + +## Edge Filter Lens + +Edge Filter Lens is designed for edge filtering, the desired edges will be kept inside the lens while the others will be hidden. + +### Configuration + +| Name | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| trigger | 'drag' / 'mousemove' / 'click' | false | 'mousemove' | The trigger for the lens | +| type | 'one' / 'both' / 'only-source' / 'only-target' | false | 'both' | Simple filtering conditions related to the end nodes. `'one'`: show the edge whose one or more end nodes are inside the filter lens; `'both'`: show the edge whose both end nodes are inside the lens; `'only-source'`: show the edge whose source node is inside the lens and target node is not; `'only-target'`: show the edge whose target node is inside the lens and source node is not. More complicated conditions can be defined by the `shouldShow` | +| shouldShow | (d?: unknown) => boolean | false | undefined | The custom conditions for filtering. The parameter `d` is the data of each edge, you can return boolean value according to the data, where `true` means show. | +| r | Number | false | 60 | The radius of the filter area | +| delegateStyle | Object | false | { stroke: '#000', strokeOpacity: 0.8, lineWidth: 2, fillOpacity: 0.1, fill: '#ccc' } | The style of the lens's delegate | +| showLabel | 'edge' / 'node' / 'both' | false | 'edge' | If the label is hidden, whether to show the label of nodes inside the focus area | +| maxR | Number | The height of the graph | The maximum radius scaled by the wheel | +| minR | Number | 0.05 \* The height of the graph | The minimum radius scaled by the wheel | +| scaleRBy | 'wheel'/'drag'/'unset'/undefined | false | 'unset' | The trigger for end users to scale the range of the lens | + +### Member Function + +#### updateParams(cfg) + +Update partial of the configurations of the filter lens instance, including `trigger`, `type`, `r`, `maxR`, `minR`, `shouldShow`, `showLabel`, and `scaleRBy`. E.g. + +```javascript +const filterLens = new G6.EdgeFilterLens({ + trigger: 'drag' +}); + +... // Other operations + +filterLens.updateParams({ + r: 500, + // ... +}) +``` + +### Usage + +```javascript +const filterLens = new G6.EdgeFilterLens({ + trigger: 'mousemove', + r: 300, + shouldShow: d => { + return d.size > 10; + } +}); + +const graph = new G6.Graph({ + //... Other graph configurations + plugins: [filterLens], // configuring edge filter lens plugin +}); +``` + +## TimeBar + +There are three types of built-in TimeBar in G6: + +- Time bar with a line chart as background; +- Simple time bar; +- Time bar with descrete ticks. + +All the three types of timebar supports play, fast forward, and fast backward. + + +
Time bar with a line chart as background
+ + +
Simple time bar
+ + +
Time bar with descrete ticks
+ +
Refer to the demos [HERE](https://g6.antv.antgroup.com/en/examples/tool/timebar#timebar)
+ +### Common Usage + +Same to other plugins of G6, the users can initiate the TimeBar and assign it to the graph as: + +```javascript +import G6 from '@antv/g6'; + +const timebar = new G6.TimeBar({ + width: 500, + height: 150, + padding: 10, + type: 'trend', + trend: { + data: timeBarData, + }, +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [timebar], +}); +``` + +
If you want to use the TimeBar with line chart, assign the `type` to be `trend` when instantiating the TimeBar, which results in: + + + +
Assigning the `type` to be `simple` results in: + + + +
And assigning the `type` to be `tick` results in a TimeBar with descrete ticks. Note that it is different from the above two types of TimeBar, \*\*The TimeBar with decrete ticks is configured with the `tick` object but not the `trend` object. + +```javascript +const timebar = new G6.TimeBar({ + width, + height: 150, + type: 'tick', + tick: { + data: timeBarData, + width, + height: 42, + tickLabelFormatter: d => { + const dateStr = `${d.date}`; + if ((count - 1) % 10 === 0) { + return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`; + } + return false; + }, + tooltipFomatter: d => { + const dateStr = `${d}`; + return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`; + }, + }, +}); +``` + + + + +### Event Listener + +TimeBar Plugin exposes several timing events. They could be listened by `graph.on('eventname', e => {})`. + +| Event Name | Description | +| --- | --- | +| valuechange | Emitted when the value range of the timebar is chaged. | +| timebarstartplay | Emitted when the timeline starts to play. | +| timebarendplay | Emitted when the timeline ends playing. | + +### API + +#### play + +Controll the timebar instance begin to play. e.g. `timebar.play()`. + +#### pause + +Controll the timebar instance to pause. e.g. `timebar.pause()`. + +### Definition of the Interfaces + +The complete interfaces for the TimeBar is shown below: + +```javascript +interface TimeBarConfig extends IPluginBaseConfig { + // position size + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + readonly padding?: number; + + readonly type?: 'trend' | 'simple' | 'tick'; + // the configuration for the TimeBar with line chart and simple TimeBar, takes effect whtn the type is 'trend' or 'simple' + readonly trend?: TrendConfig; + + // the configurations for the two sliders + readonly slider?: SliderOption; + + // when the type is 'tick', it is the configuration for the TimeBar with descrete ticks + // when the type is 'trend' or 'simpe', it is the configuration for the time tick labels under the timeBar + readonly tick?: TimeBarSliceOption | TickCfg; + + // the buttons for play, fast forward, and back forward + readonly controllerCfg?: ControllerCfg; + + // [Supported from v4.5.1] the CSS style for the DOM container of the timebar + readonly containerCSS?: Object; + + // [Supported from v4.5.1] the item types that will be filtered by the timebar. e.g. ['node', 'edge']. The default value is ['node'] + readonly filterItemTypes?: string[]; + + // [Deprecated from v4.5.1, replaced by filterItemTypes] whether to consider the edge filtering. If it is false, only filter the nodes and the edges whose end nodes are filtered out while the selected range of the timeBar is changed. If it is true, there should be `date` properties on the edges data, and the timeBar will filter the edges which is not in the selected range in the same time + readonly filterEdge?: boolean; + + // [Supported from v4.5.1] whether filter the nodes and edges on the graph by graph.changeData, which means the data of the graph will be changed by the timebar. If it is false, the graph.hideItem and graph.showItem will be called to hide/show the nodes and edges instead of changeData + readonly changeData?: boolean; + + // the callback function after the time range is changed. When it is not assigned, the graph elements will be filtered after the time range is changed + rangeChange?: (graph: IGraph, minValue: string, maxValue: string) => void; + + // [Supported from v4.5.1] user returns the date value according to the data of a node or an edge + getDate?: (d: any) => number; + + // [Supported from v4.5.1] user returns the value according to the data of a node or an edge. The value is used to draw the trend line for timebar with type 'trend' + getValue?: (d: any) => number; + + // [Supported from v4.5.1] user returns true or false to decide whether to ignore the node or the edge while filtering. If it is true, the item with data model will be ignored. Or the item will be filtered according to the min and max date value + shouldIgnore?: (itemType: 'node' | 'edge', model: any, dateRage: { min: number, max: number }) => boolean; +} +``` + +#### The Parameters of the Interfaces + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | The DOM container of the TimeBar. By default, the plugin will create a container DOM with 'g6-component-timebar' as className | +| x | number | 0 | The beginning x position of the TimeBar plugin | +| y | number | 0 | The beginning y position of the TimeBar plugin | +| width | number | | **Requred**, the width of the TimeBar | +| height | number | | **Requred**, the height of the TimeBar | +| padding | number/number[] | 10 | The padding of the container of the TimeBar | +| type | 'trend' / 'simple' / 'tick' | trend | The type of the TimeBar, 'trend' by default | +| trend | TrendConfig | null | The configuration for the TimeBar with line chart and simple TimeBar, takes effect whtn the type is 'trend' or 'simple' | +| slider | SliderOption | null | The configurations for the two sliders | +| tick | TimeBarSliceOption / TickCfg | null | If the type is 'tick', it is the configuration for the TimeBar with descrete ticks. If it the type is 'trend' or 'simple', it is the configuration for the time tick labels under the timeBar | +| controllerCfg | ControllerCfg | null | The buttons for play, fast forward, and back forward | +| containerCSS | Object | null | [Supported from v4.5.1] The CSS style for the DOM container of the timebar | +| filterItemTypes | string[] | null | [Supported from v4.5.1] The item types that will be filtered by the timebar. e.g. ['node', 'edge']. The default value is ['node'] | +| filterEdge | boolean | false | [Deprecated from v4.5.1, replaced by filterItemTypes] Whether to consider the edge filtering. If it is false, only filter the nodes and the edges whose end nodes are filtered out while the selected range of the timeBar is changed. If it is true, there should be `date` properties on the edges data, and the timeBar will filter the edges which is not in the selected range in the same time | +| changeData | boolean | null | [Supported from v4.5.1] Whether filter the nodes and edges on the graph by graph.changeData, which means the data of the graph will be changed by the timebar. If it is false, the graph.hideItem and graph.showItem will be called to hide/show the nodes and edges instead of changeData | +| rangeChange | Function | null | The callback function after the time range is changed. When it is not assigned, the graph elements will be filtered after the time range is changed | +| getDate | (d: any) => number | null | [Supported from v4.5.1] User returns the date value according to the data of a node or an edge | +| getValue | (d: any) => number | null | [Supported from v4.5.1] User returns the value according to the data of a node or an edge. The value is used to draw the trend line for timebar with type 'trend' | +| shouldIgnore | (itemType: 'node' | 'edge', model: any, dateRage: { min: number, max: number }) => boolean | null | [Supported from v4.5.1] User returns true or false to decide whether to ignore the node or the edge while filtering. If it is true, the item with data model will be ignored. Or the item will be filtered according to the min and max date value | + +#### Interface for TrendConfig + +> Does not support the configurations for the style of the tick labels. + +```javascript +interface TrendConfig { + // The data + readonly data: { + date: string; + value: string; + }[]; + // The position and size + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + // The styles + readonly smooth?: boolean; + readonly isArea?: boolean; + readonly lineStyle?: ShapeStyle; + readonly areaStyle?: ShapeStyle; + readonly interval?: Interval; +} +``` + +#### Parameters of the TrendConfig + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| x | number | 0 | The beginning x position of the trend line chart | +| y | number | 0 | The beginning y position of the trend line chart | +| width | number | The width of the TimeBar | The width of the trend line chart of the TimeBar, we suggest to use the default value. If you wanna custom it, please assign the `width` of the slider in the same time | +| height | number | 28 when type='trend'
8 when type='simple' | The height of the TimeBar | The width of the trend line chart of the TimeBar, we suggest to use the default value. If you wanna custom it, please assign the `height` of the slider in the same time | +| smooth | boolean | false | Whether to show a smooth line on the trend line chart | +| isArea | boolean | false | Whether to show a area chart instead | +| lineStyle | ShapeStyle | null | The configurations for the style of the line in the line chart | +| areaStyle | ShapeStyle | null | The configuration for the style of the area in the chart when `isArea` is `true` | +| interval | Interval | null | The configuration for the style of the bars in the chart. When it is assigned, a mixed trend chart will take place. `Interval = { data: number[], style: ShapeStyle }`. Except the configurations in `ShapeStyle` for the style of the shapes in the bar charts, `barWidth` for the width of one bar is also configurable for `style` | + +#### Interfaces of SliderOption + +```javascript +export type SliderOption = Partial<{ + readonly width?: number; + readonly height?: number; + readonly backgroundStyle?: ShapeStyle; + readonly foregroundStyle?: ShapeStyle; + // The style of the sliders + readonly handlerStyle?: { + width?: number; + height?: number; + style?: ShapeStyle; + }; + readonly textStyle?: ShapeStyle; + // The start and end position for the sliders, which indicate the data range for the filtering. Ranges from 0 to 1 + readonly start: number; + readonly end: number; + // The labels for the sliders + readonly minText: string; + readonly maxText: string; +}>; +``` + +#### Parameters for the SliderOption + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| width | number | The width of the container of the TimeBar - 2 \* padding | The width of the background trend chart. We suggest to use the default value. If you wanna custom it, assign it the the `width` in the `trend` in the same time | +| height | number | 28 when type='trend'
8 when type='simple' | The height of the background trend chart. We suggest to use the default value. If you wanna custom it, assign it the the `height` in the `trend` in the same time | +| backgroundStyle | ShapeStyle | null | The configuration for the style of the background | +| foregroundStyle | ShapeStyle | null | The configuration for the style of the forground | +| handlerStyle | ShapeStyle | null | The configuration for the style of the two sliders | +| textStyle | ShapeStyle | null | The configuration for the style of the labels on the two sliders | +| start | number | 0.1 | The start position for the sliders, which indicate the start of the data range for the filtering. Ranges from 0 to `end` | +| end | number | 0.9 | The end position for the sliders, which indicate the end of the data range for the filtering. Ranges from `start` to 1 | +| minText | string | min | The label for the left slider | +| maxText | string | max | The label for the right slider | + +#### TimeBarSliceOption + +```javascript +export interface TimeBarSliceOption { + // position size + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + readonly padding?: number; + + // styles + readonly selectedTickStyle?: TickStyle; + readonly unselectedTickStyle?: TickStyle + readonly tooltipBackgroundColor?: string; + + readonly start?: number; + readonly end?: number; + + // data + readonly data: { + date: string; + value: string; + }[]; + + // custom the formatter function for the tick labels + readonly tickLabelFormatter?: (d: any) => string | boolean; + // custom the formatter function for the tooltip + readonly tooltipFomatter?: (d: any) => string; +} +``` + +#### Parameters for the TimeBarSliceOption + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| x | number | 0 | The beginning x position for the TimeBar | +| y | number | 0 | The beginning y position for the TimeBar | +| width | number | | **Requred**, the width of the TimeBar | +| height | number | | **Requred**, the height of the TimeBar | +| padding | number / number[] | 0 | The padding of the container of the TimeBar | +| selectedTickStyle | ShapeStyle | null | The style of the tick(s) which is(are) selected | +| unselectedTickStyle | ShapeStyle | null | The style of the tick(s) which is(are) unselected | +| tooltipBackgroundColor | ShapeStyle | null | The background style for the tooltip | +| start | number | 0.1 | The start position for the sliders, which indicate the start of the data range for the filtering. Ranges from 0 to `end` | +| end | number | 0.9 | The end position for the sliders, which indicate the end of the data range for the filtering. Ranges from `start` to 1 | +| data | any[] | [] | **Requred**, the data for the ticks | +| tickLabelFormatter | Function | null | The formatter function for customing the labels of the ticks | +| tooltipFomatter | Function | null | The formatter function for customing the tooltip | + +#### TickCfg + +```javascript +export interface TickCfg { + // the fomatter for the time tick labels + readonly tickLabelFormatter?: (d: any) => string | undefined; + // the shape style for the time tick labels. [Supported from v4.5.1] tickLabelStyle.rotate can be configured to controll the rotate of the tick label to avoid overlappings + readonly tickLabelStyle?: ShapeStyle; + // the shape style for the short vertical lines uppon the time tick labels + readonly tickLineStyle?: ShapeStyle; +} +``` + +#### Parameters for TickCfg + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| tickLabelFormatter | Function | null | The formatter function for customing the labels of the ticks | +| tickLabelStyle | ShapeStyle | {} | The shape style for the time tick labels. [Supported from v4.5.1] tickLabelStyle.rotate can be configured to controll the rotate of the tick label to avoid overlappings | +| tickLineStyle | ShapeStyle | {} | The shape style for the short vertical lines uppon the time tick labels | + +#### Interface of the ControllerCfg + +> Does not support for now + +> Does not support the style configuration for controller buttons + +> Does not support loop play + +```javascript +type ControllerCfg = Partial<{ + /** the begining position and the size of the controller, the width and height will not scale the sub-controllers but only affects the positions of them. To change the size of the sub-controllers, try ControllerCfg.scale or the scale in the style of sub-controller */ + readonly x?: number; + readonly y?: number; + readonly width: number; + readonly height: number; + /** the scale of the whole controller */ + readonly scale?: number; + /** the fill and stroke color of the background */ + readonly fill?: string; + readonly stroke?: string; + /** the font family for the whole controller, whose priority is lower than the fontFamily in the text style of each sub-controller */ + readonly fontFamily?: string; + + /** the play spped, means the playing time for 1 tick */ + readonly speed?: number; + /** whether play in loop */ + readonly loop?: boolean; + /** whether hide the 'time type controller' on the right-bottom */ + readonly hideTimeTypeController: boolean; + + /** style of the backward button. scale, offsetX, offsetY are also can be assigned to it to controll the size and position of the backward button */ + readonly preBtnStyle?: ShapeStyle; + + /** style of the forward button. scale, offsetX, offsetY are also can be assigned to it to controll the size and position of the forward button */ + readonly nextBtnStyle?: ShapeStyle; + + /** style of the play button. scale, offsetX, offsetY are also can be assigned to it to controll the size and position of the paly button */ + readonly playBtnStyle?: ShapeStyle; + + /** style of the 'speed controller'. scale, offsetX, offsetY are also can be assigned to it and each sub-styles to controll the size and position of the speed controller and sub-shapes*/ + readonly speedControllerStyle?: { + offsetX?: number, + offsetY?: number; + scale?: number + pointer?: ShapeStyle, + scroller?: ShapeStyle, + text?: ShapeStyle + }; + + /** style of the 'time type controller'. scale, offsetX, offsetY are also can be assigned to it and each sub-styles to controll the size and position of the speed controller and sub-shapes */ + readonly timeTypeControllerStyle?: { + offsetX?: number, + offsetY?: number; + scale?: number + check?: ShapeStyle, + box?: ShapeStyle, + text?: ShapeStyle + }; + /** [Supported from v4.5.1] The style of the background rect of the controller */ + readonly containerStyle?: ExtendedShapeStyle; + /** the text for the right-bottom switch controlling play with single time point or time range */ + readonly timePointControllerText?: string; + readonly timeRangeControllerText?: string; + /** [Supported from v4.7.11] the default type of the playing, 'single' means single time point, and 'range' means time range. 'range' by default */ + readonly defaultTimeType?: 'single' | 'range'; +}> +``` + +#### Parameters for ControllerCfg + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| x | number | 0 | The beginning x position for the buttons group of the TimeBar | +| y | number | 0 | The beginning y position for the buttons group of the TimeBar | +| width | number | The width of the TimeBar | The width of the buttons group of the TimeBar, do not scale the sub-controllers but only affects the positions of them | +| height | number | 40 | The width of the buttons group of the TimeBar, do not scale the sub-controllers but only affects the positions of them | +| scale | number | 1 | The scale of the whole controller | +| speed | number | 1 | The play speed | +| loop | boolean | false | _Does not support for now_, whether play in loop | +| hideTimeTypeController | boolean | true | Whther hide the time type controller on the right bottom | +| fill | string | | The fillling color for the background of the controller | +| stroke | string | | The stroke color for the background of the buttons group | +| preBtnStyle | ShapeStyle | null | The style of the backward button. `scale`, `offsetX`, `offsetY` are also can be assigned to it to controll the size and position of the backward button | +| nextBtnStyle | ShapeStyle | null | The style of the forward button. `scale`, `offsetX`, `offsetY` are also can be assigned to it to controll the size and position of the forward button | +| playBtnStyle | ShapeStyle | null | The style of the play button. `scale`, `offsetX`, `offsetY` are also can be assigned to it to controll the size and position of the paly button | +| speedControllerStyle | { offsetX?: number, offsetY?: number, scale?: number, pointer?: ShapeStyle, text?: ShapeStyle, scroller?: ShapeStyle} | null | The style of the 'speed controller'. `scale`, `offsetX`, `offsetY` are also can be assigned to it and each sub-styles to controll the size and position of the speed controller and sub-shapes | +| timeTypeControllerStyle | { offsetX?: number, offsetY?: number, scale?: number, box?: ShapeStyle, check?: ShapeStyle, text?: ShapeStyle } | null | The style of the 'time type controller'. `scale`, `offsetX`, `offsetY` are also can be assigned to it and each sub-styles to controll the size and position of the speed controller and sub-shapes | +| containerStyle | ShapeStyle | {} | [Supported from v4.5.1] The style of the background rect of the controller | +| timePointControllerText | string | "单一时间" | The text for the right-bottom switch controlling play with single time point or time range | +| timeRangeControllerText | string | "时间范围" | The text for the right-bottom switch controlling play with single time point or time range | diff --git a/packages/site/docs/api/Plugins.zh.md b/packages/site/docs/api/Plugins.zh.md new file mode 100644 index 0000000000..676d4996b3 --- /dev/null +++ b/packages/site/docs/api/Plugins.zh.md @@ -0,0 +1,1022 @@ +--- +title: 插件 Plugins +order: 14 +--- + +G6 提供了一些可插拔的组件,包括: + +- [Legend](#legend) *v4.3.0 起支持* +- [SnapLine](#snapline) *v4.3.0 起支持* +- [Grid](#grid) +- [Minimap](#minimap) +- [ImageMinimap](#image-minimap) +- [Edge Bundling](#edge-bundling) +- [Menu](#menu) +- [ToolBar](#toolbar) +- [TimeBar](#timebar) +- [Tooltip](#tooltip) +- [Fisheye](#fisheye) +- [EdgeFilterLens](#edge-filter-lens) + +## 配置方法 + +引入 G6 后,首先实例化需要使用的某插件对象。然后,在实例化图时将其配置到 `plugins` 中: + +```javascript +// 实例化 Grid 插件 +const grid = new G6.Grid(); +const minimap = new G6.Minimap(); +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [grid, minimap], // 配置 Grid 插件和 Minimap 插件 +}); +``` + +## Legend + +Legend 是 G6 内置的图例插件。用于说明图中不同类型的节点和边所代表的含义,并可以通过与图例的交互做简单的高亮和过滤。 *v4.3.0 起支持*。 + +img + +### 配置项 + +| 名称 | 类型 | 描述 | +| --- | --- | --- | +| data | GraphData | 图例的数据,与图数据格式相同。节点图例目前支持 `'circle'`,`'rect'`,和 `'ellipse'`,边图例目前支持 `'line'`、`'cubic'`、`'quadratic'`。通过指定每个数据项中的 `type` 字段以确定图例元素的类型,每个数据项中的 `order` 字段可用于同组图例的排序 | +| position | 'top' / 'top-left' / 'top-right' / 'right' / 'right-top' / 'right-bottom' / 'left' / 'left-top' / 'left-bottom' / 'bottom' / 'bottom-left' / 'bottom-right' | 图例在画布中的相对位置,默认为 `'top'`,代表在画布正上方 | +| padding | number / number[] | 图例区域内部内容到边框的距离,四位数组分别代表上、右、下、左边距 | +| margin | number / number[] | 图例区域与画布边界的距离,四位数组分别代表上、右、下、左边距。在 `position:'top'` 时只有上边距生效,其他情况类似 | +| offsetX | number | 图例区域离 `position` 对应的默认位置的 x 方向的偏移量,可被用于图例位置的微调 | +| offsetY | number | 图例区域离 `position` 对应的默认位置的 y 方向的偏移量,可被用于图例位置的微调 | +| containerStyle | ShapeStyle | 图例背景框的样式,格式与 [rect 图形的样式](/zh/docs/api/shapeProperties#矩形图形-rect)相同 | +| horiSep | number | 图例之间的水平间距 | +| vertiSep | number | 图例之间的竖直间距 | +| layout | 'vertical' / 'horizontal' | 图例的布局方式。默认为 `'horizontal'` 横向布局 | +| align | 'center' / 'right' / 'left' | 图例的对齐方式,可以是居中、右对齐、左对齐。默认为 `'center'` 居中 | +| title | string | 图例的标题文本内容,样式通过 `titleConfig` 设置 | +| titleConfig | object | 图例标题的样式,具体配置项如下 | +| titleConfig.position | 'center' / 'right' / 'left' | 图例标题的对齐方式,可以是居中、右对齐、左对齐。默认为 `'center'` 居中 | +| titleConfig.offsetX | number | 图例标题的 x 方向偏移,用于微调标题位置 | +| titleConfig.offsetY | number | 图例标题的 y 方向偏移,用于微调标题位置 | +| titleConfig[key] | ShapeStyle | 其他对于文本本身的样式,支持的内容与 [text 图形的样式](/zh/docs/api/shapeProperties#文本-text)相同 | +| filter | object | 通过图例的交互对主图元素进行过滤的配置项,具体配置如下 | +| filter.enable | boolean | 是否允许通过图例的交互对主图元素过滤,默认为 `false` | +| filter.multiple | boolean | 是否支持多种元素过滤,默认为 `false`。当它为 `true` 时,只有 `filter.trigger` 为 `'click'` 时方可多选图例 | +| filter.trigger | 'click' / 'mouseenter' | 触发主图元素过滤的图例交互方式,默认为 `click` | +| filter.legendStateStyles | { active?: ShapeStyle, inactive?: ShapeStyle | 在过滤时,图例本身的状态样式,包括 `filter.legendStateStyles.active` 和 `filter.legendStateStyles.inactive` 两种,每种的类型均为 ShapeStyle。类似图的 `nodeStateStyles` 配置 | +| filter.graphActiveState | string | 主图元素过滤时,被选中的主图元素的状态名,将寻找主图元素的对应的状态样式进行主图元素的更新。默认值为 `'active'` | +| filter.graphInactiveState | string | 主图元素过滤时,未被选中的主图元素的状态名,将寻找主图元素的对应的状态样式进行主图元素的更新。默认值为 `'inactive'` | +| filter.filterFunctions | { [key: string]: (d) => boolean; } | 由于图例的数据与主图解耦,因此需要配置每种图例对应的主图过滤函数,`key` 为图例数据的 `type`,值为函数,函数的参数为主图元素的数据,返回值为布尔型,代表是否被选中 | + +## SnapLine + +SnapLine 是 G6 内置的对齐线插件。 *v4.3.0 起支持*。 + +实例化时可以通过配置项调整 SnapLine 的样式和功能。 + +### 配置项 + +| 名称 | 类型 | 描述 | +| --- | --- | --- | +| line | ShapeStyle | 辅助线的样式 | +| itemAlignType | boolean、'horizontal' 、'vertical'、'center'; | 辅助线类型,true 表示全部 | + + +## Grid + +Grid 插件在画布上绘制了网格。 + +img + +使用 [配置方法](#配置方法) 中代码实例化时可以通过配置项调整 Grid 的图片。 + +### 配置项 + +| 名称 | 类型 | 描述 | +| ---- | ------ | ---------------------------- | +| img | String | grid 图片,base64 格式字符串 | + +## Minimap + +Minimap 是用于快速预览和探索图的工具。 + +img + +实例化时可以通过配置项调整 Minimap 的样式和功能。 + +### 配置项 + +| 名称 | 类型 | 描述 | +| --- | --- | --- | +| container | Object | 放置 Minimap 的 DOM 容器。若不指定则自动生成 | +| className | String | 生成的 DOM 元素的 className | +| viewportClassName | String | Minimap 上视窗 DOM 元素的 className | +| type | String | 选项:`'default'`:渲染图上所有图形;`'keyShape'`:只渲染图上元素的 keyShape,以减少渲染成本;`'delegate'`:只渲染图上元素的大致图形,以降低渲染成本。渲染成本 `'default'` > `'keyShape'` > `'delegate'`。默认为 `'default'` | +| size | Array | Minimap 的大小 | +| delegateStyle | Object | 在 `type` 为 `'delegate'` 时生效,代表元素大致图形的样式 | +| hideEdge | Boolean | false | **v4.7.16 起支持** 控制 Minimap 上边的显示与隐藏,设置为 `true` 可在大规模图上大幅提升性能 | + + +其中,delegateStyle 可以设置如下属性: + +| 名称 | 类型 | 描述 | +| ----------- | ------ | ---------- | +| fill | String | 填充颜色 | +| stroke | String | 描边颜色 | +| lineWidth | Number | 描边宽度 | +| opacity | Number | 透明度 | +| fillOpacity | Number | 填充透明度 | + +## Image Minimap + +由于 [Minimap](#minimap) 的原理是将主画布内容复制到 minimap 的画布上,在大数据量下可能会造成双倍的绘制效率成本。为缓解该问题,Image Minimap 采用另一种机制,根据提供的图片地址或 base64 字符串 `graphImg` 绘制 `` 代替 minimap 上的 canvas。该方法可以大大减轻两倍 canvas 绘制的压力。但 `graphImg` 完全交由 G6 的用户控制,需要注意主画布更新时需要使用 `updateGraphImg` 方法替换 `graphImg`。 + +img + +实例化时可以通过配置项调整 Image inimap 的样式和功能。 + +### 配置项 + +| 名称 | 类型 | 是否必须 | 描述 | +| --- | --- | --- | --- | +| graphImg | String | true | minimap 的图片地址或 base64 文本 | +| width | Number | false | minimap 的宽度。Image Minimap 的长宽比一定等于主图长宽比。因此,若设置了 `width`,则按照主画布容器长宽比确定 `height`,也就是说,`width` 的优先级高于 `height`。 | +| height | Number | false | minimap 的高度。Image Minimap 的长宽比一定等于主图长宽比。若未设置了 `width`,但设置了 `height`,则按照主画布容器长宽比确定 `width`;若设置了 `width` 则以 `width` 为准 | +| container | Object | false | 放置 Minimap 的 DOM 容器。若不指定则自动生成 | +| className | String | false | 生成的 DOM 元素的 className | +| viewportClassName | String | false | Minimap 上视窗 DOM 元素的 className | +| delegateStyle | Object | false | 在 `type` 为 `'delegate'` 时生效,代表元素大致图形的样式 | + +其中,`delegateStyle` 可以设置如下属性: + +| 名称 | 类型 | 描述 | +| ----------- | ------ | ---------- | +| fill | String | 填充颜色 | +| stroke | String | 描边颜色 | +| lineWidth | Number | 描边宽度 | +| opacity | Number | 透明度 | +| fillOpacity | Number | 填充透明度 | + +### API + +#### updateGraphImg(img) + +更新 minimap 图片。建议在主画布更新时使用该方法同步更新 minimap 图片。 + +参数: + +| 名称 | 类型 | 是否必须 | 描述 | +| ---- | ------ | -------- | -------------------------------- | +| img | String | true | minimap 的图片地址或 base64 文本 | + +### 用法 + +实例化 Image Minimap 插件时,`graphImg` 是必要参数。 + +```javascript +// 实例化 Image Minimap 插件 +const imageMinimap = new G6.ImageMinimap({ + width: 200, + graphImg: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*eD7nT6tmYgAAAAAAAAAAAABkARQnAQ' +}); +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [imageMinimap], // 配置 imageMinimap 插件 +}); + +graph.data(data); +graph.render() + +... // 一些主画布更新操作 +imageMinimap.updateGraphImg(img); // 使用新的图片(用户自己生成)替换 minimap 图片 + +``` + +## Edge Bundling + +在关系复杂、繁多的大规模图上,通过边绑定可以降低视觉复杂度。 + +img + +> 美国航线图边绑定。Demo 链接。该 Demo 教程。 + +实例化时可以通过配置项调整边绑定的功能。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| K | Number | 0.1 | 边绑定的强度 | +| lambda | Number | 0.1 | 算法的初始步长 | +| divisions | Number | 1 | 初始的切割点数,即每条边将会被切割成的份数。每次迭代将会被乘以 `divRate` | +| divRate | Number | 2 | 切割增长率,每次迭代都会乘以该数字。数字越大,绑定越平滑,但计算量将增大 | +| cycles | Number | 6 | 迭代次数 | +| iterations | Number | 90 | 初始的内迭代次数,每次外迭代中将会被乘以 `iterRate` | +| iterRate | Number | 0.6666667 | 迭代下降率 | +| bundleThreshold | Number | 0.6 | 判定边是否应该绑定在一起的相似容忍度,数值越大,被绑在一起的边相似度越低,数量越多 | + +## Menu + +Menu 用于配置节点上的右键菜单。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| className | string | null | menu 容器的 class 类名 | +| getContent | (evt?: IG6GraphEvent) => HTMLDivElement / string | img | 菜单项内容,支持 DOM 元素或字符串 | +| handleMenuClick | (target: HTMLElement, item: Item, graph?: IGraph) => void | undefined | 点击菜单项的回调函数 | +| shouldBegin | (evt: G6Event) => boolean | undefined | 是否允许 menu 出现,可以根据 `evt.item`(当前鼠标事件中的元素) 或 `evt.target`(当前鼠标事件中的图形)的内容判断此时是否允许 menu 出现 | +| offsetX | number | 6 | menu 的 x 方向偏移值,需要考虑父级容器的 padding | +| offsetY | number | 6 | menu 的 y 方向偏移值,需要考虑父级容器的 padding | +| itemTypes | string[] | ['node', 'edge', 'combo'] | menu 作用在哪些类型的元素上,若只想在节点上显示,可将其设置为 ['node'] | +| trigger | 'click' / 'contextmenu' | 'contextmenu' | menu 出现的触发方式,默认为 `'contextmenu'`,即右击。`'click'` 代表左击。*v4.3.2 起支持 'click'* | + +### 用法 + +实例化 Menu 插件时,如果不传参数,则使用 G6 默认提供的值,只能展示默认的菜单项,不能进行任何操作。 + +```javascript +// 实例化 Menu 插件 +const menu = new G6.Menu(); +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [menu], // 配置 Menu 插件 +}); +``` + +#### DOM Menu + +```javascript +const menu = new G6.Menu({ + offsetX: 6, + offsetX: 10, + itemTypes: ['node'], + getContent(e) { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = `
    +
  • 测试01
  • +
  • 测试01
  • +
  • 测试01
  • +
  • 测试01
  • +
  • 测试01
  • +
` + return outDiv + }, + handleMenuClick(target, item) { + console.log(target, item) + }, +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [menu], // 配置 Menu 插件 +}); +``` + +#### String Menu + +```javascript +const menu = new G6.Menu({ + getContent(evt) { + return `
    +
  • 测试02
  • +
  • 测试02
  • +
  • 测试02
  • +
  • 测试02
  • +
  • 测试02
  • +
`; + }, + handleMenuClick(target, item) { + console.log(target, item) + }, +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [menu], // 配置 Menu 插件 +}); +``` + +## ToolBar + +ToolBar 集成了以下常见的操作: + +- 重做; +- 撤销; +- 放大; +- 缩小; +- 适应屏幕; +- 实际大小。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | ToolBar 容器,如果不设置,则默认使用 canvas 的 DOM 容器 | +| className | string | null | ToolBar 内容元素的 class 类名 | +| getContent | (graph?: IGraph) => HTMLDivElement | string | img | ToolBar 内容,支持 DOM 元素或字符串 | +| handleClick | (code: string, graph: IGraph) => void | undefined | 点击 ToolBar 中每个图标的回调函数 | +| position | Point | null | ToolBar 的位置坐标 | + +### 用法 + +#### 默认用法 + +默认的 ToolBar 提供了撤销、重做、放大等功能。 + +```javascript +const toolbar = new G6.ToolBar(); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [toolbar], // 配置 ToolBar 插件 +}); +``` + +#### 使用 String 自定义 ToolBar 功能 + +```javascript +const tc = document.createElement('div'); +tc.id = 'toolbarContainer'; +document.body.appendChild(tc); + +const toolbar = new G6.ToolBar({ + container: tc, + getContent: () => { + return ` +
    +
  • 增加节点
  • +
  • 撤销
  • +
+ ` + }, + handleClick: (code, graph) => { + if (code === 'add') { + graph.addItem('node', { + id: 'node2', + label: 'node2', + x: 300, + y: 150 + }) + } else if (code === 'undo') { + // 自定义 undo + toolbar.undo() + toolbar.autoZoom() + } else { + // 其他操作保持默认不变 + toolbar.handleDefaultOperator(code) + } + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [toolbar], // 配置 ToolBar 插件 +}); +``` + +#### 使用 DOM 自定义 ToolBar 功能 + +```javascript +const toolbar = new G6.ToolBar({ + getContent: () => { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = `
    +
  • 测试01
  • +
  • 测试02
  • +
  • 测试03
  • +
  • 测试04
  • +
  • 测试05
  • +
` + return outDiv + }, + handleClick: (code, graph) => { + + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [toolbar], // 配置 ToolBar 插件 +}); +``` + +## Tooltip + +Tooltip 插件主要用于在节点和边上展示一些辅助信息,G6 4.0 以后,Tooltip 插件将会替换 Behavior 中的 tooltip。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| className | string | null | tooltip 容器的 class 类名 | +| container | HTMLDivElement | null | Tooltip 容器,如果不设置,则默认使用 canvas 的 DOM 容器 | +| getContent | (evt?: IG6GraphEvent) => HTMLDivElement / string | img | tooltip 内容,支持 DOM 元素或字符串 | +| shouldBegin | (evt: G6Event) => boolean | undefined | 是否允许 tooltip 出现,可以根据 `evt.item`(当前鼠标事件中的元素) 或 `evt.target`(当前鼠标事件中的图形)的内容判断此时是否允许 tooltip 出现 | +| offsetX | number | 6 | tooltip 的 x 方向偏移值,需要考虑父级容器的 padding | +| offsetY | number | 6 | tooltip 的 y 方向偏移值,需要考虑父级容器的 padding | +| itemTypes | string[] | ['node', 'edge', 'combo'] | tooltip 作用在哪些类型的元素上,若只想在节点上显示,可将其设置为 ['node'] | +| trigger | 'mouseenter' / 'click' | 'mouseenter' | v4.2.1 支持。出现 tooltip 的触发方式。默认为鼠标进入节点/边/combo。设置为 `'click'` 代表触发方式为点击节点/边/combo | +| fixToNode | boolean / [number, number] | false | v4.2.1 支持。是否固定出现在相对于目标节点的某个位置,鼠标在节点上方移动时不实时更新。`false` 代表不固定,`[number, number]` 类型的值用于指定固定位置,例如 `[1, 0.5]` 代表 tooltip 的左上角固定到目标节点的正右方(数组代表的类似 [Anchor Point](/zh/docs/manual/middle/elements/nodes/anchorpoint) 的位置定义)。仅在节点上生效 | + +### 用法 + +默认的 Tooltip 只展示元素类型和 ID,一般情况下都需要用户自己定义 Tooltip 上面展示的内容。 + +#### Dom Tooltip + +```javascript +const tooltip = new G6.Tooltip({ + offsetX: 10, + offsetY: 20, + getContent(e) { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = ` +

自定义tooltip

+
    +
  • Label: ${e.item.getModel().label || e.item.getModel().id}
  • +
` + return outDiv + }, + itemTypes: ['node'] +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [tooltip], // 配置 Tooltip 插件 +}); +``` + +#### String Tooltip + +```javascript +const tooltip = new G6.Tooltip({ + getContent(e) { + return `
+ +
`; + }, +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [tooltip], // 配置 Tooltip 插件 +}); +``` + +### 事件监听 + +Tooltip 插件暴露除了几个时机事件,方便用户监听内部状态的变化。以下事件可通过 `graph.on('eventname', e => {})` 进行监听。 + +| 事件名称 | 描述 | +| --- | --- | +| tooltipchange | Tooltip 发生变化时触发 | + +## Fisheye + +Fisheye 鱼眼放大镜是为 focus+context 的探索场景设计的,它能够保证在放大关注区域的同时,保证上下文以及上下文与关注中心的关系不丢失。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| trigger | 'mousemove' / 'click' / 'drag' | 'mousemove' | 放大镜的触发事件 | +| d | Number | 1.5 | 放大系数,数值越大,放大程度越大 | +| r | Number | 300 | 放大区域的范围半径 | +| delegateStyle | Object | { stroke: '#000', strokeOpacity: 0.8, lineWidth: 2, fillOpacity: 0.1, fill: '#ccc' } | 放大镜蒙层样式 | +| showLabel | Boolean | false | 若 label 默认被隐藏,是否在关注区域内展示 label | +| maxR | Number | 图的高度 | 滚轮调整缩放范围的最大半径 | +| minR | Number | 0.05 \* 图的高度 | 滚轮调整缩放范围的最小半径 | +| maxD | Number | 5 | `trigger` 为 `'mousemove'` / `'click'` 时,可以在放大镜上左右拖拽调整缩放系数。maxD 指定了这种调整方式的最大缩放系数,建议取值范围 [0, 5]。若使用 `minimap.updateParam` 更新参数不受该系数限制 | +| minD | Number | 0 | `trigger` 为 `'mousemove'` / `'click'` 时,可以在放大镜上左右拖拽调整缩放系数。maxD 指定了这种调整方式的最小缩放系数,建议取值范围 [0, 5]。若使用 `minimap.updateParam` 更新参数不受该系数限制 | +| scaleRBy | 'wheel'/'drag'/'unset'/undefined | false | 'unset' | 终端用户调整放大镜范围大小的方式 | +| scaleDBy | 'wheel'/'drag'/'unset'/undefined | false | 'unset' | 终端用户调整放大镜缩放系数的方式 | +| showDPercent | Boolean | false | true | 是否在放大镜下方显示当前缩放系数的比例值(与 minD、maxD 相较) | + +### 成员函数 + +#### updateParams(cfg) + +用于更新该 FishEye 的部分配置项,包括 `trigger`,`d`,`r`,`maxR`,`minR`,`maxD`,`minD`,`scaleRBy`,`scaleDBy`。例如: + +```javascript +const fisheye = new G6.Fisheye({ + trigger: 'mousemove' +}); + +... // 其他操作 + +fisheye.updateParams({ + d: 2, + r: 500, + // ... +}) +``` + +### 用法 + +```javascript +const fisheye = new G6.Fisheye({ + trigger: 'mousemove', + d: 1.5, + r: 300, + delegateStyle: clone(lensDelegateStyle), + showLabel: false +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [fisheye], // 配置 fisheye 插件 +}); +``` + +## Edge Filter Lens + +EdgeFilterLens 边过滤镜可以将关注的边保留在过滤镜范围内,其他边将在该范围内不显示。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| trigger | 'drag' / 'mousemove' / 'click' | 'mousemove' | 过滤镜的触发事件 | +| type | 'one' / 'both' / 'only-source' / 'only-target' | 'both' | 根据边两端点作为边过滤的简单条件。`'one'`:边至少有一个端点在过滤镜区域内,则在该区域内显示该边;`'both'`:两个端点都在过滤区域内,则在该区域显示该边;`'only-source'`:只有起始端在过滤镜区域内,则在该区域显示该边;`'only-target'`:只有结束端在过滤区域内,则在该区域显示该边。更复杂的条件可以使用 `shouldShow` 指定 | +| shouldShow | (d?: unknown) => boolean | undefined | 边过滤的自定义条件。参数 `d` 为边每条边的数据,用户可以根据边的参数返回布尔值。返回 `true` 代表该边需要在过滤镜区域内显示,`false` 反之。 | +| r | Number | 60 | 过滤镜的范围半径 | +| delegateStyle | Object | { stroke: '#000', strokeOpacity: 0.8, lineWidth: 2, fillOpacity: 0.1, fill: '#ccc' } | 过滤镜蒙层样式 | +| showLabel | 'edge' / 'node' / 'both' | 'edge' | 若 label 默认被隐藏,是否在关注区域内展示对应元素类型的 label。'both' 代表节点和边的 label 都在过滤镜区域显示 | +| maxR | Number | 图的高度 | 滚轮调整过滤镜的最大半径 | +| minR | Number | 0.05 \* 图的高度 | 滚轮调整过滤镜的最小半径 | +| scaleRBy | 'wheel' / undefined | 'wheel' | 终端用户调整过滤镜大小的方式,undefined 代表不允许终端用户调整 | + +### 成员函数 + +#### updateParams(cfg) + +用于更新该过滤镜的部分配置项,包括 `trigger`,`type`,`r`,`maxR`,`minR`,`scaleRBy`,`showLabel`,`shouldShow`。例如: + +```javascript +const filterLens = new G6.EdgeFilterLens({ + trigger: 'drag' +}); + +... // 其他操作 + +filterLens.updateParams({ + r: 500, + // ... +}) +``` + +### 用法 + +```javascript +const filterLens = new G6.EdgeFilterLens({ + trigger: 'mousemove', + r: 300, + shouldShow: d => { + return d.size > 10; + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [filterLens], // 配置 filterLens 插件 +}); +``` + +## TimeBar + +[AntV G6](https://github.com/antvis/G6) 内置了三种形态的 TimeBar 组件: + +- 带有趋势图的 TimeBar 组件; +- 简易版的 TimeBar 组件; +- 刻度 TimeBar 组件。 + +并且每种类型的 TimeBar 组件都可以配合播放、快进、后退等控制按钮组使用。 + + +
趋势图 TimeBar 组件
+ + +
简易版 TimeBar 组件
+ + +
刻度 TimeBar 组件
+ +
在趋势图 TimeBar 基础上,我们可以通过配置数据,实现更加复杂的趋势图 TimeBar 组件,如下图所示。 + + + +
虽然 G6 提供了各种不同类型的 TimeBar 组件,但在使用的方式却非常简单,通过配置字段就可以进行区分。

关于 TimeBar 的使用案例,请参考[这里](https://g6.antv.antgroup.com/examples/tool/timebar#timebar)。
+ +### 使用 TimeBar 组件 + +使用 G6 内置的 TimeBar 组件,和使用其他组件的方式完全相同。 + +```javascript +import G6 from '@antv/g6'; + +const timebar = new G6.TimeBar({ + width: 500, + height: 150, + padding: 10, + type: 'trend', + trend: { + data: timeBarData, + }, +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [timebar], +}); +``` + +
通过上面的方式,我们就可以在图中使用 TimeBar 组件了,当实例化 TimeBar 时,type 参数值为 trend,表示我们实例化的是趋势图组件,效果如下图所示。 + + + +
当设置 type 为 simple 时,就可以使用简易版的 TimeBar。 + + + +
当设置 type 为 tick 时,表示我们要使用刻度 TimeBar 组件,但此时要注意的是,**刻度时间轴的配置项是通过 tick 对象配置而不是 trend 对象**,这也是刻度时间轴和趋势即简易时间轴不同的地方。 + +```javascript +const timebar = new G6.TimeBar({ + width, + height: 150, + type: 'tick', + tick: { + data: timeBarData, + width, + height: 42, + tickLabelFormatter: (d) => { + const dateStr = `${d.date}`; + if ((count - 1) % 10 === 0) { + return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`; + } + return false; + }, + tooltipFomatter: (d) => { + const dateStr = `${d}`; + return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`; + }, + }, +}); +``` + + + +### 事件监听 + +TimeBar 插件暴露除了几个时机事件,方便用户监听内部状态的变化。以下事件可通过 `graph.on('eventname', e => {})` 进行监听。 + +| 事件名称 | 描述 | +| --- | --- | +| valuechange | 时间轴的时间范围发生变化时触发 | +| timebarstartplay | 时间轴开始播放时触发 | +| timebarendplay | 时间轴播放结束时触发 | + +### API + +#### play + +使用 API 控制时间轴开始播放。e.g. `timebar.play()`。 + +#### pause + +使用 API 控制时间轴暂停播放。e.g. `timebar.pause()`。 + +### 接口定义 + +完整的 TimeBar 的接口定义如下: + +```javascript +interface TimeBarConfig extends IPluginBaseConfig { + // position size + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + readonly padding?: number; + + readonly type?: 'trend' | 'simple' | 'tick'; + // 趋势图配置项 + readonly trend?: TrendConfig; + // 滑块、及前后背景的配置 + readonly slider?: SliderOption; + + // 当 type 是 tick 时,这是 tick 类型时间轴的配置项 + // 当 type 是 trend 或 simple 时,这是时间轴下方时间刻度文本的配置项 + readonly tick?: TimeBarSliceOption | TickCfg; + + // 控制按钮 + readonly controllerCfg?: ControllerCfg; + + // [v4.5.1 起支持] 容器的 CSS 样式 + readonly containerCSS?: Object; + + // [v4.5.1 起支持] 过滤的类型, ['node', 'edge'], 默认为 ['node'] + readonly filterItemTypes?: string[]; + + // [v4.5.1 起废弃,由 filterItemTypes 代替] 是否过滤边,若为 true,则需要配合边数据上有 date 字段,过滤节点同时将不满足 date 在选中范围内的边也过滤出去;若为 false,则仅过滤节点以及两端节点都被过滤出去的边 + readonly filterEdge?: boolean; + + // [v4.5.1 起支持] 是否通过增删图上元素(graph.addItem graph.removeItem)从而达到筛选目的。若为 false 则将使用 graph.hideItem 和 graph.showItem 以隐藏/展示图上元素从而达到筛选目的 + readonly changeData?: boolean; + + // TimeBar 时间范围变化时的回调函数,当不定义该函数时,时间范围变化时默认过滤图上的数据 + rangeChange?: (graph: IGraph, minValue: string, maxValue: string) => void; + + // [v4.5.1 起支持] 用户根据节点/边数据返回对应时间值的方法 + getDate?: (d: any) => number; + + // [v4.5.1 起支持] 用户根据节点/边数据返回对应 value 的方法。value 用于在 type 为 trend 的时间轴上显示趋势线 + getValue?: (d: any) => number; + + // [v4.5.1 起支持] 在过滤图元素时是否要忽略某些元素。返回 true,则忽略。否则按照正常过滤逻辑处理 + shouldIgnore?: (itemType: 'node' | 'edge', model: any, dateRage: { min: number, max: number }) => boolean; +} +``` + +#### 接口参数说明 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | TimeBar 容器,如果不设置,则默认创建 className 为 g6-component-timebar 的 DOM 容器 | +| x | number | 0 | TimeBar 开始 x 坐标 | +| y | number | 0 | TimeBar 开始 y 坐标 | +| width | number | | **必选**,TimeBar 容器宽度 | +| height | number | | **必选**,TimeBar 高度 | +| padding | number/number[] | 10 | TimeBar 距离容器的间距值 | +| type | 'trend' / 'simple' / 'tick' | trend | 默认的 TimeBar 类型,默认为趋势图样式 | +| trend | TrendConfig | null | Timebar 中趋势图的配置项,当 type 为 trend 或 simple 时,该字段必选 | +| slider | SliderOption | null | TimeBar 组件背景及控制调节范围的滑块的配置项 | +| tick | TimeBarSliceOption / TickCfg | null | 当 type 是 tick 时,这是 tick 类型时间轴的配置项,该字段必须按;当 type 是 trend 或 simple 时,这是时间轴下方时间刻度文本的配置项 | +| controllerCfg | ControllerCfg | null | 控制按钮组配置项 | +| containerCSS | Object | null | [v4.5.1 起支持] 容器的 CSS 样式 | +| filterItemTypes | string[] | null | [v4.5.1 起支持] 过滤的类型, ['node', 'edge'], 默认为 ['node'] | +| filterEdge | boolean | false | [v4.5.1 起废弃,由 filterItemTypes 代替] 是否过滤边,若为 true,则需要配合边数据上有 date 字段,过滤节点同时将不满足 date 在选中范围内的边也过滤出去;若为 false,则仅过滤节点以及两端节点都被过滤出去的边 | +| changeData | boolean | null | [v4.5.1 起支持] 是否通过 graph.changeData 改变图上数据从而达到筛选目的。若为 false 则将使用 graph.hideItem 和 graph.showItem 以隐藏/展示图上元素从而达到筛选目的 | +| rangeChange | Function | null | TimeBar 时间范围变化时的回调函数,当不定义该函数时,时间范围变化时默认过滤图上的数据 | +| getDate | (d: any) => number | null | [v4.5.1 起支持] 用户根据节点/边数据返回对应时间值的方法 | +| getValue | (d: any) => number | null | [v4.5.1 起支持] 用户根据节点/边数据返回对应 value 的方法。value 用于在 type 为 trend 的时间轴上显示趋势线 | +| shouldIgnore | (itemType: 'node' | 'edge', model: any, dateRage: { min: number, max: number }) => boolean | null | [v4.5.1 起支持] 在过滤图元素时是否要忽略某些元素。返回 true,则忽略。否则按照正常过滤逻辑处理 | + +#### TrendConfig 接口定义 + +> 暂不支持刻度文本的样式配置 + +```javascript +interface TrendConfig { + // 数据 + readonly data: { + date: string; + value: string; + }[]; + // 位置大小 + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + // 样式 + readonly smooth?: boolean; + readonly isArea?: boolean; + readonly lineStyle?: ShapeStyle; + readonly areaStyle?: ShapeStyle; + readonly interval?: Interval; +} +``` + +#### TrendConfig 参数说明 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| x | number | 0 | 趋势图开始 x 坐标 | +| y | number | 0 | 趋势图开始 y 坐标 | +| width | number | TimeBar 容器宽度 | TimeBar 趋势图宽度,不建议自己设定,如果设定时需要同步设置 slider 中的 width 值 | +| height | number | type=trend:默认为 28
type=simple:默认为 8 | TimeBar 趋势图高度,不建议自己设定,如果设定时需要同步设置 slider 中的 height 值 | +| smooth | boolean | false | 是否是平滑的曲线 | +| isArea | boolean | false | 是否显示面积图 | +| lineStyle | ShapeStyle | null | 折线的样式配置 | +| areaStyle | ShapeStyle | null | 面积的样式配置项,只有当 isArea 为 true 时生效 | +| interval | Interval | null | 柱状图配置项,当配置了该项后,趋势图上会展现为混合图样式。`Interval = { data: number[], style: ShapeStyle }`,`style` 除 `ShapeStyle` 类型中图形的样式外,还可配置 `barWidth` 配置柱状图柱子的宽度。 | + +#### SliderOption 接口定义 + +```javascript +export type SliderOption = Partial<{ + readonly width?: number; + readonly height?: number; + readonly backgroundStyle?: ShapeStyle; + readonly foregroundStyle?: ShapeStyle; + // 滑块样式 + readonly handlerStyle?: { + width?: number; + height?: number; + style?: ShapeStyle; + }; + readonly textStyle?: ShapeStyle; + // 初始位置 + readonly start: number; + readonly end: number; + // 滑块文本 + readonly minText: string; + readonly maxText: string; +}>; +``` + +#### SliderOption 参数说明 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| width | number | TimeBar 容器宽度 - 2 \* padding | 趋势图背景框宽度,不建议自己设定,如果设定时要同步修改 trend 中 width 值 | +| height | number | 趋势图默认为 28
简易版默认为 8 | TimeBar 趋势图高度,不建议自己设定,如果设定时需要同步设置 trend 中的 height 值 | +| backgroundStyle | ShapeStyle | null | 背景样式配置项 | +| foregroundStyle | ShapeStyle | null | 前景色样式配置,即选中范围的样式配置项 | +| handlerStyle | ShapeStyle | null | 滑块的样式配置项 | +| textStyle | ShapeStyle | null | 滑块上文本的样式配置项 | +| start | number | 0.1 | 开始位置 | +| end | number | 0.9 | 结束位置 | +| minText | string | min | 最小值文本 | +| maxText | string | max | 最大值文本 | + +#### TimeBarSliceOption + +```javascript +export interface TimeBarSliceOption { + // position size + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + readonly padding?: number; + + // styles + readonly selectedTickStyle?: TickStyle; + readonly unselectedTickStyle?: TickStyle + readonly tooltipBackgroundColor?: string; + + readonly start?: number; + readonly end?: number; + + // 数据 + readonly data: { + date: string; + value: string; + }[]; + + // 自定义标签格式化函数 + readonly tickLabelFormatter?: (d: any) => string | boolean; + // 自定义 tooltip 内容格式化函数 + readonly tooltipFomatter?: (d: any) => string; +} +``` + +#### TimeBarSliceOption 参数说明 + +| 名称 | 类型 | 默认值 | 描述 | +| ---------------------- | ----------------- | ------ | ------------------------------ | +| x | number | 0 | 刻度 TimeBar 开始 x 坐标 | +| y | number | 0 | 刻度 TimeBar 开始 y 坐标 | +| width | number | | 必选,刻度 TimeBar 宽度 | +| height | number | | 必选,刻度 TimeBar 高度 | +| padding | number / number[] | 0 | 刻度 TimeBar 距离边界的间距 | +| selectedTickStyle | ShapeStyle | null | 选中刻度的样式配置项 | +| unselectedTickStyle | ShapeStyle | null | 未选中刻度的样式配置项 | +| tooltipBackgroundColor | ShapeStyle | null | tooltip 背景框配置项 | +| start | number | 0.1 | 开始位置 | +| end | number | 0.9 | 结束位置 | +| data | any[] | [] | 必选,刻度时间轴的刻度数据 | +| tickLabelFormatter | Function | null | 刻度的格式化回调函数 | +| tooltipFomatter | Function | null | tooltip 上内容格式化的回调函数 | + + +#### TickCfg 接口定义 + +```javascript +export interface TickCfg { + // 时间轴下方文本的格式化函数 + readonly tickLabelFormatter?: (d: any) => string | undefined; + // 时间轴下方文本的图形样式。[v4.5.1 起支持] 可配置 tickLabelStyle.rotate 以控制时间轴下方每个文本的旋转角度,可避免文本相互重叠 + readonly tickLabelStyle?: ShapeStyle; + // 时间轴下方文本上的竖线图形样式 + readonly tickLineStyle?: ShapeStyle; +} +``` + +#### TickCfg 参数说明 + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| tickLabelFormatter | Function | null | 时间轴下方文本的格式化函数 | +| tickLabelStyle | ShapeStyle | {} | 时间轴下方文本的图形样式。[v4.5.1 起支持] 可配置 tickLabelStyle.rotate 以控制时间轴下方每个文本的旋转角度,可避免文本相互重叠 | +| tickLineStyle | ShapeStyle | {} | 时间轴下方文本上方的竖线的图形样式 | + +#### ControllerCfg 接口定义 + +> 暂不支持 + +> 控制按钮暂不支持配置样式 + +> 不支持循环播放 + +```javascript +type ControllerCfg = Partial<{ + + /** 控制栏的起始位置以及宽高,width height 将不缩放内部子控制器,仅影响它们的位置分布。需要缩放请使用 scale */ + readonly x?: number; + readonly y?: number; + readonly width: number; + readonly height: number; + /** 控制栏缩放比例 */ + readonly scale?: number; + /** 控制器背景的颜色和描边色 */ + readonly fill?: string; + readonly stroke?: string; + /** 整个控制栏的字体样式,优先级低于各个子控制器的 text 内的 fontFamily */ + readonly fontFamily?: string; + + /** 播放速度,1 个 tick 花费时间 */ + readonly speed?: number; + /** 是否循环播放 */ + readonly loop?: boolean; + /** 是否隐藏右下角的 ’播放时间类型切换器‘ */ + readonly hideTimeTypeController: boolean; + + /** ‘上一帧’按钮的样式,同时可以为其配置 scale、offsetX、offsetY 单独控制该控制器的缩放以及平移 */ + readonly preBtnStyle?: ShapeStyle; + + /** ‘下一帧’按钮的样式,同时可以为其配置 scale、offsetX、offsetY 单独控制该控制器的缩放以及平移 */ + readonly nextBtnStyle?: ShapeStyle; + + /** ‘播放’ 与 ‘暂停’ 按钮的样式,同时可以为其配置 scale、offsetX、offsetY 单独控制该控制器的缩放以及平移 */ + readonly playBtnStyle?: ShapeStyle; + + /** [v4.7.11 起支持配置] 时间播放类型默认值,不配置则为 'range' 即‘时间范围’ */ + readonly defaultTimeType?: 'single' | 'range'; + + /** ‘速度控制器’ 的样式,包括速度的指针、速度指示滚轮(横线)、文本的样式,同时可以为 speedControllerStyle 及其子图形样式配置 scale、offsetX、offsetY 单独控制该控制器及其子图形的缩放以及平移) */ + readonly speedControllerStyle?: { + offsetX?: number, + offsetY?: number; + scale?: number + pointer?: ShapeStyle, + scroller?: ShapeStyle, + text?: ShapeStyle + }; + + /** ‘播放时间类型切换器’ 的样式,包括 checkbox 的框、checkbox 的选中勾、文本的样式,同时可以为 timeTypeControllerStyle 及其子图形样式配置 scale、offsetX、offsetY 单独控制该控制器及其子图形的缩放以及平移 */ + readonly timeTypeControllerStyle?: { + offsetX?: number, + offsetY?: number; + scale?: number + check?: ShapeStyle, + box?: ShapeStyle, + text?: ShapeStyle + }; + /** [v4.5.1 起支持] 控制栏背景方框的样式 */ + readonly containerStyle?: ExtendedShapeStyle; + /** ‘播放时间类型切换器’单一文本时的文本,默认为‘单一时间’ */ + readonly timePointControllerText?: string; + /** ‘播放时间类型切换器’单一文本时的文本,默认为‘时间范围’ */ + readonly timeRangeControllerText?: string; +}> +``` + +#### ControllerCfg 参数说明 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| x | number | 0 | 控制栏开始 x 坐标 | +| y | number | 0 | 控制栏开始 y 坐标 | +| width | number | TimeBar 宽度 | 控制栏宽度,将不缩放内部子控制器,仅影响它们的位置分布 | +| height | number | 40 | 控制栏高度,将不缩放内部子控制器,仅影响它们的位置分布 | +| scale | number | 1 | 控制栏缩放比例 | +| speed | number | 1 | 播放速度 | +| loop | boolean | false | 暂不支持,是否循环播放 | +| hideTimeTypeController | boolean | true | 是否隐藏时间类型切换 | +| fill | string | | 控制栏背景框填充色 | +| stroke | string | | 整个控制栏的字体样式,优先级低于各个子控制器的 text 内的 fontFamily | +| preBtnStyle | string | null | 控制栏背景框边框色 | +| preBtnStyle | ShapeStyle | null | ‘上一帧’按钮的样式,同时可以为其配置 `scale`、`offsetX`、`offsetY` 单独控制该控制器的缩放以及平移 | +| nextBtnStyle | ShapeStyle | null | ‘下一帧’按钮的样式,同时可以为其配置 `scale`、`offsetX`、`offsetY` 单独控制该控制器的缩放以及平移 | +| playBtnStyle | ShapeStyle | null | ‘播放’ 与 ‘暂停’ 按钮的样式,同时可以为其配置 `scale`、`offsetX`、`offsetY` 单独控制该控制器的缩放以及平移 | +| speedControllerStyle | { offsetX?: number, offsetY?: number, scale?: number, pointer?: ShapeStyle, text?: ShapeStyle, scroller?: ShapeStyle} | null | ‘速度控制器’ 的样式,包括速度的指针、速度指示滚轮(横线)、文本的样式,同时可以为 `speedControllerStyle` 及其子图形样式配置 `scale`、`offsetX`、`offsetY` 单独控制该控制器及其子图形的缩放以及平移 | +| timeTypeControllerStyle | { offsetX?: number, offsetY?: number, scale?: number, box?: ShapeStyle, check?: ShapeStyle, text?: ShapeStyle } | null | ‘播放时间类型切换器’ 的样式,包括 checkbox 的框、checkbox 的选中勾、文本的样式,同时可以为 `timeTypeControllerStyle` 及其子图形样式配置 `scale`、`offsetX`、`offsetY` 单独控制该控制器及其子图形的缩放以及平移 | +| containerStyle | ShapeStyle | {} | 控制栏背景方框的样式 | +| timePointControllerText | string | "单一时间" | 右下角“单一时间”文本,默认为”单一时间“ | +| timePointControllerText | string | "时间范围" | 右下角“单一时间”文本,默认为”时间范围时间“ | diff --git a/packages/site/docs/api/TreeGraph.en.md b/packages/site/docs/api/TreeGraph.en.md new file mode 100644 index 0000000000..4a8c0b7fc2 --- /dev/null +++ b/packages/site/docs/api/TreeGraph.en.md @@ -0,0 +1,68 @@ +--- +title: G6.TreeGraph(cfg) +order: 3 +--- + +Initialize a tree graph. + +```ts +// highlight-start +new G6.TreeGraph(cfg: GraphOptions) => TreeGraph +// highlight-end + +const treeGraph = new G6.TreeGraph({ + container: 'mountNode', + width: 800, + height: 600, + modes: { + default: [ + { + type: 'collapse-expand', + onChange(item, collapsed) { + const icon = item.get('group').findByClassName('collapse-icon'); + if (collapsed) { + icon.attr('symbol', EXPAND_ICON); + } else { + icon.attr('symbol', COLLAPSE_ICON); + } + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + layout: { + type: 'dendrogram', + direction: 'LR', // H / V / LR / RL / TB / BT + nodeSep: 50, + rankSep: 100, + radial: true, + }, +}); +``` + +If you are going to visualize a tree, TreeGraph of G6 is more appropriate than Graph. The main differences between `G6.TreeGraph` and `G6.Graph` are data structure and built-in layout algorithms: + +- Data structure: In G6, the tree data has nested structure. Edges are implicit in it. Each node data has `id` and `children` properties at least: + +```javascript +const data = { + id: 'root', + children: [ + { + id: 'subTree1', + children: [...] + }, + { + id: 'subTree2', + children: [...] + } + ] +}; +``` + +- Tree layout algorithms: + - Tree layout algorithms do not modify the source data. it generates a new data instead. And the source data will be a property of the new data. This mechanism will reduce the complexity of transformation from nested data to nodes and edges in graph. + - The layout will be re-calculated after adding / deleting / expanding / collapsing nodes on the tree. + +TreeGraph is inherited from Graph, please refer to [G6.Graph(GraphOptions)](/en/docs/api/Graph) for its configurations. One difference is the `layout` option. There are four layout algorithms for tree in G6: dendrogram, compactBox, mindmap, and indeted, whose detailed configurations are listed in [TreeGraph Layout](/en/docs/api/treeGraphLayout/guide). diff --git a/packages/site/docs/api/TreeGraph.zh.md b/packages/site/docs/api/TreeGraph.zh.md new file mode 100644 index 0000000000..fc34932b16 --- /dev/null +++ b/packages/site/docs/api/TreeGraph.zh.md @@ -0,0 +1,68 @@ +--- +title: 树图配置 G6.TreeGraph(cfg) +order: 3 +--- + +创建 TreeGraph 实例。 + +```ts +// highlight-start +new G6.TreeGraph(cfg: GraphOptions) => TreeGraph +// highlight-end + +const treeGraph = new G6.TreeGraph({ + container: 'mountNode', + width: 800, + height: 600, + modes: { + default: [ + { + type: 'collapse-expand', + onChange(item, collapsed) { + const icon = item.get('group').findByClassName('collapse-icon'); + if (collapsed) { + icon.attr('symbol', EXPAND_ICON); + } else { + icon.attr('symbol', COLLAPSE_ICON); + } + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + layout: { + type: 'dendrogram', + direction: 'LR', // H / V / LR / RL / TB / BT + nodeSep: 50, + rankSep: 100, + radial: true, + }, +}); +``` + +TreeGraph 是 G6 专门为树图场景打造的图。`G6.TreeGraph` 与 `G6.Graph` 最大的区别就是数据结构和内置布局计算。主要考虑: + +- 数据结构:树图的数据一般是嵌套结构,边的数据隐含在嵌套结构中,并不会特意指定 edge 。此布局要求数据中一个节点需要有 `id` 和 `children` 两个数据项,最精简的数据结构如下所示: + +```javascript +const data = { + id: 'root', + children: [ + { + id: 'subTree1', + children: [...] + }, + { + id: 'subTree2', + children: [...] + } + ] +}; +``` + +- 布局特殊性: + - 树图的布局算法一般是不改变源数据的,而是重新生成一份数据,将源数据作为新数据的一个属性。如果每次都需要做次遍历转换数据到节点和边的数据增加了用户的实现复杂度。 + - 树图的每次新增/删除/展开/收缩节点,都需要重新计算布局。遍历一份结构化数据对应到图上每个节点去做更新操作,也很麻烦。 + +TreeGraph 继承自 Graph,配置项参考 [G6.Graph(GraphOptions)](/zh/docs/api/Graph)。其中, layout 配置项支持的布局类型 (`type` 属性) 和 Graph 中所支持的类型不同,TreeGraph 中 layout 目前支持 dendrogram、compactBox、mindmap 和 indeted 四种布局方式,具体配置方式见 [TreeGraph Layout](/zh/docs/api/treeGraphLayout/guide)。 diff --git a/packages/site/docs/api/Util.en.md b/packages/site/docs/api/Util.en.md new file mode 100644 index 0000000000..3a4226dfb6 --- /dev/null +++ b/packages/site/docs/api/Util.en.md @@ -0,0 +1,166 @@ +--- +title: Util +order: 15 +--- + +G6 provides a set of util functions for data pre-processing and graphics computation. + +## Usage + +Import G6, and call a util function by `G6.Util.functionName`. The following demo shows the usage of `processParallelEdges` to process multiple edges between two nodes. + +```javascript +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: '1' }, { id: '2' } + ] + edges: [ + { source: '1', target: '2' }, + { source: '1', target: '2' }, + ]; +} + +const offsetDiff = 10; +const multiEdgeType = 'quadratic'; +const singleEdgeType = 'line'; +const loopEdgeType = 'loop'; +G6.Util.processParallelEdges(data.edges, offsetDiff, multiEdgeType, singleEdgeType, loopEdgeType); +``` + +## Data Pre-processing + +### processParallelEdges Process Multiple Edges + +If the two end nodes of the two edges are the same, the two edges are said to be parallel to each other. When there are multiple edges between a pair of nodes, rendering them directly without processing may lead to edge overlappings. `processParallelEdges` will find the parallel edges in the `edges` and calculate a reasonable control point offset `curveOffset` of Bezier curve for them, and assign `curveOffset` to the edge data. Then, the `curveOffset` will take effects while rendering with the edge type `quadratic` or a custom type based on `quadratic`. + +img + +#### Configurations + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| edges | EdgeConfig[] | true | The edge data array need to be processed | +| offsetDiff | number | false | The offset between two parallel edges, 15 by default | +| multiEdgeType | string | false | The edge type for the parallel edges, 'quadratic' by default. You can assign any custom edge type based on 'quadratic' to it | +| singleEdgeType | string | false | The edge type for the single edge between two nodes, undefined by default, which means the type of the edge is kept unchanged as it is in the input data | +| loopEdgeType | string | false | The edge type for a self-loop edge, undefined by default, which means the type of the edge is kept unchanged as it is in the input data | + +#### Usage + +[Demo](/en/examples/item/multiEdge#multiEdges) + +### traverseTree: Depth-first Top to Bottom Tree Data Traversing + +Traverse the tree data depth-first from top (the root) to bottom (the leaves). + +#### Configurations + +| Name | Type | Required | Description | +| ---- | -------- | -------- | ---------------------------------------------- | +| data | TreeData | true | The tree data to be traversed | +| fn | function | true | The callback function called when visit a node. Returning `false` from the callback function will stop traversal. | + +Parameters of the callback function `fn` in the table above: + +| Name | Type | Description | +| -------- | -------- | ------------------------------------------------------ | +| node | T | Tree node being currently visited | +| parent | T | null | Parent of the current tree node | +| index | number | Index of current tree node among the parent's children | + +#### Usage + +```javascript +const treeData = { + id: '1', + children: [ + { + id: '2', + children: [{ id: '3' }, { id: '4' }], + }, + { + id: '5', + children: [ + { id: '6' }, + { + id: '7', + children: [{ id: '8' }, { id: '9' }], + }, + ], + }, + { + id: '10', + children: [{ id: '11' }], + }, + ], +}; + +traverseTree(treeData, (subTree) => { + subTree.color = '#f00'; + return true; +}); +``` + +### traverseTreeUp Depth-first Bottom to Top Tree Data Traversing + +Traverse the tree data depth-first from bottom (the leaves) to top (the root). + +#### Configurations + +| Name | Type | Required | Description | +| ---- | -------- | -------- | ---------------------------------------------- | +| data | TreeData | true | The tree data to be traversed | +| fn | function | true | The callback function called when visit a node. Returning `false` from the callback function will stop traversal. | + +Parameters of the callback function `fn` in the table above: + +| Name | Type | Description | +| -------- | -------- | ------------------------------------------------------ | +| node | T | Tree node being currently visited | +| parent | T | null | Parent of the current tree node | +| index | number | Index of current tree node among the parent's children | + +#### Usage + +```javascript +const treeData = { + id: '1', + children: [ + { + id: '2', + children: [{ id: '3' }, { id: '4' }], + }, + { + id: '5', + children: [ + { id: '6' }, + { + id: '7', + children: [{ id: '8' }, { id: '9' }], + }, + ], + }, + { + id: '10', + children: [{ id: '11' }], + }, + ], +}; + +traverseTreeUp(treeData, (subTree) => { + subTree.color = '#f00'; + return true; +}); +``` + +## Bounding Box Calculation + +### calculationItemsBBox Returns the BBox of a Set of Node Items + +Returns the bounding box of a set of node items. + +| Name | Type | Required | Description | +| ----- | ------ | -------- | ---------------------------------------- | +| items | Item[] | true | The array of node items to be calculated | diff --git a/packages/site/docs/api/Util.zh.md b/packages/site/docs/api/Util.zh.md new file mode 100644 index 0000000000..4a43bd8529 --- /dev/null +++ b/packages/site/docs/api/Util.zh.md @@ -0,0 +1,166 @@ +--- +title: 工具方法 Util +order: 15 +--- + +G6 提供了一些工具方法,方便用户做数据的预处理、图形计算等。 + +## 使用方法 + +引入 G6 后,可通过 `G6.Util.functionName` 调用到 G6 抛出的工具方法。下面代码演示了使用 `processParallelEdges` 处理两节点之间存在多条边的情况。 + +```javascript +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: '1' }, { id: '2' } + ] + edges: [ + { source: '1', target: '2' }, + { source: '1', target: '2' }, + ]; +} + +const offsetDiff = 10; +const multiEdgeType = 'quadratic'; +const singleEdgeType = 'line'; +const loopEdgeType = 'loop'; +G6.Util.processParallelEdges(data.edges, offsetDiff, multiEdgeType, singleEdgeType, loopEdgeType); +``` + +## 数据预处理 + +### processParallelEdges 处理平行边 + +若两条边的两个端点相同,则称这两条边相互平行。当一对节点节点之间存在多条边,不做处理直接绘制可能会导致边相互重叠。该 Util 方法将找到数据中的平行边,为它们计算合理的贝塞尔曲线控制点偏移量 `curveOffset`,挂载到相应的边数据上,使得平行边在渲染时可以根据 `curveOffset` 绘制贝塞尔曲线,从而不相互重叠。因此该方法适用于将平行边处理为 `quadratic` 或基于 `quadratic` 自定义的边类型。 + +img + +#### 配置项 + +| 名称 | 类型 | 是否必须 | 描述 | +| --- | --- | --- | --- | +| edges | EdgeConfig[] | true | 需要处理的边数据数组 | +| offsetDiff | number | false | 两条平行边的之间的距离,默认为 15 | +| multiEdgeType | string | false | 两节点之间若存在多条边时,这些边的类型,默认为 'quadratic' | +| singleEdgeType | string | false | 两节点之间仅有一条边时,该边的类型,默认为 undefined,即不改变这种边的类型 | +| loopEdgeType | string | false | 若一条边的起点和终点是同一个节点(自环边),该边的类型,默认为 undefined,即不改变这种边的类型 | + +#### 使用示例 + +[Demo](/zh/examples/item/multiEdge#multiEdges) + +### traverseTree 深度优先遍历树数据 + +从根节点到叶子节点的由上至下的深度优先遍历树数据。 + +#### 配置项 + +| 名称 | 类型 | 是否必须 | 描述 | +| ---- | -------- | -------- | -------------------------- | +| data | TreeData | true | 需要遍历的树数据 | +| fn | function | true | 遍历到每个节点时的回调函数,返回 `false` 将终止遍历 | + + +上表中的回调函数 fn 的参数: + +| Name | Type | Description | +| -------- | -------- | ------------------------------------------------------ | +| node | T | 当前正在访问的树上的节点 | +| parent | T | null | 当前节点的父节点 | +| index | number | 当前节点在父节点的子节点列表中的顺序 | + +#### 使用示例 + +```javascript +const treeData = { + id: '1', + children: [ + { + id: '2', + children: [{ id: '3' }, { id: '4' }], + }, + { + id: '5', + children: [ + { id: '6' }, + { + id: '7', + children: [{ id: '8' }, { id: '9' }], + }, + ], + }, + { + id: '10', + children: [{ id: '11' }], + }, + ], +}; + +traverseTree(treeData, (subTree) => { + subTree.color = '#f00'; + return true; +}); +``` + +### traverseTreeUp 深度优先遍历树数据 + +从叶子节点到根节点的由下至上的深度优先遍历树数据。 + +#### 配置项 + +| 名称 | 类型 | 是否必须 | 描述 | +| ---- | -------- | -------- | -------------------------- | +| data | TreeData | true | 需要遍历的树数据 | +| fn | function | true | 遍历到每个节点时的回调函数,返回 `false` 将终止遍历 | + +上表中的回调函数 fn 的参数: + +| Name | Type | Description | +| -------- | -------- | ------------------------------------------------------ | +| node | T | 当前正在访问的树上的节点 | +| parent | T | null | 当前节点的父节点 | +| index | number | 当前节点在父节点的子节点列表中的顺序 | + + +#### 使用示例 + +```javascript +const treeData = { + id: '1', + children: [ + { + id: '2', + children: [{ id: '3' }, { id: '4' }], + }, + { + id: '5', + children: [ + { id: '6' }, + { + id: '7', + children: [{ id: '8' }, { id: '9' }], + }, + ], + }, + { + id: '10', + children: [{ id: '11' }], + }, + ], +}; + +traverseTreeUp(treeData, (subTree) => { + subTree.color = '#f00'; + return true; +}); +``` + +## 包围盒计算 + +### calculationItemsBBox 一组节点的总包围盒 + +| 名称 | 类型 | 是否必须 | 描述 | +| ----- | ------ | -------- | -------- | +| items | Item[] | true | 节点数组 | diff --git a/packages/site/docs/api/graphFunc/animation.en.md b/packages/site/docs/api/graphFunc/animation.en.md new file mode 100644 index 0000000000..c7693682f4 --- /dev/null +++ b/packages/site/docs/api/graphFunc/animation.en.md @@ -0,0 +1,22 @@ +--- +title: Animation +order: 14 +--- + +### graph.positionsAnimate() + +Update the node positions according to the data model animatively. The `animateCfg` of the graph will be the animation configurations. + +### graph.stopAnimate() + +Stop the animation on the canvas. + +**Usage** + +```javascript +graph.stopAnimate(); +``` + +### graph.isAnimating() + +Return if the graph is animating. diff --git a/packages/site/docs/api/graphFunc/animation.zh.md b/packages/site/docs/api/graphFunc/animation.zh.md new file mode 100644 index 0000000000..3fe73bbec5 --- /dev/null +++ b/packages/site/docs/api/graphFunc/animation.zh.md @@ -0,0 +1,22 @@ +--- +title: 动画相关方法 +order: 14 +--- + +### graph.positionsAnimate() + +根据当前数据中的节点位置,动画更新节点位置。将会使用 graph 上的 `animateCfg` 配置项作为动画行为的依据。 + +### graph.stopAnimate() + +停止画布上的所有动画。 + +**用法** + +```javascript +graph.stopAnimate(); +``` + +### graph.isAnimating() + +判断当前是否有正在执行的动画。 diff --git a/packages/site/docs/api/graphFunc/behavior.en.md b/packages/site/docs/api/graphFunc/behavior.en.md new file mode 100644 index 0000000000..69d66b9d36 --- /dev/null +++ b/packages/site/docs/api/graphFunc/behavior.en.md @@ -0,0 +1,92 @@ +--- +title: Add/Remove Behaviors +order: 10 +--- + +### graph.addBehaviors(behaviors, modes) + +Add interaction behaviors to a mode or multiple modes. + +**Parameters** + +| Name | Type | Required | Description | +| --------- | -------------- | -------- | --------------------------------------- | +| behaviors | string / Array | true | The name(s) of behavior(s) to be added. | +| modes | string / Array | true | The name(s) of mode(s) | + +**Usage** + +```javascript +// Add single behavior 'click-select' to a single mode 'default' +graph.addBehaviors('click-select', 'default'); + +// Add multiple behaviors to single mode 'default' +graph.addBehaviors(['brush-select', 'click-select'], 'default'); + +// Add single behavior 'brush-select' to multiple modes +graph.addBehaviors('brush-select', ['default', 'select']); + +// Add multiple behaviors to multiple modes +graph.addBehaviors(['brush-select', 'click-select'], ['default', 'select']); +``` + +### graph.removeBehaviors(behaviors, modes) + +Remove behavior(s) from mode(s). + +**Parameters** + +| Name | Type | Required | Description | +| --------- | -------------- | -------- | ----------------------------------------- | +| behaviors | string / Array | true | The name(s) of behavior(s) to be removed. | +| modes | string / Array | true | The name(s) of mode(s). | + +**Usage** + +```javascript +// remove single behavior 'click-select' from single mode 'default' +graph.removeBehaviors('click-select', 'default'); + +// remove multiple behaviors from single mode 'default' +graph.removeBehaviors(['brush-select', 'click-select'], 'default'); + +// remove single behavior 'brush-select' from multiple modes +graph.removeBehaviors('brush-select', ['default', 'select']); + +// remove multiple behaviors from multiple modes +graph.removeBehaviors(['brush-select', 'click-select'], ['default', 'select']); +``` + +### graph.updateBehavior(behavior, mode) + +Update the configurations for a behavior from mode. + +**Parameters** + +| Name | Type | Required | Description | +| --------- | -------------- | -------- | ----------------------------------------- | +| behavior | string | true | The type name of the behavior need to be updated | +| newCfg | object | true | The new configurations | +| mode | string | false | The mode name of the mode where the behavior need to be updated. 'default' by default | + +**Usage** + +```javascript +const graph = new Graph({ + ... // Other graph configurations + modes: { + default: ['zoom-canvas', 'drag-canvas'], + select: ['click-select'] + } +}); + +graph.data(data); +graph.render(); + +// update the behavior 'zoom-canvas' from mode 'default' +graph.updateBehavior('zoom-canvas', { sensitivity: 1.5, enableOptimize: true}, 'default'); + +// update the behavior 'click-select' from mode 'select' +graph.updateBehavior('click-select', { trigger: 'ctrl' }, 'select'); +``` + diff --git a/packages/site/docs/api/graphFunc/behaviors.zh.md b/packages/site/docs/api/graphFunc/behaviors.zh.md new file mode 100644 index 0000000000..180666a9af --- /dev/null +++ b/packages/site/docs/api/graphFunc/behaviors.zh.md @@ -0,0 +1,92 @@ +--- +title: 增删复合交互 +order: 10 +--- + +### graph.addBehaviors(behaviors, modes) + +新增行为,将单个或多个行为添加到单个或多个模式中。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --------- | -------------- | -------- | ---------------- | +| behaviors | string / Array | true | 添加的行为的名称 | +| modes | string / Array | true | 模式的名称 | + +**用法** + +```javascript +// 将单个 Behavior 添加到单个模式(默认的 default 模式)中 +graph.addBehaviors('click-select', 'default'); + +// 将多个 Behavior 添加到单个模式(默认的 default 模式)中 +graph.addBehaviors(['brush-select', 'click-select'], 'default'); + +// 将单个 Behavior 添加到多个模式中 +graph.addBehaviors('brush-select', ['default', 'select']); + +// 将多个 Behavior 添加到多个模式中 +graph.addBehaviors(['brush-select', 'click-select'], ['default', 'select']); +``` + +### graph.removeBehaviors(behaviors, modes) + +移除行为,将单个或多个行为从单个或多个模式中去除。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --------- | -------------- | -------- | ---------------- | +| behaviors | string / Array | true | 删除的行为的名称 | +| modes | string / Array | true | 模式的名称 | + +**用法** + +```javascript +// 从单个模式中移除单个 Behavior +graph.removeBehaviors('click-select', 'default'); + +// 从单个模式中移除多个 Behavior +graph.removeBehaviors(['brush-select', 'click-select'], 'default'); + +// 从多个模式中移除单个 Behavior +graph.removeBehaviors('brush-select', ['default', 'select']); + +// 从多个模式中移除多个 Behavior +graph.removeBehaviors(['brush-select', 'click-select'], ['default', 'select']); +``` + +### graph.updateBehavior(behavior, mode) + +更新某个模式下的 behavior 的参数。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --------- | -------------- | -------- | ----------------------------------------- | +| behavior | string | true | 需要更新的 behavior 类型名 | +| newCfg | object | true | 新的配置项 | +| mode | string | false | 需要修改的 behavior 所在的模式名称,默认为 'default' | + +**用法** + +```javascript +const graph = new Graph({ + ... // 其他配置项 + modes: { + default: ['zoom-canvas', 'drag-canvas'], + select: ['click-select'] + } +}) + +graph.data(data); +graph.render(); + +// 更新 'default' 模式下的 behavior 'zoom-canvas' +graph.updateBehavior('zoom-canvas', { sensitivity: 1.5, enableOptimize: true}, 'default'); + +// 更新 'select' 模式下的 behavior 'click-select' +graph.updateBehavior('click-select', { trigger: 'ctrl' }, 'select'); +``` + diff --git a/packages/site/docs/api/graphFunc/calculation.en.md b/packages/site/docs/api/graphFunc/calculation.en.md new file mode 100644 index 0000000000..b956c6b669 --- /dev/null +++ b/packages/site/docs/api/graphFunc/calculation.en.md @@ -0,0 +1,56 @@ +--- +title: Calculation +order: 12 +--- + +### graph.getNodeDegree(node, degreeType, refresh) + +Get the in-degree, out-degree, degree, or all of the three kinds of degree. + +**Parameter** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| node | string / INode | true | Node's ID or item | +| degreeType | `'in'` \ `'out'` \ `'total'` \ `'all'` | false | The degree type. If it is assigned to `'in'`, returns the in-degree; `'out'` returns out-degree; `'total'` returns total degree; `'all'` returns an object contains three kinds of the degree: `{ inDegree, outDegree, degree}`; If it is not assigned, returns total degree as default | +| refresh | boolean | false | Whether to force refresh the degree for the whole graph. The default value is `false`. You should assign it to be true after adding edges by `addItem` | + +**Usage** + +```javascript +graph.getNodeDegree('node1', 'in'); +``` + +### graph.getShortestPathMatrix(cache, directed) + +Get all-pairs shortest-path matrix of the graph. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| cache | boolean | false | Whether to use the cached matrix, 'true' by default. | +| directed | boolean | false | Whether the graph is directed, use the value of `graph.get('directed')` by default. | + +**Usage** + +```javascript +const matrix = graph.getShortestPathMatrix(); +``` + +### graph.getAdjMatrix(cache, directed) + +Get the adjacency matrix of the graph. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| cache | boolean | false | Whether to use the cached matrix, 'true' by default. | +| directed | boolean | false | Whether the graph is directed, use the value of `graph.get('directed')` by default. | + +**Usage** + +```javascript +const matrix = graph.getAdjMatrix(); +``` diff --git a/packages/site/docs/api/graphFunc/calculation.zh.md b/packages/site/docs/api/graphFunc/calculation.zh.md new file mode 100644 index 0000000000..b0f6a0f684 --- /dev/null +++ b/packages/site/docs/api/graphFunc/calculation.zh.md @@ -0,0 +1,64 @@ +--- +title: 图计算相关 +order: 12 +--- + +### graph.getNodeDegree(node, degreeType, refresh) + +获取节点的出度、入度、总度数,或同时获得以上三种。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| node | string / INode | true | 节点 ID 或实例 | +| degreeType | `'in'` \ `'out'` \ `'total'` \ `'all'` | false | 获取度数的类型。设置为 `'in'` 将返回入度;`'out'` 将返回出度;`'total'` 将返回总度数;`'all'` 将返回一个含有三种度数的对象:`{ inDegree, outDegree, degree}`;若不指定,将返回总度数 | +| refresh | boolean | false | 是否强制更新整个图的度数缓存。默认为 `false`。当通过 `addItem` 添加边后,再使用 getNodeDegree 时应当将 `refresh` 设置为 `true` | + +**用法** + +```javascript +graph.getNodeDegree('node1', 'in'); +``` + +### graph.getShortestPathMatrix(cache, directed) + +获取图中两两节点之间的最短路径矩阵。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| -------- | ------- | -------- | ------------------------------------------ | +| cache | boolean | false | 是否使用缓存,默认为 true | +| directed | boolean | false | 是否是有向图,默认取 graph.get('directed') | + +**返回值** + +返回图的最短路径矩阵。 + +**用法** + +```javascript +const matrix = graph.getShortestPathMatrix(); +``` + +### graph.getAdjMatrix(cache, directed) + +获取邻接矩阵。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| -------- | ------- | -------- | ------------------------------------------ | +| cache | boolean | false | 是否使用缓存,默认为 true | +| directed | boolean | false | 是否是有向图,默认取 graph.get('directed') | + +**返回值** + +返回图的邻接矩阵。 + +**用法** + +```javascript +const matrix = graph.getAdjMatrix(); +``` diff --git a/packages/site/docs/api/graphFunc/combo.en.md b/packages/site/docs/api/graphFunc/combo.en.md new file mode 100644 index 0000000000..8bd153ce13 --- /dev/null +++ b/packages/site/docs/api/graphFunc/combo.en.md @@ -0,0 +1,248 @@ +--- +title: Combo Operation +order: 6 +--- + +### graph.node(nodeFn) + +Set the style and other configurations for each node. + +⚠️Attention: this funcion must **be called before graph.render()**. It does not take effect otherwise. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | -------- | -------- | ---------------------------------------- | +| nodeFn | Function | true | Return the configurations for each node. | + +**Usage** + +```javascript +graph.node((node) => { + return { + id: node.id, + type: 'rect', + style: { + fill: 'blue', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +### graph.edge(edgeFn) + +Set the style and other configurations for each edge. + +⚠️Attention: this funcion must **be called before graph.render()**. It does not take effect otherwise. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | -------- | -------- | ---------------------------------------- | +| edgeFn | Function | true | Return the configurations for each edge. | + +**Usage** + +```javascript +graph.edge((edge) => { + return { + id: edge.id, + type: 'cubic-horizontal', + style: { + stroke: 'green', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +### graph.combo(comboFn) + +Set the style and other configurations for each combo. + +⚠️Attention: this funcion must **be called before graph.render()**. It does not take effect otherwise. + +**Parameters** + +| Name | Type | Required | Description | +| ------- | -------- | -------- | ----------------------------------------- | +| comboFn | Function | true | Return the configurations for each combo. | + +**Usage** + +```javascript +graph.combo((combo) => { + return { + id: combo.id, + type: 'rect', + style: { + stroke: 'green', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +### graph.collapseCombo(combo) + +Collapse a Combo. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | --------------- | -------- | ----------------------------------------------------- | +| combo | string / ICombo | true | The ID of the combo or the combo item to be collapsed | + +**Usage** + +```javascript +graph.collapseCombo('combo1') +``` + +### graph.expandCombo(combo) + +Expand a Combo. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | --------------- | -------- | ---------------------------------------------------- | +| combo | string / ICombo | true | The ID of the combo or the combo item to be expanded | + +**Usage** + +```javascript +graph.expandCombo('combo1') +``` + +### graph.collapseExpandCombo(combo) + +Expand the `combo` if it is collapsed. Collapse the `combo` if it is expanded. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| combo | string / ICombo | true | The ID of the combo or the combo item to be collapsed or expanded | + +**Usage** + +```javascript +graph.collapseExpandCombo('combo1') +``` + +### graph.createCombo(combo, elements, stack) + +Create a new combo with existing nodes or combos to be its children. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| combo | string / ICombo | true | The ID or the configuration of the combo to be created | +| elements | string[] | The IDs of the existing nodes or combos to be the children of the newly created combo | +| stack | boolean | false | **Supported by v4.7.17 and later versions** Whether to push the operator into the undo & redo stack. If the `enableStack` is `true`, this operation will be automatically pushed into the stack by default. Set `stack` to be `false` if you do not want it. | + + +**Usage** + +```javascript +// The first parameter is the id of the combo to be created +graph.createCombo('combo1', ['node1', 'node2', 'combo2']) + +// The first parameter is the configuration of the combo to be created +graph.createCombo({ + id: 'combo1', + style: { + fill: '#f00' + } +}, ['node1', 'node2', 'combo2']) +``` + +### graph.uncombo(combo, stack) + +Ungroup the combo, which deletes the combo itself, and appends the children of the combo to its parent(if it exists). + +**Parameters** + +| Name | Type | Required | Description | +| ----- | --------------- | -------- | -------------------------------------------------- | +| combo | string / ICombo | true | The ID of the item or the combo item to be updated | +| stack | boolean | false | **Supported by v4.7.17 and later versions** Whether to push the operator into the undo & redo stack. If the `enableStack` is `true`, this operation will be automatically pushed into the stack by default. Set `stack` to be `false` if you do not want it. | + +**Usage** + +```javascript +graph.uncombo('combo1') +``` + +### graph..updateCombos() + +Update the sizes and positions of all the combos according to the bboxes of its children. + +**Usage** + +```javascript +// Update all the combos +graph.updateCombos(); +``` + +### graph.updateCombo(combo) + +Update the positions and sizes of the combo and all of its ancestors. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | --------------- | -------- | ----------------------------------- | +| combo | string / ICombo | true | The ID or the instance of the combo | + +**Usage** + +```javascript +// Update a node's position +const node1 = graph.findById('node1'); +graph.updateItem(node1, { + x: 100, + y: 100, +}); + +// the combo who contains the node +const comboId = node1.getModel().comboId; + +// Update the combo and all its ancestors who contains node1 +graph.updateCombo(comboId); +``` + +### graph.updateComboTree(item, parentId) + +Update the hierarchy structure of the combo, such as move a combo into another one. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| item | string / INode / ICombo | The ID or the item of the node/combo to be updated | +| parentId | string | undefined | The ID of the new parent combo, undefined means updating the item with no parent | + +**Usage** + +```javascript +// move combo1 out of its parent combo. combo1 will be in the same hierarchy level as its old parent. +graph.updateComboTree('combo1') + +// move combo1 into combo2. combo1 will be the child of combo2. +graph.updateComboTree('combo1', 'combo2') +``` + + +### Comparison for Combo and Hull + +combo-hull \ No newline at end of file diff --git a/packages/site/docs/api/graphFunc/combo.zh.md b/packages/site/docs/api/graphFunc/combo.zh.md new file mode 100644 index 0000000000..15795308cc --- /dev/null +++ b/packages/site/docs/api/graphFunc/combo.zh.md @@ -0,0 +1,158 @@ +--- +title: Combo 操作 +order: 6 +--- + +### graph.collapseCombo(combo) + +收起指定的 Combo。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | --------------- | -------- | ---------------------- | +| combo | string / ICombo | true | combo ID 或 combo 实例 | + +**用法** + +```javascript +graph.collapseCombo('combo1') +``` + +### graph.expandCombo(combo) + +展开指定的 Combo。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | --------------- | -------- | ---------------------- | +| combo | string / ICombo | true | combo ID 或 combo 实例 | + +**用法** + +```javascript +graph.expandCombo('combo1') +``` + +### graph.collapseExpandCombo(combo) + +展开或收缩指定的 Combo。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | --------------- | -------- | ---------------------- | +| combo | string / ICombo | true | combo ID 或 combo 实例 | + +**用法** + +```javascript +graph.collapseExpandCombo('combo1') +``` + +### graph.createCombo(combo, elements, stack) + +根据已经存在的节点或 combo 创建新的 combo。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| -------- | -------------------- | -------- | ------------------------------------------ | +| combo | string / ComboConfig | true | combo ID 或 Combo 配置 | +| elements | string[] | true | 添加到 Combo 中的元素 ID,包括节点和 combo | +| stack | boolean | false | **v4.7.17 及后续版本支持** 操作是否入 undo & redo 栈,当实例化 Graph 时设置 enableStack 为 true 时,默认情况下会自动入栈,入栈以后,就支持 undo & redo 操作,如果不需要,则设置该参数为 false 即可 | + +**用法** + +```javascript +// 第一个参数为 combo ID +graph.createCombo('combo1', ['node1', 'node2', 'combo2']) + +// 第一个参数为 combo 配置 +graph.createCombo({ + id: 'combo1', + style: { + fill: '#f00' + } +}, ['node1', 'node2', 'combo2']) +``` + +### graph.uncombo(combo, stack) + +拆解 Combo,即拆分组/解组。调用后,combo 本身将被删除,而该分组内部的子元素将会成为该分组父分组(若存在)的子元素。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | --------------- | -------- | ----------------------------- | +| combo | string / ICombo | true | 需要被拆解的 Combo item 或 id | +| stack | boolean | false | **v4.7.17 及后续版本支持** 操作是否入 undo & redo 栈,当实例化 Graph 时设置 enableStack 为 true 时,默认情况下会自动入栈,入栈以后,就支持 undo & redo 操作,如果不需要,则设置该参数为 false 即可 | + +**用法** + +```javascript +graph.uncombo('combo1') +``` + + +### graph.updateCombos() + +根据子元素(子节点与子 combo)的 bbox 更新所有 combos 的绘制,包括 combos 的位置和范围。 + +**用法** + +```javascript +// 更新所有 combos +graph.updateCombos(); +``` + +### graph.updateCombo(combo) + +仅更新 combo 及其所有祖先 combo。建议在使用 graph.updateItem 来更新节点位置时,调用该方法以更新节点的祖先 combos。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | --------------- | -------- | ---------------------- | +| combo | string / ICombo | true | Combo ID 或 Combo 实例 | + +**用法** + +```javascript +// 更新了某个节点的位置 +const node1 = graph.findById('node1'); +graph.updateItem(node1, { + x: 100, + y: 100, +}); +const comboId = node1.getModel().comboId; + +// 更新 node1 所属的 combo 及其所有祖先 combo 的大小和位置 +graph.updateCombo(comboId); +``` + +### graph.updateComboTree(item, parentId) + +更新 Combo 结构,例如移动子树等。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| -------- | ----------------------- | -------- | ------------------------------------------- | +| item | string / INode / ICombo | true | 需要被更新的 Combo 或 节点 ID | +| parentId | string / undefined | false | 新的父 combo ID,undefined 代表没有父 combo | + +**用法** + +```javascript +// 将 combo1 从父 combo 中移出,完成后同原父 combo 平级 +graph.updateComboTree('combo1') + +// 将 combo1 移动到 Combo2 下面,作为 Combo2 的子元素 +graph.updateComboTree('combo1', 'combo2') +``` + +### 比较 Combo 与 Hull + +combo-hull diff --git a/packages/site/docs/api/graphFunc/coordinate.en.md b/packages/site/docs/api/graphFunc/coordinate.en.md new file mode 100644 index 0000000000..931d457f7c --- /dev/null +++ b/packages/site/docs/api/graphFunc/coordinate.en.md @@ -0,0 +1,138 @@ +--- +title: Coordinate Transformation +order: 15 +--- + +In this part, we will describe the methods about transformation between point canvas, and client coordinates. The relationships and details could be refered to [Coordinate Systems in G6](/en/docs/manual/advanced/coordinate-system). + +### graph.getPointByClient(clientX, clientY) + +Transform client/screen coordinates into point coordinates. + +**Parameters** + +| Name | Type | Required | Description | +| ------- | ------ | -------- | ------------------------------ | +| clientX | Number | true | x coordinate of client/screen. | +| clientY | Number | true | y coordinate of client/screen. | + +**Return** + +- Type of the return value: Object; +- Includes x and y. + +**Usage** + +```javascript +const point = graph.getPointByClient(e.clientX, e.clientY); +console.log('The x and y of point coordinate system are: ', point.x, point.y); +``` + +### graph.getClientByPoint(x, y) + +Transform point coordinates into client/screen coordinates. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | -------------------------- | +| x | Number | true | x coordinate of point coordinate system. | +| y | Number | true | y coordinate of point coordinate system. | + +**Return** + +- Type of the return value: Object; +- Includes `x` and `y`. + +**Usage** + +```javascript +const point = graph.getClientByPoint(100, 200); +console.log('The x and y of client/screen are: ', point.x, point.y); +``` + +### graph.getPointByCanvas(canvasX, canvasY) + +Transform canvas coordinates into point coordinates. + +**Parameters** + +| Name | Type | Required | Description | +| ------- | ------ | -------- | --------------------------- | +| canvasX | Number | true | The x coordinate of canvas. | +| canvasY | Number | true | The y coordinate of canvas. | + +**Return** + +- Type of the return value: Object; +- Include x and y. + +**Usage** + +```javascript +const point = graph.getPointByCanvas(100, 200); +console.log('The x and y of point coordinate system: ', point.x, point.y); +``` + +### graph.getCanvasByPoint(x, y) + +Transform point coordinates into canvas coordinates. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ------------------------------ | +| x | Number | true | The x coordinate of point coordinate system. | +| y | Number | true | The y coordinate of point coordinate system. | + +**Return** + +- Type of the return value: Object; +- Includes x and y. + +**Usage** + +```javascript +const point = graph.getCanvasByPoint(100, 200); +console.log('The x and y coordinates of canvas: ', point.x, point.y); +``` + +### graph.getGraphCenterPoint() + +Get the x/y in point coordinate system of the graph content's center. Supported by v4.2.1 + +**Parameters** + +None + +**Return** + +- Type of the return value: Object; +- Includes x and y of point coordinate system. + +**Usage** + +```javascript +const point = graph.getGraphCenterPoint(); +console.log('graph content center under point coordinate system', point.x, point.y); +``` + +### graph.getViewPortCenterPoint() + +Get the x/y in point coordinate system of the view port center. Supported by v4.2.1 + +**Parameters** + +None + +**Return** + +- Type of the return value: Object; +- Includes x and y of point coordinate system. + +**Usage** + +```javascript +const point = graph.getViewPortCenterPoint(); +console.log('view port center under point coordinate system', point.x, point.y); +``` diff --git a/packages/site/docs/api/graphFunc/coordinate.zh.md b/packages/site/docs/api/graphFunc/coordinate.zh.md new file mode 100644 index 0000000000..fbb0c35893 --- /dev/null +++ b/packages/site/docs/api/graphFunc/coordinate.zh.md @@ -0,0 +1,141 @@ +--- +title: 坐标转换 +order: 15 +--- + +这部分主要是说明渲染坐标、Canvas 坐标和页面坐标之前的相互转换。三种坐标系的关系参见文档:[G6 坐标系深度解析](/zh/docs/manual/advanced/coordinate-system)。 + +img + + +### graph.getPointByClient(clientX, clientY) + +将屏幕/页面坐标转换为渲染坐标。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------- | ------ | -------- | ----------- | +| clientX | Number | true | 屏幕 x 坐标 | +| clientY | Number | true | 屏幕 y 坐标 | + +**返回值** + +- 返回值类型:Object; +- 包含的属性:x 和 y 属性,分别表示渲染的 x 、y 坐标。 + +**用法** + +```javascript +const point = graph.getPointByClient(e.clientX, e.clientY); +console.log('渲染坐标 x/y 分别为:', point.x, point.y); +``` + +### graph.getClientByPoint(x, y) + +将渲染坐标转换为屏幕/页面坐标。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ------------ | +| x | Number | true | 渲染坐标系下的 x 坐标 | +| y | Number | true | 渲染坐标系下的 y 坐标 | + +**返回值** + +- 返回值类型:Object; +- 包含的属性:`x` 和 `y` 属性,分别表示屏幕/页面的 x、y 坐标。 + +**用法** + +```javascript +const point = graph.getClientByPoint(100, 200); +console.log('屏幕/页面x/y坐标分别为:', point.x, point.y); +``` + +### graph.getPointByCanvas(canvasX, canvasY) + +将 Canvas 画布坐标转换为渲染坐标。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------- | ------ | -------- | ----------- | +| canvasX | Number | true | 画布 x 坐标 | +| canvasY | Number | true | 画布 y 坐标 | + +**返回值** + +- 返回值类型:Object; +- 包含的属性:x 和 y 属性,分别表示渲染的 x、y 坐标。 + +**用法** + +```javascript +const point = graph.getPointByCanvas(100, 200); +console.log('渲染坐标 x/y 分别为:', point.x, point.y); +``` + +### graph.getCanvasByPoint(x, y) + +将渲染坐标转换为 Canvas 画布坐标。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ------------ | +| x | Number | true | 渲染坐标系下的 x 坐标 | +| y | Number | true | 渲染坐标系下的 y 坐标 | + +**返回值** + +- 返回值类型:Object; +- 包含的属性:x 和 y 属性,分别表示 Canvas 画布的 x、y 坐标。 + +**用法** + +```javascript +const point = graph.getCanvasByPoint(100, 200); +console.log('Canvas 画布的 x/y 坐标分别为:', point.x, point.y); +``` + +### graph.getGraphCenterPoint() + +获取图内容的中心绘制坐标。v4.2.1 支持。 + +**参数** + +无 + +**返回值** + +- 返回值类型:Object; +- 包含的属性:x 和 y 属性,分别表示渲染坐标下的 x、y 值。 + +**用法** + +```javascript +const point = graph.getGraphCenterPoint(); +console.log('图内容中心的绘制坐标是', point.x, point.y); +``` + +### graph.getViewPortCenterPoint() + +获取视口中心绘制坐标。v4.2.1 支持。 + +**参数** + +无 + +**返回值** + +- 返回值类型:Object; +- 包含的属性:x 和 y 属性,分别表示渲染坐标下的 x、y 值。 + +**用法** + +```javascript +const point = graph.getViewPortCenterPoint(); +console.log('视口中心的绘制坐标是', point.x, point.y); +``` diff --git a/packages/site/docs/api/graphFunc/data.en.md b/packages/site/docs/api/graphFunc/data.en.md new file mode 100644 index 0000000000..436dbca8f1 --- /dev/null +++ b/packages/site/docs/api/graphFunc/data.en.md @@ -0,0 +1,164 @@ +--- +title: Read/Save/Load Data +order: 0 +--- + +Load the data for graph. + +### graph.data(_data_) + + _Object_ **required** + +Graph data, it should be an object containing an array of nodes and an array of edges. | + +**Usage** + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + }, + { + id: 'node2', + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +// graph is an instance of Graph +graph.data(data); +``` + +### graph.save() + +Get the graph data. + +**Return** + +- Type of the return value: Object; +- The return value has all the nodes and edges as below: + +```javascript +{ + nodes: [], + edges: [], + groups: [], +} +``` + +**Usage** + +```javascript +graph.save(); +``` + +### graph.read(data) + +Read the data and render the graph. It is equal to combining graph.data(data) and graph.render(). + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| data | Object | true | Graph data, it should be an object containing an array of nodes and an array of edges. | + +**Usage** + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + }, + { + id: 'node2', + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +// graph is an instance of Graph +graph.read(data); +``` + +### graph.changeData(data, stack) + +Change the data source, and render the graph according to the new data. If there is `layout` configured on the graph, the new data will be placed according to the layout algorithm. If you do not want to layout the new data with origin layout algorithm, call `graph.destroyLayout`, ref to [destroyLayout](#destroyLayout). + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| data | Object | false | Graph data, it should be an object containing an array of nodes and an array of edges. If it is not assigned, the graph will be re-rendered with the current data on the graph | +| stack | boolean | false | Whether to push the operator into the undo & redo stack. If the `enableStack` is `true`, this operation will be automatically pushed into the stack by default. Set `stack` to be `false` if you do not want it. | + +**Usage** + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + }, + { + id: 'node2', + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +// graph is an instance of Graph +graph.changeData(data); + +// If there is no parameter, the graph will be re-rendered with the current data on the graph +graph.changeData(); +``` + +### destroyLayout() + +Destroy the layout algorithm. After that, the `changeData` will not place the new nodes with origin layout configurations. + +**Usage** + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'circular', + center: [500, 300], + }, + animate: true, +}); +graph.data(data); +graph.render(); +graph.destroyLayout(); +// If there is no position info in data2, the new nodes will be placed according to position initing problem. If the position info exists, the new node will be placed according to its position info +graph.changeData(data2); +``` diff --git a/packages/site/docs/api/graphFunc/data.zh.md b/packages/site/docs/api/graphFunc/data.zh.md new file mode 100644 index 0000000000..9ce40f2aad --- /dev/null +++ b/packages/site/docs/api/graphFunc/data.zh.md @@ -0,0 +1,138 @@ +--- +title: 数据 +order: 0 +--- + +设置图初始化数据。 + +### graph.data(_data_) + + _Object_ **required** + +初始化的图数据,是一个包括 nodes 数组和 edges 数组的对象。 + +**用法** + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + }, + { + id: 'node2', + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +// graph 是 Graph 的实例 +graph.data(data); +``` + +### graph.save() + +获取图数据。 + +该方法无参数。 + +**返回值** + +- 返回值类型:Object; +- 返回值包括所有节点和边,数据结构如下下所示: + +```javascript +{ + nodes: [], + edges: [], + groups: [], +} +``` + +**用法** + +```javascript +graph.save(); +``` + +### graph.read(data) + +接收数据,并进行渲染,read 方法的功能相当于 data 和 render 方法的结合。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ------------------------------------------------ | +| data | Object | true | 初始化的图数据,是一个包括 nodes 和 edges 的对象 | + +**用法** + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + }, + { + id: 'node2', + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +// graph是Graph的实例 +graph.read(data); +``` + +### graph.changeData(data, stack) + +更新数据源,根据新的数据重新渲染视图。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| data | Object | false | 图数据,是一个包括 nodes 和 edges 的对象。若不指定该参数,则使用当前数据重新渲染 | +| stack | boolean | false | 操作是否入 undo & redo 栈,当实例化 Graph 时设置 enableStack 为 true 时,默认情况下会自动入栈,入栈以后,就支持 undo & redo 操作,如果不需要,则设置该参数为 false 即可 | + +**用法** + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + }, + { + id: 'node2', + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +// graph是Graph的实例 +graph.changeData(data); +// 若不指定该参数,则使用当前图上的数据重新渲染 +graph.changeData(); +``` diff --git a/packages/site/docs/api/graphFunc/destroy.en.md b/packages/site/docs/api/graphFunc/destroy.en.md new file mode 100644 index 0000000000..5770144146 --- /dev/null +++ b/packages/site/docs/api/graphFunc/destroy.en.md @@ -0,0 +1,24 @@ +--- +title: Clear/Destroy Canvas +order: 18 +--- + +### graph.clear() + +Clear all the items in the canvas. This function is used for reseting the data source and re-rendering the graph. + +**Usage** + +```javascript +graph.clear(); +``` + +### graph.destroy() + +Destroy the graph. + +**Usage** + +```javascript +graph.destroy(); +``` diff --git a/packages/site/docs/api/graphFunc/destroy.zh.md b/packages/site/docs/api/graphFunc/destroy.zh.md new file mode 100644 index 0000000000..2934faa705 --- /dev/null +++ b/packages/site/docs/api/graphFunc/destroy.zh.md @@ -0,0 +1,28 @@ +--- +title: 清除/销毁画布 +order: 18 +--- + +### graph.clear() + +清除画布元素。该方法一般用于清空数据源,重新设置数据源,重新 render 的场景,此时所有的图形都会被清除。 + +该方法无参数。 + +**用法** + +```javascript +graph.clear(); +``` + +### graph.destroy() + +销毁画布。 + +该方法无参数。 + +**用法** + +```javascript +graph.destroy(); +``` diff --git a/packages/site/docs/api/graphFunc/download.en.md b/packages/site/docs/api/graphFunc/download.en.md new file mode 100644 index 0000000000..a1753b3e7c --- /dev/null +++ b/packages/site/docs/api/graphFunc/download.en.md @@ -0,0 +1,112 @@ +--- +title: Export Image +order: 17 +--- + +### graph.downloadFullImage(name, type, imageConfig) + +Export the whole graph as an image, whatever (a part of) the graph is out of the screen. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| name | String | false | The name of the image. 'graph' by default. | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | The type of the image. When the `renderer` of the graph is `'canvas'`(default), `type` takes effect. When the `renderer` is `'svg'`, `toFullDataURL` will export a svg file | +| imageConfig | Object | false | The configuration for the exported image, detials are shown below | + +where the `imageConfig` is the configuration for exported image: + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| backgroundColor | String | false | The background color of the image. If it is not assigned, the background will be transparent. | +| padding | Number / Number[] | false | The top, right, bottom, right paddings of the exported image. When its type is number, the paddings around the graph are the same | + +**Usage** + +```javascript +graph.downloadFullImage('tree-graph', 'image/png', { + backgroundColor: '#ddd', + padding: [30, 15, 15, 15], +}); +``` + +### graph.downloadImage(name, type, backgroundColor) + +Export the canvas as an image. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| name | String | false | The name of the image. 'graph' by default | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | The type of the image. When the `renderer` of the graph is `'canvas'`(default), `type` takes effect. When the `renderer` is `'svg'`, `toFullDataURL` will export a svg file | +| backgroundColor | String | false | The background color of the image. If it is not assigned, the background will be transparent. | + +**Usage** + +```javascript +graph.downloadImage(); +``` + +### graph.toDataURL(type, backgroundColor) + +Generate url of the image of the graph inside the view port. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | The type of the image. When the `renderer` of the graph is `'canvas'`(default), `type` takes effect. When the `renderer` is `'svg'`, `toFullDataURL` will export a svg file | +| backgroundColor | String | false | The background color of the image. If it is not assigned, the background will be transparent. | + +**Return** + +- Type of the return value: string; +- The return value is the image url. + +**Usage** + +```javascript +const dataURL = graph.toDataURL(); +``` + +### graph.toFullDataURL(callback, type, backgroundColor) + +Generate url of the image of the whole graph including the part out of the view port. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| callback | Function | true | The callback function after finish generating the dataUrl of the full graph | +| Asynchronously | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | The type of the image. When the `renderer` of the graph is `'canvas'`(default), `type` takes effect. When the `renderer` is `'svg'`, `toFullDataURL` will export a svg file | +| imageConfig | Object | false | The configuration for the exported image, detials are shown below | + +where the `imageConfig` is the configuration for exported image: + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| backgroundColor | String | false | The background color of the image. If it is not assigned, the background will be transparent. | +| padding | Number / Number[] | false | The top, right, bottom, right paddings of the exported image. When its type is number, the paddings around the graph are the same | + +No return value, you can process the result in the callback function as shown below: + +**Usage** + +```javascript +graph.toFullDataUrl( + // The first parameter: callback, required + (res) => { + // ... something + console.log(res); // e.g. print the result + }, + // The second and third parameter is not required + 'image/jpeg', + (imageConfig: { + backgroundColor: '#fff', + padding: 10, + }), +); +``` diff --git a/packages/site/docs/api/graphFunc/download.zh.md b/packages/site/docs/api/graphFunc/download.zh.md new file mode 100644 index 0000000000..a395a2d6ec --- /dev/null +++ b/packages/site/docs/api/graphFunc/download.zh.md @@ -0,0 +1,111 @@ +--- +title: 导出图片 +order: 17 +--- + +### graph.downloadFullImage(name, type, imageConfig) + +将画布上的元素导出为图片。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| name | String | false | 图片的名称,不指定则为 'graph' | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | 图片的类型。图的 `renderer` 为默认的 `'canvas'` 时生效,图的 `renderer` 为 `'svg'` 时将导出 svg 文件 | +| imageConfig | Object | false | 图片的配置项,可选,具体字段见下方 | + +其中,imageConfig 为导出图片的配置参数: + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| backgroundColor | String | false | 图片的背景色,可选,不传值时将导出透明背景的图片 | +| padding | Number / Number[] | false | 导出图片的上左下右 padding 值。当 `padding` 为 number 类型时,四周 `padding` 相等 | + +**用法** + +```javascript +graph.downloadFullImage('tree-graph', 'image/png', { + backgroundColor: '#ddd', + padding: [30, 15, 15, 15], +}); +``` + +### graph.downloadImage(name, type, backgroundColor) + +将画布上的元素导出为图片。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| name | String | false | 图片的名称,不指定则为 'graph' | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | 图片的类型。图的 `renderer` 为默认的 `'canvas'` 时生效,图的 `renderer` 为 `'svg'` 时将导出 svg 文件 | +| backgroundColor | String | false | 图片的背景色,可选,不传值时将导出透明背景的图片 | + +**用法** + +```javascript +graph.downloadImage(); +``` + +### graph.toFullDataURL(callback, type, imageConfig) + +将画布上元素生成为图片的 URL。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| callback | Function | true | 异步生成 dataUrl 完成后的回调函数,在这里处理生成的 dataUrl 字符串 | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | 图片的类型。图的 `renderer` 为默认的 `'canvas'` 时生效,图的 `renderer` 为 `'svg'` 时将导出 svg 文件 | +| imageConfig | Object | false | 图片的配置项,可选,具体字段见下方 | + +其中,imageConfig 为导出图片的配置参数: + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| backgroundColor | String | false | 图片的背景色,可选,不传值时将导出透明背景的图片 | +| padding | Number / Number[] | false | 导出图片的上左下右 padding 值。当 `padding` 为 number 类型时,四周 `padding` 相等 | + +无返回值,生成的结果请在 callback 中处理。如下示例: + +**用法** + +```javascript +graph.toFullDataUrl( + // 第一个参数为 callback,必须 + (res) => { + // ... something + console.log(res); // 打印出结果 + }, + // 后两个参数不是必须 + 'image/jpeg', + (imageConfig: { + backgroundColor: '#fff', + padding: 10, + }), +); +``` + +### graph.toDataURL(type, backgroundColor) + +将画布上元素生成为图片的 URL。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | 图片的类型。图的 `renderer` 为默认的 `'canvas'` 时生效,图的 `renderer` 为 `'svg'` 时将导出 svg 文件 | +| backgroundColor | String | false | 图片的背景色,可选,不传值时将导出透明背景的图片 | + +**返回值** + +- 返回值类型:String; +- 返回值表示生成的图片的 URL。 + +**用法** + +```javascript +const dataURL = graph.toDataURL(); +``` diff --git a/packages/site/docs/api/graphFunc/find.en.md b/packages/site/docs/api/graphFunc/find.en.md new file mode 100644 index 0000000000..19174bc1a5 --- /dev/null +++ b/packages/site/docs/api/graphFunc/find.en.md @@ -0,0 +1,197 @@ +--- +title: Find Element +order: 5 +--- + +### graph.getNodes() + +Get all the node items in the graph. + +⚠️Attention: it returns the items but not data models. + +**Return** + +- Type of the return value: Array; +- Return the node items in the graph. + +**Usage** + +```javascript +const nodes = graph.getNodes(); +``` + +### graph.getEdges() + +Get all the edge items in the graph. + +⚠️Attention: it returns the items but not data models. + +**Return** + +- Type of the return value: Array; +- Return the edge items in the graph. + +**Usage** + +```javascript +const edges = graph.getEdges(); +``` + +### graph.getCombos() + +Get all the combo items in the graph. + +**Return** + +- Type of the return value: Array; +- Return the combo items in the graph. + +**Usage** + +```javascript +const combos = graph.getCombos(); +``` + +### graph.getComboChildren(combo) + +Get the children of the `combo`. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | --------------- | -------- | ---------------------------------------- | +| combo | string / ICombo | true | The ID or of the combo or the combo item | + +**Return** + +- Type of the return value: Object. Formated as: + +```javascript +{ + nodes: INode[], + edges: ICombo[] +} +``` + +- Return the children (sub nodes and combos) of the `combo`. + +**Usage** + +```javascript +const elements: { + nodes: INode[], + combos: ICombo[] +} = graph.getComboChildren('combo1') +``` + +### graph.getNeighbors(node, type) + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| node | string / INode | true | node ID or the node instance | +| type | 'source' / 'target' / undefined | false | The type of the neighbors, 'source': only return the source nodes; 'target': only return the target nodes, undefined: return all of the neighbors | + +**Return** + +- Type of the return value: Array; +- Return a list of node items. + +**Usage** + +```javascript +const neighbors = graph.getNeighbors('node1', 'source'); +``` + +### graph.find(type, fn) + +Find single item according to a rule. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | -------- | -------- | ---------------------------------------------- | +| type | string | true | Type of the item. Options: `'node'`, `'edge'`. | +| fn | Function | true | Rule for searching. | + +**Return** + +- Type of the return value: Object; +- If there are items that match the rule, return the first one. Return `undefined` otherwise. + +**Usage** + +```javascript +const findNode = graph.find('node', (node) => { + return node.get('model').x === 100; +}); +``` + +### graph.findById(id) + +Find an item by id. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ----------- | +| id | string | true | 元素 ID | + +**Return** + +- Type of the return value: Object; +- If there are items that match the rule, return the first one. Return `undefined` otherwise. + +**Usage** + +```javascript +const node = graph.findById('node'); +``` + +### graph.findAll(type, fn) + +Find all the items that match the rule. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | -------- | -------- | -------------------------------------------------- | +| type | string | true | The type of the item. Options: `'node'`, `'edge'`. | +| fn | Function | true | Rule for searching. | + +**Return** + +- Type of the return value: Array; +- If there are items that match the rule, return all of them. Return `undefined` otherwise. + +**Usage** + +```javascript +const nodes = graph.findAll('node', (node) => { + return node.get('model').x; +}); +``` + +### graph.findAllByState(type, state) + +Find all the items whose value of state is true. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | -------------------------------------------------- | +| type | string | true | The type of the item. Options: `'node'`, `'edge'`. | +| state | string | true | State for searching. | + +**Return** + +- Type of the return value: Array; +- Return all the items that match the state. + +**Usage** + +```javascript +// Find all the items whose 'selected' state is true +const nodes = graph.findAllByState('node', 'selected'); +``` diff --git a/packages/site/docs/api/graphFunc/find.zh.md b/packages/site/docs/api/graphFunc/find.zh.md new file mode 100644 index 0000000000..420f9a805c --- /dev/null +++ b/packages/site/docs/api/graphFunc/find.zh.md @@ -0,0 +1,197 @@ +--- +title: 查找元素 +order: 5 +--- + +### graph.getNodes() + +获取图中所有节点的实例。 + +⚠️ 注意: 这里返回的是节点的实例,而不是节点的数据项。 + +**返回值** + +- 返回值类型:Array; +- 返回值表示图中所有节点的实例。 + +**用法** + +```javascript +const nodes = graph.getNodes(); +``` + +### graph.getEdges() + +获取图中所有边的实例。 + +⚠️ 注意: 这里返回的是边的实例,而不是边的数据项。 + +**返回值** + +- 返回值类型:Array; +- 返回值表示图中所有边的实例。 + +**用法** + +```javascript +const edges = graph.getEdges(); +``` + +### graph.getCombos() + +获取当前图中所有 combo 的实例。 + +**返回值** + +- 返回值类型:Array; +- 返回值表示图中所有 combo 的实例。 + +**用法** + +```javascript +const combos = graph.getCombos(); +``` + +### graph.getComboChildren(combo) + +获取指定 combo 中所有的子节点及子 combo。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | --------------- | -------- | ---------------------- | +| combo | string / ICombo | true | Combo ID 或 combo 实例 | + +**返回值** + +- 返回值类型:Object,格式如下 + +```javascript +{ + nodes: INode[], + edges: ICombo[] +} +``` + +- 返回指定 combo 中的子元素(子节点及子 combo)。 + +**用法** + +```javascript +const elements: { + nodes: INode[], + combos: ICombo[] +} = graph.getComboChildren('combo1') +``` + +### graph.getNeighbors(node, type) + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| node | string / INode | true | 节点 ID 或节点实例 | +| type | 'source' / 'target' / undefined | false | 邻居类型, 'source' 只获取当前节点的源节点,'target' 只获取当前节点指向的目标节点, 若不指定则返回所有类型的邻居 | + +**返回值** + +- 返回值类型:Array; +- 返回值符合要求的节点数组。 + +**用法** + +```javascript +const neighbors = graph.getNeighbors('node1', 'source'); +``` + +### graph.find(type, fn) + +根据具体规则查找单个元素。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | -------- | -------- | ------------------------------------- | +| type | string | true | 元素类型,可选值为 `'node'`、`'edge'` | +| fn | Function | true | 查找的规则 | + +**返回值** + +- 返回值类型:Object; +- 如果有符合规则的元素实例,则返回第一个匹配的元素实例,否则返回 `undefined` 。 + +**用法** + +```javascript +const findNode = graph.find('node', (node) => { + return node.get('model').x === 100; +}); +``` + +### graph.findById(id) + +根据 ID,查询对应的元素实例。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ------- | +| id | string | true | 元素 ID | + +**返回值** + +- 返回值类型:Object; +- 如果有符合规则的元素实例,则返回该元素实例,否则返回 `undefined`。 + +**用法** + +```javascript +const node = graph.findById('node'); +``` + +### graph.findAll(type, fn) + +查询所有满足规则的元素。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | -------- | -------- | ------------------------------------- | +| type | string | true | 元素类型,可选值为 `'node'`、`'edge'` | +| fn | Function | true | 查找的规则 | + +**返回值** + +- 返回值类型:Array; +- 如果有符合规则的元素实例,则返回所有元素实例,否则返回 `undefined`。 + +**用法** + +```javascript +const nodes = graph.findAll('node', (node) => { + return node.get('model').x; +}); +``` + +### graph.findAllByState(type, state) + +查找所有处于指定状态的元素。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | ------------------------------------- | +| type | string | true | 元素类型,可选值为 `'node'`、`'edge'` | +| state | string | true | 状态名称 | + +**返回值** + +- 返回值类型:Array; +- 返回所有指定状态的元素实例。 + +**用法** + +```javascript +// 查询所有选中的元素 +const nodes = graph.findAllByState('node', 'selected'); +``` diff --git a/packages/site/docs/api/graphFunc/get_set.en.md b/packages/site/docs/api/graphFunc/get_set.en.md new file mode 100644 index 0000000000..a5cbcddf9f --- /dev/null +++ b/packages/site/docs/api/graphFunc/get_set.en.md @@ -0,0 +1,169 @@ +--- +title: Get/Set +order: 2 +--- + +### graph.get(key) + +Get an property of graph by key. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | -------------------- | +| key | string | true | Key of the property. | + +**Usage** + +```javascript +// get the group +const group = graph.get('group'); + +// get the canvas instance +const canvas = graph.get('canvas'); + +// get the value of autoPaint +const autoPaint = graph.get('autoPaint'); +``` + +### graph.set(key, val) + +Set the value to an property. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ----------------------- | -------- | -------------------------- | +| key | string | true | The key of the property. | +| val | string / Object / Array | true | The value of the property. | + +**Usage** + +```javascript +// Set capture to false +graph.set('capture', false); + +// Set customGroup to group +graph.set('customGroup', group); + +// Set nodeIdList to [1, 3, 5] +graph.set('nodeIdList', [1, 3, 5]); +``` + +### graph.getContainer() + +Get the DOM container of the graph. + +**Parameter** + +No parameter. + +**Usage** + +```javascript +graph.getContainer(); +``` + +### graph.getGroup() + +Get the root [graphics group](/en/docs/manual/middle/elements/shape/graphics-group) of the graph. + +**Parameter** + +No parameter. + +**Usage** + +```javascript +graph.getGroup(); +``` + +### graph.getMinZoom() + +Get the `minZoom` for the graph, which is the lower limit of the zoom ratio. + +**Parameter** + +No parameter + +**Usage** + +```javascript +graph.getMinZoom(); +``` + +### graph.setMinZoom(ratio) + +Set the `minZoom` for the graph, which is the lower limit of the zoom ratio. + +**Parameter** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | ---------------------------- | +| ratio | number | true | The minimum zoom ratio value | + +**Usage** + +```javascript +graph.setMinZoom(0.001); +``` + +### graph.getMaxZoom() + +Get the `maxZoom` for the graph, which is the upper limit of the zoom ratio. + +**Parameter** + +No parameter. + +**Usage** + +```javascript +graph.getMaxZoom(); +``` + +### graph.setMaxZoom(ratio) + +Set the `maxZoom` for the graph, which is the upper limit of the zoom ratio. + +**Parameter** + +| Name | Type | Required | Description | +| ----- | ------ | -------- | ---------------------------- | +| ratio | number | true | The maximum zoom ratio value | + +**Usage** + +```javascript +graph.setMaxZoom(1000); +``` + +### graph.getWidth() + +Get the current width of the graph. + +**Parameter** + +No parameter. + +**Usage** + +```javascript +graph.getWidth(); +``` + +### graph.getHeight() + +Get the current height of the graph. + +**Parameter** + +No parameter. + +**Usage** + +```javascript +graph.getHeight(); +``` + +
diff --git a/packages/site/docs/api/graphFunc/get_set.zh.md b/packages/site/docs/api/graphFunc/get_set.zh.md new file mode 100644 index 0000000000..01f2311d3f --- /dev/null +++ b/packages/site/docs/api/graphFunc/get_set.zh.md @@ -0,0 +1,169 @@ +--- +title: 获取/设置 +order: 2 +--- + +### graph.get(key) + +根据 key 获取属性值。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | -------- | +| key | string | true | 属性的键 | + +**用法** + +```javascript +// 获取 group +const group = graph.get('group'); + +// 获取 canvas 实例 +const canvas = graph.get('canvas'); + +// 获取 autoPaint 值 +const autoPaint = graph.get('autoPaint'); +``` + +### graph.set(key, val) + +设置属性值。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ----------------------- | -------- | -------- | +| key | string | true | 属性的键 | +| val | string / Object / Array | true | 属性的值 | + +**用法** + +```javascript +// 设置 capture 值为 false +graph.set('capture', false); + +// 设置 customGroup 值为 group +graph.set('customGroup', group); + +// 设置 nodeIdList 值为数组 +graph.set('nodeIdList', [1, 3, 5]); +``` + +### graph.getContainer() + +获取 Graph 的 DOM 容器。 + +**参数** + +无参数 + +**用法** + +```javascript +graph.getContainer(); +``` + +### graph.getGroup() + +获取 Graph 根[图形分组](/zh/docs/manual/middle/elements/shape/graphics-group)。 + +**参数** + +无参数 + +**用法** + +```javascript +graph.getGroup(); +``` + +### graph.getMinZoom() + +获取 graph 当前允许的最小缩放比例。 + +**参数** + +无参数 + +**用法** + +```javascript +graph.getMinZoom(); +``` + +### graph.setMinZoom(ratio) + +设置 graph 当前允许的最小缩放比例。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | -------------- | +| ratio | number | true | 最小缩放比例值 | + +**用法** + +```javascript +graph.setMinZoom(0.001); +``` + +### graph.getMaxZoom() + +获取 graph 当前允许的最大缩放比例。 + +**参数** + +无参数 + +**用法** + +```javascript +graph.getMaxZoom(); +``` + +### graph.setMaxZoom(ratio) + +设置 graph 当前允许的最大缩放比例。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------ | -------- | -------------- | +| ratio | number | true | 最大缩放比例值 | + +**用法** + +```javascript +graph.setMaxZoom(1000); +``` + +### graph.getWidth() + +获取 graph 当前的宽度。 + +**参数** + +无参数 + +**用法** + +```javascript +graph.getWidth(); +``` + +### graph.getHeight() + +获取 graph 当前的高度。 + +**参数** + +无参数 + +**用法** + +```javascript +graph.getHeight(); +``` + + diff --git a/packages/site/docs/api/graphFunc/hull.en.md b/packages/site/docs/api/graphFunc/hull.en.md new file mode 100644 index 0000000000..1ce53cf0da --- /dev/null +++ b/packages/site/docs/api/graphFunc/hull.en.md @@ -0,0 +1,112 @@ +--- +title: Hull for Clusters +order: 11 +--- + +## createHull(cfg: HullCfg) + +**Parameter** + +| Name | Type | Required | Description | +| ---- | ------- | -------- | --------------------------- | +| cfg | HullCfg | true | Configuration for the hull. | + +Details for HullCfg: + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| id | string | true | Hull id | +| type | `round-convex` / `smooth-convex` / `bubble` | false | Configuration for hull type: `round-convex` generates a rounded-convex contour, `smooth-convex` generates a smooth-convex contour / `bubble` generates a smooth concave contour that could avoids nonMembers ([algorithm](http://vialab.science.uoit.ca/portfolio/bubblesets). The default value is `round-convex`. | +| members | Item[] / string[] | true | Node Instances or Node Ids that should be included in the hull. | +| nonMembers | Item[] / string[] | false | Node Instances or Node Ids that should be excluded from the hull, it only works when the hull type is set to be `bubble`. | +| padding | number | false | The offset between the hull polygon and the inner members. | +| style | object | false | The style properties for hull, including `fill` ( the fill color), `stroke` ( the stroke color) and `opacity` (transparency of the hull shape). | + +**Usage** + +```javascript +let centerNodes = graph.getNodes().filter((node) => !node.getModel().isLeaf); +graph.on('afterlayout', () => { + const hull1 = graph.createHull({ + id: 'centerNode-hull', + type: 'bubble', + members: centerNodes, + padding: 10, + }); + + const hull2 = graph.createHull({ + id: 'leafNode-hull1', + members: ['node6', 'node7'], + padding: 10, + style: { + fill: 'lightgreen', + stroke: 'green', + }, + }); + + const hull3 = graph.createHull({ + id: 'leafNode-hull2', + members: ['node8', 'node9', 'node10', 'node11', 'node12'], + padding: 10, + style: { + fill: 'lightgreen', + stroke: 'green', + }, + }); + + graph.on('afterupdateitem', (e) => { + hull1.updateData(hull1.members); + hull2.updateData(hull2.members); + hull3.updateData(hull3.members); + }); +}); +``` + +## getHulls() + +Get all of the hulls in the current graph. + +**Return** + +- Type of return value: {[key: string]: Hull; +- The return object indicates the mapping of hull ID to the corresponding hull instance. Each key of the object is a string representing the ID of hull, and the value of the object is the corresponding hull instance. + +**Usage** + +```javascript +const hullMap = graph.getHulls(); +``` + +## removeHull(id: string) + +Remove a hull with id. + +**Parameter** + +| Name | Type | Required | Description | +| ---- | ------------- | -------- | -------------------------------------- | +| hull | string / Hull | true | hull id or hull instance to be removed | + +**Usage** + +```javascript +graph.removeHull('myHull'); +``` + +## removeHulls() + +Remove all the hulls on the graph. + +**Parameter** + +No parameters. + +**Usage** + +```javascript +graph.removeHulls(); +``` + +## Comparison for Combo and Hull + +combo-hull \ No newline at end of file diff --git a/packages/site/docs/api/graphFunc/hull.zh.md b/packages/site/docs/api/graphFunc/hull.zh.md new file mode 100644 index 0000000000..d7122601d3 --- /dev/null +++ b/packages/site/docs/api/graphFunc/hull.zh.md @@ -0,0 +1,112 @@ +--- +title: 聚类轮廓包裹 +order: 11 +--- + +## createHull(cfg: HullCfg) + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------- | -------- | ---------- | +| cfg | HullCfg | true | 轮廓配置项 | + +轮廓包裹的形状支持 `round-convex` / `smooth-convex` / `bubble` 三种类型,默认为 `round-convex` 类型。`round-convex` 为圆角凸包轮廓,smooth-convex 为平滑曲线凸包轮廓,这两种凸包轮廓不可绕开配置项中的 nonMembers;bubble 为自由凹包轮廓,可以绕开 nonMembers。配置项( HullCfg)支持的配置参数详情如下: + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| id | string | true | 包裹的 id | +| type | `round-convex` / `smooth-convex` / `bubble` | false | 包裹的类型:`round-convex` 生成圆角凸包轮廓,`smooth-convex` 生成平滑凸包轮廓 / `bubble` 产生一种可以避开 nonMembers 的平滑凹包轮廓([算法](http://vialab.science.uoit.ca/portfolio/bubblesets))。 默认值是 `round-convex`。 | +| members | Item[] / string[] | true | 在包裹内部的节点实例或节点 Id 数组 | +| nonMembers | Item[] / string[] | false | 不在轮廓内部的节点数组,只在 `bubble` 类型的包裹中生效 | +| padding | number | false | 轮廓边缘和内部成员的间距 | +| style | object | false | 轮廓的样式属性,属性包括 fill (填充颜色), stroke (描边颜色), opacity (透明度) | + +**用法** + +```javascript +let centerNodes = graph.getNodes().filter((node) => !node.getModel().isLeaf); +graph.on('afterlayout', () => { + const hull1 = graph.createHull({ + id: 'centerNode-hull', + type: 'bubble', + members: centerNodes, + padding: 10, + }); + + const hull2 = graph.createHull({ + id: 'leafNode-hull1', + members: ['node6', 'node7'], + padding: 10, + style: { + fill: 'lightgreen', + stroke: 'green', + }, + }); + + const hull3 = graph.createHull({ + id: 'leafNode-hull2', + members: ['node8', 'node9', 'node10', 'node11', 'node12'], + padding: 10, + style: { + fill: 'lightgreen', + stroke: 'green', + }, + }); + + graph.on('afterupdateitem', (e) => { + hull1.updateData(hull1.members); + hull2.updateData(hull2.members); + hull3.updateData(hull3.members); + }); +}); +``` + +## getHulls() + +获取图上所有的包裹轮廓。 + +**返回值** + +- 返回值类型:Object;对象中的 key 是字符串类型,代表 hull 的 ID ,对象中的 value 是对应的 hull 实例。 +- 返回值表示当前 graph 中所有轮廓 ID 到 轮廓实例的映射。 + +**用法** + +```javascript +const hullMap = graph.getHulls(); +``` + +## removeHull(hull: string | Hull) + +移除指定 id 或指定实例的轮廓。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------------- | -------- | ------------------ | +| hull | string / Hull | true | 轮廓 id 或轮廓实例 | + +**用法** + +```javascript +graph.removeHull('myHull'); +``` + +## removeHulls() + +移除当前图上所有轮廓实例。 + +**参数** + +无参数。 + +**用法** + +```javascript +graph.removeHulls(); +``` + +## 比较 Combo 与 Hull + +combo-hull diff --git a/packages/site/docs/api/graphFunc/item.en.md b/packages/site/docs/api/graphFunc/item.en.md new file mode 100644 index 0000000000..c619c29a18 --- /dev/null +++ b/packages/site/docs/api/graphFunc/item.en.md @@ -0,0 +1,315 @@ +--- +title: Item Operation +order: 4 +--- + +## Add/Remove + +### graph.addItem(type, model, stack) + +Add item(node, edge) to the graph. + +⚠️ Attention: G6 will use the `model` object as the model of the newly added item, and the `model` might be modified. If you do not want it to be modified, use the deep cloned `model` instead. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| type | string | true | The type of the item. Options: `'node'`, `'edge'`. | +| model | Object | true | The data model of the item, refer to [Item Model Properties](/en/docs/api/Items/itemProperties). | +| stack | boolean | false | Whether to push the operator into the undo & redo stack. If the `enableStack` is `true`, this operation will be automatically pushed into the stack by default. Set `stack` to be `false` if you do not want it. | + +**Usage** + +```javascript +const model = { + id: 'node', + label: 'node', + address: 'cq', + x: 200, + y: 150, + style: { + fill: 'blue', + }, +}; + +graph.addItem('node', model); +``` + +### graph.removeItem(item, stack) + +Remove the item. When the item is the id of a group, this operation will delete the corresponding group. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| item | string / Object | true | The id or the instance of the item. | +| stack | boolean | false | Whether to push the operator into the undo & redo stack. If the `enableStack` is `true`, this operation will be automatically pushed into the stack by default. Set `stack` to be `false` if you do not want it. | + +**Usage** + +```javascript +// Find the item instance by id +const item = graph.findById('node'); +graph.removeItem(item); +``` + +## Update + +### graph.updateItem(item, model, stack) + +Update the item with new data model. If there are combos in the graph, after calling updateItem to update the position of a node, call [updateCombo(combo)](/en/docs/api/Graph#updatecombocombo) to update the sizes and positions of the related combos. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| item | string / Object | true | The ID or the instance of the item | +| model | Object | true | New data model, refer to [Item Model Properties](/en/docs/api/Items/itemProperties) | +| stack | boolean | false | Whether to push the operator into the undo & redo stack. If the `enableStack` is `true`, this operation will be automatically pushed into the stack by default. Set `stack` to be `false` if you do not want it. | + +**Usage** + +```javascript +const model = { + id: 'node', + label: 'node', + address: 'cq', + x: 200, + y: 150, + style: { + fill: 'blue', + }, +}; + +// Find the item instance by id +const item = graph.findById('node'); +graph.updateItem(item, model); +``` + +### graph.update(item, model, stack) + +The same as updateItem(item, model). + +### graph.updateCombos() + +Update the sizes and positions of all the combos according to the bboxes of its children. + +**Usage** + +```javascript +// Update all the combos +graph.updateCombos(); +``` + +### graph.updateCombo(combo) + +Update the positions and sizes of the combo and all of its ancestors. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | --------------- | -------- | ----------------------------------- | +| combo | string / ICombo | true | The ID or the instance of the combo | + +**Usage** + +```javascript +// Update a node's position +const node1 = graph.findById('node1'); +graph.updateItem(node1, { + x: 100, + y: 100, +}); + +// the combo who contains the node +const comboId = node1.getModel().comboId; + +// Update the combo and all its ancestors who contains node1 +graph.updateCombo(comboId); +``` + +### graph.updateComboTree(item, parentId) + +Update the hierarchy structure of the combo, such as move a combo into another one. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| item | string / INode / ICombo | The ID or the item of the node/combo to be updated | +| parentId | string | undefined | The ID of the new parent combo, undefined means updating the item with no parent | + +**Usage** + +```javascript +// move combo1 out of its parent combo. combo1 will be in the same hierarchy level as its old parent. +graph.updateComboTree('combo1') + +// move combo1 into combo2. combo1 will be the child of combo2. +graph.updateComboTree('combo1', 'combo2') +``` + +### graph.refreshItem(item) + +Refresh the item. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | --------------- | -------- | ----------------------------------- | +| item | string / Object | true | The id or the instance of the item. | + +**Usage** + +```javascript +// Find the item instance by id +const item = graph.findById('node'); +graph.refreshItem(item); +``` + +### graph.refreshPositions() + +When the positions of nodes in their data models are changed, refresh the canvas to paint the nodes with new positions. It will update the edges in the same time. + +**Usage** + +```javascript +graph.refreshPositions(); +``` + +## Configure + +### graph.node(nodeFn) + +Set the style and other configurations for each node. + +⚠️Attention: this funcion must **be called before graph.render()**. It does not take effect otherwise. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | -------- | -------- | ---------------------------------------- | +| nodeFn | Function | true | Return the configurations for each node. | + +**Usage** + +```javascript +graph.node((node) => { + return { + id: node.id, + type: 'rect', + style: { + fill: 'blue', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +### graph.edge(edgeFn) + +Set the style and other configurations for each edge. + +⚠️Attention: this funcion must **be called before graph.render()**. It does not take effect otherwise. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | -------- | -------- | ---------------------------------------- | +| edgeFn | Function | true | Return the configurations for each edge. | + +**Usage** + +```javascript +graph.edge((edge) => { + return { + id: edge.id, + type: 'cubic-horizontal', + style: { + stroke: 'green', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +### graph.combo(comboFn) + +Set the style and other configurations for each combo. + +⚠️Attention: this funcion must **be called before graph.render()**. It does not take effect otherwise. + +**Parameters** + +| Name | Type | Required | Description | +| ------- | -------- | -------- | ----------------------------------------- | +| comboFn | Function | true | Return the configurations for each combo. | + +**Usage** + +```javascript +graph.combo((combo) => { + return { + id: combo.id, + type: 'rect', + style: { + stroke: 'green', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +## Show/Hide + +### graph.showItem(item, stack) + +Show the item. If the item is a node, the related edges will be shown in the same time. Different from that, [item.show()](/en/docs/api/Items/itemMethods#itemshow) only show the node item itself. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| item | string / Object | true | The id or the instance of the item. | +| stack | boolean | false | Whether to push the operator into the undo & redo stack. If the `enableStack` is `true`, this operation will be automatically pushed into the stack by default. Set `stack` to be `false` if you do not want it. | + +**Usage** + +```javascript +// Find the item instance by id +const item = graph.findById('nodeId'); +graph.showItem(item); + +// equal to +graph.showItem('nodeId'); +``` + +### graph.hideItem(item, stack) + +Hide the item. If the item is a node, the related edges will be hidden in the same time. Different from that, [item.hide()](/en/docs/api/Items/itemMethods#itemhide) only hide the node item itself. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| item | string / Object | true | The id or the instance of the item. | +| stack | boolean | false | Whether to push the operator into the undo & redo stack. If the `enableStack` is `true`, this operation will be automatically pushed into the stack by default. Set `stack` to be `false` if you do not want it. | + +**Usage** + +```javascript +// Find the item instance by id +const item = graph.findById('nodeId'); +graph.hideItem(item); + +// Equal to +graph.hideItem('nodeId'); +``` diff --git a/packages/site/docs/api/graphFunc/item.zh.md b/packages/site/docs/api/graphFunc/item.zh.md new file mode 100644 index 0000000000..fef8ee74a0 --- /dev/null +++ b/packages/site/docs/api/graphFunc/item.zh.md @@ -0,0 +1,314 @@ +--- +title: 元素操作 +order: 4 +--- + +## 增删 + +### graph.addItem(type, model, stack) + +新增元素(节点和边)。 + +⚠️ 注意: 将会直接使用 `model` 对象作为新增元素的数据模型,G6 内部可能会对其增加或修改一些必要的字段。若不希望原始参数被修改,建议在使用深拷贝后的 `model`。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| type | string | true | 元素类型,可选值为 `'node'`、`'edge'` | +| model | Object | true | 元素的数据模型,具体内容参见[元素配置项](/zh/docs/api/Items/itemProperties)。 | +| stack | boolean | false | 操作是否入 undo & redo 栈,当实例化 Graph 时设置 enableStack 为 true 时,默认情况下会自动入栈,入栈以后,就支持 undo & redo 操作,如果不需要,则设置该参数为 false 即可 | + +**用法** + +```javascript +const model = { + id: 'node', + label: 'node', + address: 'cq', + x: 200, + y: 150, + style: { + fill: 'blue', + }, +}; + +graph.addItem('node', model); +``` + +### graph.removeItem(item, stack) + +删除元素,当 item 为 group ID 时候,则删除分组。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| item | string / Object | true | 元素 ID 或元素实例 | +| stack | boolean | false | 操作是否入 undo & redo 栈,当实例化 Graph 时设置 enableStack 为 true 时,默认情况下会自动入栈,入栈以后,就支持 undo & redo 操作,如果不需要,则设置该参数为 false 即可 | + +**用法** + +```javascript +// 通过 ID 查询节点实例 +const item = graph.findById('node'); +graph.removeItem(item); + +// 该操作不会进入到 undo & redo 栈,即 redo & undo 操作会忽略该操作 +graph.removeItem(item, false); +``` + +## 更新 + +### graph.updateItem(item, model, stack) + +更新元素,包括更新数据、样式等。若图上有 combo,使用该函数更新一个节点位置后,需要调用 [updateCombo(combo)](/zh/docs/api/Graph#updatecombocombo) 以更新相关 combo 的位置。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| item | string / Object | true | 元素 ID 或元素实例 | +| model | Object | true | 需要更新的数据模型,具体内容参见[元素配置项](/zh/docs/api/Items/itemProperties) | +| stack | boolean | false | 操作是否入 undo & redo 栈,当实例化 Graph 时设置 enableStack 为 true 时,默认情况下会自动入栈,入栈以后,就支持 undo & redo 操作,如果不需要,则设置该参数为 false 即可 | + +**用法** + +```javascript +const model = { + id: 'node', + label: 'node', + address: 'cq', + x: 200, + y: 150, + style: { + fill: 'blue', + }, +}; + +// 通过 ID 查询节点实例 +const item = graph.findById('node'); +graph.updateItem(item, model); +``` + +### graph.refreshItem(item) + +刷新指定元素。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | --------------- | -------- | ------------------ | +| item | string / Object | true | 元素 ID 或元素实例 | + +**用法** + +```javascript +// 通过 ID 查询节点实例 +const item = graph.findById('node'); +graph.refreshItem(item); +``` + +### graph.refreshPositions() + +当节点位置发生变化时,刷新所有节点位置,并重计算边的位置。 + +该方法无参数。 + +**用法** + +```javascript +graph.refreshPositions(); +``` + +### graph.updateCombos() + +根据子元素(子节点与子 combo)的 bbox 更新所有 combos 的绘制,包括 combos 的位置和范围。 + +**用法** + +```javascript +// 更新所有 combos +graph.updateCombos(); +``` + +### graph.updateCombo(combo) + +仅更新 combo 及其所有祖先 combo。建议在使用 graph.updateItem 来更新节点位置时,调用该方法以更新节点的祖先 combos。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | --------------- | -------- | ---------------------- | +| combo | string / ICombo | true | Combo ID 或 Combo 实例 | + +**用法** + +```javascript +// 更新了某个节点的位置 +const node1 = graph.findById('node1'); +graph.updateItem(node1, { + x: 100, + y: 100, +}); +const comboId = node1.getModel().comboId; + +// 更新 node1 所属的 combo 及其所有祖先 combo 的大小和位置 +graph.updateCombo(comboId); +``` + +### graph.updateComboTree(item, parentId) + +更新 Combo 结构,例如移动子树等。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| -------- | ----------------------- | -------- | ------------------------------------------- | +| item | string / INode / ICombo | true | 需要被更新的 Combo 或 节点 ID | +| parentId | string / undefined | false | 新的父 combo ID,undefined 代表没有父 combo | + +**用法** + +```javascript +// 将 combo1 从父 combo 中移出,完成后同原父 combo 平级 +graph.updateComboTree('combo1') + +// 将 combo1 移动到 Combo2 下面,作为 Combo2 的子元素 +graph.updateComboTree('combo1', 'combo2') +``` + +## 配置 + +### graph.node(nodeFn) + +设置各个节点样式及其他配置,以及在各个状态下节点的 KeyShape 的样式。 + +提示: 该方法必须**在 render 之前调用**,否则不起作用。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | -------- | -------- | ------------------ | +| nodeFn | Function | true | 返回每个节点的配置 | + +**用法** + +```javascript +graph.node((node) => { + return { + id: node.id, + type: 'rect', + style: { + fill: 'blue', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +### graph.edge(edgeFn) + +设置各个边样式及其他配置,以及在各个状态下节点的 KeyShape 的样式。 + +提示: 该方法必须**在 render 之前调用**,否则不起作用。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | -------- | -------- | ---------------- | +| edgeFn | Function | true | 返回每条边的配置 | + +**用法** + +```javascript +graph.edge((edge) => { + return { + id: edge.id, + type: 'cubic-horizontal', + style: { + stroke: 'green', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +### graph.combo(comboFn) + +设置各个 combo 样式及其他配置,以及在各个状态下节点的 KeyShape 的样式。 + +提示: 该方法必须**在 render 之前调用**,否则不起作用。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------- | -------- | -------- | --------------------- | +| comboFn | Function | true | 返回每个 combo 的配置 | + +**用法** + +```javascript +graph.combo((combo) => { + return { + id: combo.id, + type: 'rect', + style: { + stroke: 'green', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +## 显示/隐藏 + +### graph.showItem(item, stack) + +显示指定的元素。若 item 为节点,则相关边也会随之显示。而 [item.show()](/zh/docs/api/Items/itemMethods#itemshow) 则将只显示自身。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| item | string / Object | true | 元素 ID 或元素实例 | +| stack | boolean | false | 操作是否入 undo & redo 栈,当实例化 Graph 时设置 enableStack 为 true 时,默认情况下会自动入栈,入栈以后,就支持 undo & redo 操作,如果不需要,则设置该参数为 false 即可 | + +**用法** + +```javascript +// 通过 ID 查询节点实例 +const item = graph.findById('nodeId'); +graph.showItem(item); + +// 等价于 +graph.showItem('nodeId'); +``` + +### graph.hideItem(item, stack) + +隐藏指定元素。若 item 为节点,则相关边也会随之隐藏。而 [item.hide()](/zh/docs/api/Items/itemMethods#itemhide) 则将只隐藏自身。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| item | string / Object | true | 元素 ID 或元素实例 | +| stack | boolean | false | 操作是否入 undo & redo 栈,当实例化 Graph 时设置 enableStack 为 true 时,默认情况下会自动入栈,入栈以后,就支持 undo & redo 操作,如果不需要,则设置该参数为 false 即可 | + +**用法** + +```javascript +// 通过 ID 查询节点实例 +const item = graph.findById('nodeId'); +graph.hideItem(item); + +// 等价于 +graph.hideItem('nodeId'); +``` diff --git a/packages/site/docs/api/graphFunc/layout.en.md b/packages/site/docs/api/graphFunc/layout.en.md new file mode 100644 index 0000000000..e5876df273 --- /dev/null +++ b/packages/site/docs/api/graphFunc/layout.en.md @@ -0,0 +1,126 @@ +--- +title: Layout +order: 10 +--- + +There are several basic layout algorithms in G6 3.1. For more information, please refer to [Graph Layout API](/en/docs/api/graphLayout/guide) or [TreeGraph Layout API](/en/docs/api/treeGraphLayout/guide). + +### graph.layout() + +Re-layout the graph with current layout configurations in graph. + +**Usage** + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'force', + }, + modes: { + default: ['drag-node'], + }, +}); + +graph.data({ + nodes: data.nodes, + edges: data.edges.map((edge, i) => { + edge.id = 'edge' + i; + return Object.assign({}, edge); + }), +}); + +graph.render(); + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; +} + +graph.on('node:dragstart', (e) => { + // Relayout when dragging the node + graph.layout(); + refreshDragedNodePosition(e); +}); + +graph.on('node:drag', (e) => { + refreshDragedNodePosition(e); +}); + +graph.on('node:dragend', (e) => { + e.item.get('model').fx = null; + e.item.get('model').fy = null; +}); +``` + +### graph.updateLayout(cfg) + +Update the layout configurations. + +1. If there is `type` in `cfg`, `type` is a string and it is different from current layout method, `updateLayout(cfg)` will change the layout method and relayout; +1. If there is no `type` in `cfg`, `updateLayout(cfg)` will relayout with current layout method and new layout configurations. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ----------------------------- | +| cfg | Object | true | Configurations of new layout. | + +**Usage** + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'circular', + center: [500, 300], + }, + animate: true, +}); +graph.data(data); +graph.render(); + +// configure the layout while instantializing the graph, and update the layout in somewhere you want. +graph.updateLayout({ + radius: 200, + startAngle: Math.PI / 4, + endAngle: Math.PI, + divisions: 5, + ordering: 'degree', +}); +``` + +### destroyLayout() + +Destroy the layout algorithm. After that, the `changeData` will not place the new nodes with origin layout configurations. + +**Usage** + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'circular', + center: [500, 300], + }, + animate: true, +}); +graph.data(data); +graph.render(); +graph.destroyLayout(); +// If there is no position info in data2, the new nodes will be placed according to position initing problem. If the position info exists, the new node will be placed according to its position info +graph.changeData(data2); +``` diff --git a/packages/site/docs/api/graphFunc/layout.zh.md b/packages/site/docs/api/graphFunc/layout.zh.md new file mode 100644 index 0000000000..f608ba8c52 --- /dev/null +++ b/packages/site/docs/api/graphFunc/layout.zh.md @@ -0,0 +1,126 @@ +--- +title: 布局 +order: 10 +--- + +G6 3.1 内置了丰富的布局。关于如何使用 G6 中内置的布局,请参考  [图布局 API](/zh/docs/api/graphLayout/guide) 或 [树图布局 API](/zh/docs/api/treeGraphLayout/guide)。 + +### graph.layout() + +重新以当前配置的属性进行一次布局。 + +**用法** + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'force', + }, + modes: { + default: ['drag-node'], + }, +}); + +graph.data({ + nodes: data.nodes, + edges: data.edges.map((edge, i) => { + edge.id = 'edge' + i; + return Object.assign({}, edge); + }), +}); + +graph.render(); + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; +} + +graph.on('node:dragstart', (e) => { + // 拖动节点时重新布局 + graph.layout(); + refreshDragedNodePosition(e); +}); + +graph.on('node:drag', (e) => { + refreshDragedNodePosition(e); +}); + +graph.on('node:dragend', (e) => { + e.item.get('model').fx = null; + e.item.get('model').fy = null; +}); +``` + +### graph.updateLayout(cfg) + +更新布局配置项。 + +1. 如果参数 `cfg` 中含有 `type` 字段,`type` 字段类型为 String,且与现有布局方法不同,则更换布局; +1. 如果参数 `cfg` 中不包含 `type` 字段,则保持原有布局,仅更新布局配置项。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ------------ | +| cfg | Object | true | 新布局配置项 | + +**用法** + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'circular', + center: [500, 300], + }, + animate: true, +}); +graph.data(data); +graph.render(); + +// 实例化时通过 layout 指定布局,在合适的时候通过 updateLayout 更新布局配置 +graph.updateLayout({ + radius: 200, + startAngle: Math.PI / 4, + endAngle: Math.PI, + divisions: 5, + ordering: 'degree', +}); +``` + +### destroyLayout() + +销毁布局方法,在此之后调用 `changeData` 等方法将不会按照原有的布局算法进行布局。 + +**用法** + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'circular', + center: [500, 300], + }, + animate: true, +}); +graph.data(data); +graph.render(); +graph.destroyLayout(); +// 此时 changeData,若 data2 中的节点没有位置信息,将按照初始化计算方法被放置;若有位置信息,则按照该信息被放置 +graph.changeData(data2); +``` diff --git a/packages/site/docs/api/graphFunc/mode.en.md b/packages/site/docs/api/graphFunc/mode.en.md new file mode 100644 index 0000000000..e005a49763 --- /dev/null +++ b/packages/site/docs/api/graphFunc/mode.en.md @@ -0,0 +1,46 @@ +--- +title: Set/Get Mode +order: 8 +--- + +### graph.setMode(mode) + +Switch the interaction mode of graph. For example, switch from edit mode to read-only mode. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | --------------------- | +| mode | string | true | The name of the mode. | + +**Usage** + +```javascript +const graph = new G6.Graph({ + container: div, + width: 500, + height: 500, + modes: { + default: [...], + custom: [...] + } +}) + +graph.setMode('custom') +``` + +### graph.getCurrentMode() + +Get the current mode. + +**Return** + +- Type of return value: string; +- The return value indicates the current mode. + +**Usage** + +```javascript +// The return value is the current interaction mode +const mode = graph.getCurrentMode(); +``` diff --git a/packages/site/docs/api/graphFunc/mode.zh.md b/packages/site/docs/api/graphFunc/mode.zh.md new file mode 100644 index 0000000000..6fe5a7d053 --- /dev/null +++ b/packages/site/docs/api/graphFunc/mode.zh.md @@ -0,0 +1,48 @@ +--- +title: 行为模式 +order: 8 +--- + +### graph.setMode(mode) + +切换图行为模式。主要用于不同模式下的行为切换,如从编辑模式下切换到只读模式。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ---------- | +| mode | string | true | 模式的名称 | + +**用法** + +```javascript +const graph = new G6.Graph({ + container: div, + width: 500, + height: 500, + modes: { + default: [...], + custom: [...] + } +}) + +graph.setMode('custom') +``` + +### graph.getCurrentMode() + +获取当前的行为模式。 + +该方法无参数。 + +**返回值** + +- 返回值类型:String; +- 返回值表示当前的行为模式。 + +**用法** + +```javascript +// 返回值 mode 表示当前的行为模式 +const mode = graph.getCurrentMode(); +``` diff --git a/packages/site/docs/api/graphFunc/on_off.en.md b/packages/site/docs/api/graphFunc/on_off.en.md new file mode 100644 index 0000000000..3a585c6295 --- /dev/null +++ b/packages/site/docs/api/graphFunc/on_off.en.md @@ -0,0 +1,169 @@ +--- +title: On/Off Event +order: 9 +--- + +### graph.on(eventName, handler) + +Bind event listeners for graph. + +**Parameters** + +| Name | Type | Required | Description | +| --------- | -------- | -------- | ------------------------------------------------------------- | +| eventName | string | true | Name of the event, options are in [Event](/en/docs/api/Event) | +| handler | Function | true | The listener function | + +Here is the description for the objects `item` and `target` of the `handler`'s parameter `evt`: + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| item | Item | true | The manipulated item | +| target | IShape | true | The manipulated [Graphics Shape](/en/docs/manual/middle/elements/shape/shape-keyshape) | + +**Usage** + +```javascript +const graph = new G6.Graph({ + // ... +}); + +// bind the node click listener for nodes of the graph +graph.on('node:click', (evt) => { + const item = evt.item; // The manipulated node item + const target = evt.target; // The manipulated graphics shape + // ... +}); + +// bind the click listener for canvas +graph.on('click', (evt) => { + // ... +}); +``` + + +### graph.emit(eventName, params) + +Trigger some event manually. Similar to `dispatch` in DOM. + +**Parameters** + +| Name | Type | Required | Description | +| --------- | -------- | -------- | -------------------------------------------------- | +| eventName | string | true | Name of the event, options are in [Event](/en/docs/api/Event). And you can custom a event name to trigger, use graph.on to bind lisenter for the custom event name too | +| params | object | true | The parameters of this event. If you want to trigger the events in [Event Docs](/en/docs/api/Event), you might need to simulate some required [Event Parameters](en/docs/api/Event#交互事件) for different situations | + +**Usage** + +```javascript +const node = graph.findById('node1'); +// Trigger the node click event +graph.emit('node:click', { + item: node, // the 'clicked' node + target: node.getKeyShape(), // the 'clicked' shape on the node. It uses the keyShape of the node here, you could assign any shapes in the graphics group (node.getContainer()) of the node + x: 10, + y: 10 + // ... +}) + +// Trigger a customo event named 'someevent', and pass some custom parameters +// listen to it by graph.on('someevent', e => {}). Notice that, you should bind the listener before emit it +graph.emit('someevent', { + name: 'xxx', + value: 'xxx' +}); + +``` +### graph.off(eventName, handler) + +Unbind the specific listener. + +**Parameters** + +| Name | Type | Required | Description | +| --------- | -------- | -------- | ------------------------------------------------------------- | +| eventName | string | true | Name of the event, options are in [Event](/en/docs/api/Event) | +| handler | Function | true | The listener function | + +The objects `item` and `target` of the `handler`'s parameter `evt` are the same as the ones described in [`graph.on(eventName, handler)`](#oneventname-handler). The `handler` should be the same object of binded `handler`. + +**Usage** + +```javascript +const graph = new G6.Graph({ + // ... +}); + +// listeners +const fn = (evt) => { + const item = evt.item; // The manipulated node item + const target = evt.target; // The manipulated graphics shape + // ... +}; +// bind node click listener +graph.on('node:click', fn); + +// Unbind the node click listener. The fn is the same object as above +graph.off('node:click', fn); +``` + +### graph.off(eventName) + +Unbind all the listeners for the graph. + +**Parameters** + +| Name | Type | Required | Description | +| --------- | ------ | -------- | ------------------------------------------------------------- | +| eventName | string | true | Name of the event, options are in [Event](/en/docs/api/Event) | + +**Usage** + +```javascript +const graph = new G6.Graph({ + // ... +}); + +// listeners +const fn1 = (evt) => { + const item = evt.item; // the manipulated node item + const target = evt.target; // the manipulated graphics shape + // ... +}; +const fn2 = (evt) => { + // ... +}; +// bind two listeners for nodes of the graph +graph.on('node:click', fn1); +graph.on('node:click', fn2); + +// unbind all the click listeners +graph.off('node:click'); +``` + +### graph.off() + +Unbind all the event listeners of the graph. There is no parameter for this function. + +**Usage** + +```javascript +const graph = new G6.Graph({ + // ... +}); + +// listeners +const fn1 = (evt) => { + // ... +}; +const fn2 = (evt) => { + // ... +}; +// bind mouseenter listner for the nodes of the graph +graph.on('node:mouseenter', fn1); +// bind afteranimate timing listener for graph +graph.on('afteranimate', fn2); + +// unbind all the events of the graph +graph.off(); +``` diff --git a/packages/site/docs/api/graphFunc/on_off.zh.md b/packages/site/docs/api/graphFunc/on_off.zh.md new file mode 100644 index 0000000000..3d66974044 --- /dev/null +++ b/packages/site/docs/api/graphFunc/on_off.zh.md @@ -0,0 +1,151 @@ +--- +title: 事件绑定/解绑 +order: 9 +--- + +### graph.on(eventName, handler) + +为图绑定事件监听。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --------- | -------- | -------- | -------------------------------------------------- | +| eventName | string | true | 事件名,可选事件名参见 [Event](/zh/docs/api/Event) | +| handler | Function | true | 监听函数 | + +这里对 `handler` 的参数 `evt` 中 `item` 和 `target` 参数进行解释: + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| item | Item | true | 被操作的 item | +| target | Ishape | true | 被操作的具体[图形](/zh/docs/manual/middle/elements/shape/shape-keyshape) | + +**用法** + +```javascript +// 为图上的所有节点绑定点击监听 +graph.on('node:click', (evt) => { + const item = evt.item; // 被操作的节点 item + const target = evt.target; // 被操作的具体图形 + // ... +}); + +// 为画布绑定点击监听 +graph.on('click', (evt) => { + // ... +}); +``` + +### graph.emit(eventName, params) + +手动触发某个事件。类似于 dispatch。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --------- | -------- | -------- | -------------------------------------------------- | +| eventName | string | true | 事件名,可选事件名参见 [Event](/zh/docs/api/Event)。也可以是自定事件名,同样通过 graph.on 监听该事件名 | +| params | object | true | 触发该事件的参数。若用于触发 [Event](/zh/docs/api/Event) 中的事件,可能需要模拟一些必要的[事件参数](zh/docs/api/Event#交互事件) | + +**用法** + +```javascript +const node = graph.findById('node1'); +// 模拟触发节点点击事件 +graph.emit('node:click', { + item: node, // 被点击的节点 + target: node.getKeyShape(), // 具体图形,这里使用了节点的 keyShape,也可以是该节点 node.getContainer() 图形分组中的其它图形 + x: 10, + y: 10 + // ... +}) + +// 模拟触发一个自定义名为 someevent 的事件,并传入自定义参数 +// 使用 graph.on('someevent', e => {}) 可监听该事件。注意,请在该事件触发之前绑定监听(graph.on) +graph.emit('someevent', { + name: 'xxx', + value: 'xxx' +}); + +``` +### graph.off(eventName, handler) + +为图解除指定的事件监听。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --------- | -------- | -------- | -------------------------------------------------- | +| eventName | string | true | 事件名,可选事件名参见 [Event](/zh/docs/api/Event) | +| handler | Function | true | 监听函数 | + +这里对 `handler` 的参数 `evt` 中 `item` 和 `target` 同 [`graph.on(eventName, handler)`](#oneventname-handler)。该 `handler` 必须与绑定该事件的 `handler` 是同一对象。 + +**用法** + +```javascript +// 监听函数 +const fn = (evt) => { + const item = evt.item; // 被操作的节点 item + const target = evt.target; // 被操作的具体图形 + // ... +}; +// 为图上的所有节点绑定点击监听 +graph.on('node:click', fn); + +// 解除上面的点击监听事件,注意 fn 必须是同一个对象 +graph.off('node:click', fn); +``` + +### graph.off(eventName) + +为图解除某事件的所有监听。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --------- | ------ | -------- | -------------------------------------------------- | +| eventName | string | true | 事件名,可选事件名参见 [Event](/zh/docs/api/Event) | + +**用法** + +```javascript +// 监听函数 +const fn1 = (evt) => { + const item = evt.item; // 被操作的节点 item + const target = evt.target; // 被操作的具体图形 + // ... +}; +const fn2 = (evt) => { + // ... +}; +// 为图上的所有节点绑定点击监听 +graph.on('node:click', fn1); +graph.on('node:click', fn2); + +// 解除上面的所有节点点击监听事件 +graph.off('node:click'); +``` + +### graph.off() + +为图解除所有监听。该函数无参数。 + +**用法** + +```javascript +// 监听函数 +const fn1 = (evt) => { + // ... +}; +const fn2 = (evt) => { + // ... +}; +// 为图上的所有节点绑定点击监听 +graph.on('node:mouseenter', fn1); +graph.on('afteranimate', fn2); + +// 解除图上所有监听事件 +graph.off(); +``` diff --git a/packages/site/docs/api/graphFunc/render.en.md b/packages/site/docs/api/graphFunc/render.en.md new file mode 100644 index 0000000000..abf1382a50 --- /dev/null +++ b/packages/site/docs/api/graphFunc/render.en.md @@ -0,0 +1,68 @@ +--- +title: Render/Refresh +order: 1 +--- + +### graph.render() + +```javascript +graph.render(); +``` + +Render the graph with data onto the canvas. + +### graph.refresh() + +Refresh the canvas when the **existing** data items' configurations is changed in the source data. + +Attention: If there are some new nodes/edges/combos to be added or some nodes/edges/combos to be removed, use [graph.addItem](/en/docs/api/graphFunc/item/#graphadditemtype-model-stack) / [graph.removeItem](/en/docs/api/graphFunc/item/#graphremoveitemitem-stack) or [graph.changeData](/en/docs/api/graphFunc/data/#graphchangedatadata-stack) instead. + +**Usage** + +```javascript +graph.refresh(); +``` + +### graph.paint() + +Repaint the canvas. Use it after changing the item's style or state. + +**Usage** + +```javascript +const item = e.item; +const graph = this.graph; + +const autoPaint = graph.get('autoPaint'); +graph.setAutoPaint(false); + +graph.setItemState(item, 'selected', true); + +graph.paint(); +graph.setAutoPaint(autoPaint); +``` + +### graph.setAutoPaint(auto) + +Whether to repaint the canvas automatically after updating or deleting items. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------- | -------- | -------------------------------------------- | +| auto | Boolean | true | Whether to repaint the canvas automatically. | + +**Usage** + +```javascript +const item = e.item; +const graph = this.graph; + +const autoPaint = graph.get('autoPaint'); +graph.setAutoPaint(false); + +graph.setItemState(item, 'selected', true); + +graph.paint(); +graph.setAutoPaint(autoPaint); +``` diff --git a/packages/site/docs/api/graphFunc/render.zh.md b/packages/site/docs/api/graphFunc/render.zh.md new file mode 100644 index 0000000000..1480fa5102 --- /dev/null +++ b/packages/site/docs/api/graphFunc/render.zh.md @@ -0,0 +1,74 @@ +--- +title: 渲染与更新 +order: 1 +--- + +### graph.render() + +根据提供的数据渲染视图。 + +**用法** + +```javascript +graph.render(); +``` + +### graph.refresh() + +当源数据中**现有**节点/边/ Combo 的数据项发生配置的变更时,根据新数据刷新视图。 + +注意:节点/边/ Combo 数据的增删需要使用 [graph.addItem](/zh/docs/api/graphFunc/item/#graphadditemtype-model-stack) / [graph.removeItem](/zh/docs/api/graphFunc/item/#graphremoveitemitem-stack) 或 [graph.changeData](/zh/docs/api/graphFunc/data/#graphchangedatadata-stack)。 + +该方法无参数。 + +**用法** + +```javascript +graph.refresh(); +``` + +### graph.paint() + +仅重新绘制画布。当设置了元素样式或状态后,通过调用 `paint()` 方法,让修改生效。 + +该方法无参数。 + +**用法** + +```javascript +const item = e.item; +const graph = this.graph; + +const autoPaint = graph.get('autoPaint'); +graph.setAutoPaint(false); + +graph.setItemState(item, 'selected', true); + +graph.paint(); +graph.setAutoPaint(autoPaint); +``` + +### graph.setAutoPaint(auto) + +设置是否在更新/删除后自动重绘,一般搭配 `paint()` 方法使用。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------- | -------- | ------------ | +| auto | Boolean | true | 是否自动重绘 | + +**用法** + +```javascript +const item = e.item; +const graph = this.graph; + +const autoPaint = graph.get('autoPaint'); +graph.setAutoPaint(false); + +graph.setItemState(item, 'selected', true); + +graph.paint(); +graph.setAutoPaint(autoPaint); +``` diff --git a/packages/site/docs/api/graphFunc/stack.en.md b/packages/site/docs/api/graphFunc/stack.en.md new file mode 100644 index 0000000000..bab59acb29 --- /dev/null +++ b/packages/site/docs/api/graphFunc/stack.en.md @@ -0,0 +1,45 @@ +--- +title: Opeartion Stack +order: 13 +--- + +## pushStack(action, data, stackType) + +Push operation。 + +Implemented the undo function, refer to [here](https://github.com/antvis/G6/blob/master/packages/plugin/src/toolBar/index.ts#L208) + +Implemented the redo function, refer to[here](https://github.com/antvis/G6/blob/master/packages/plugin/src/toolBar/index.ts#L295) + +**参数** + +| Name | Type | Required | Description | +| --------- | ------- | -------- | ------------------------------------------------ | --------------------------------------------------- | +| action | string | false | operation type,the value of 'update' by default | +| data | unknown | false | Stacked data | +| stackType | 'redo' / 'undo' | false | push operation type,the value of 'undo' by default | + +## getUndoStack() + +get undo stack。 + +## getRedoStack() + +get redo stack。 + +## getStackData() + +get the data in stack。 + +The return value type: + +``` +{ + undoStack: StackData[]; + redoStack: StackData[]; +}; +``` + +## clearStack() + +Clear the data in stack。 diff --git a/packages/site/docs/api/graphFunc/stack.zh.md b/packages/site/docs/api/graphFunc/stack.zh.md new file mode 100644 index 0000000000..db53cf21cd --- /dev/null +++ b/packages/site/docs/api/graphFunc/stack.zh.md @@ -0,0 +1,45 @@ +--- +title: 操作栈 +order: 13 +--- + +## pushStack(action, data, stackType) + +入栈操作。 + +实现 undo 功能,可参考[这里](https://github.com/antvis/G6/blob/master/packages/plugin/src/toolBar/index.ts#L208) + +实现 redo 功能,可参考[这里](https://github.com/antvis/G6/blob/master/packages/plugin/src/toolBar/index.ts#L295) + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --------- | ------- | -------- | ------------------------- | --------------------- | +| action | string | false | 操作类型,默认值为 update | +| data | unknown | false | 入栈的数据 | +| stackType | 'redo' / 'undo' | false | 入栈类型,默认为 undo | + +## getUndoStack() + +获取 undo 栈。 + +## getRedoStack() + +获取 redo 栈。 + +## getStackData() + +获取栈中的数据。 + +返回值类型为: + +``` +{ + undoStack: StackData[]; + redoStack: StackData[]; +}; +``` + +## clearStack() + +清空栈中的数据。 diff --git a/packages/site/docs/api/graphFunc/state.en.md b/packages/site/docs/api/graphFunc/state.en.md new file mode 100644 index 0000000000..405fef5093 --- /dev/null +++ b/packages/site/docs/api/graphFunc/state.en.md @@ -0,0 +1,73 @@ +--- +title: State of Item +order: 7 +--- + +### graph.setItemState(item, state, enabled) + +Set the item's state. + +v3.4 and futher versions support multiple values for a state, refer to [Take Use of State Mechanism](/en/docs/manual/advanced/state-new). + +This function will emit events `beforitemstatechange` and `afteritemstatechange`. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| item | string / Object | true | The id or the instance of the item. | +| state | string | true | The value of state. State can be comstomized as selected, hover, actived, and so on. | +| enabled | Boolean | true | Whether to activate the state. | + +**Usage** + +```javascript +// boolean values for state 'selected' +graph.setItemState('node1', 'selected', true); + +// multiple values for state 'body' +graph.setItemState('node1', 'body', 'health'); +graph.setItemState('node2', 'body', 'ill'); +``` + +### graph.clearItemStates(item, states) + +Clear the states of the item. This function could clear multiple states in the same time. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| item | string / Object | true | The id or the instance of the item. | +| states | string / Array / null  | false | It can be a single state value, an array, or null. When it is null, this operation will clear all state of the item. | + +**Usage** + +```javascript +// Clear single state 'a' of the node +graph.clearItemStates(node, 'a'); + +// Clear multiple states of the node +graph.clearItemStates(node, ['a', 'b']); + +// Clear all the states of the node +graph.clearItemStates(node); +``` + +### graph.priorityState(item, state) + +Raise the priority of the specified state to the highest priority. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | --------------- | -------- | ----------------------------------- | +| item | string / Object | true | The id or the instance of the item. | +| states | string | true | state value | + +**Usage** + +```javascript +// Adjust the a state of node to the highest priority +graph.priorityState(node, 'a'); +``` diff --git a/packages/site/docs/api/graphFunc/state.zh.md b/packages/site/docs/api/graphFunc/state.zh.md new file mode 100644 index 0000000000..e04f6bad71 --- /dev/null +++ b/packages/site/docs/api/graphFunc/state.zh.md @@ -0,0 +1,71 @@ +--- +title: 元素状态 +order: 7 +--- + +### graph.setItemState(item, state, value) + +设置元素状态。支持单个状态多值的情况,详情参考 [G6 状态管理最佳实践](/zh/docs/manual/advanced/state-new)。 + +该方法在执行过程中会触发 `beforitemstatechange`,`afteritemstatechange` 事件。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ---------------- | -------- | ---------------------------------------------------- | +| item | string / Item | true | 元素 ID 或元素实例 | +| state | string | true | 状态值,支持自定义,如 selected、hover、actived 等。 | +| value | Boolean / string | true | 是否启用状态 | + +**用法** + +```javascript +// 布尔状态 'selected' +graph.setItemState('node1', 'selected', true); + +// 多值状态 'body' +graph.setItemState('node1', 'body', 'health'); +graph.setItemState('node2', 'body', 'ill'); +``` + +### graph.clearItemStates(item, states) + +清除元素状态,可以一次性清除多个状态。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | --------------------- | -------- | ---------------------------------------- | +| item | string / Object | true | 元素 ID 或元素实例 | +| states | string / Array / null | false | 取值可以是单个状态值,也可以是状态值数组 | + +**用法** + +```javascript +// 清除单个状态 +graph.clearItemStates(node, 'a'); + +// 清除多个状态 +graph.clearItemStates(node, ['a', 'b']); + +// 清除所有 +graph.clearItemStates(node); +``` + +### graph.priorityState(item, state) + +将指定状态的优先级提升为最高优先级。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | --------------- | -------- | ------------------ | +| item | string / Object | true | 元素 ID 或元素实例 | +| states | string | true | 状态名称 | + +**用法** + +```javascript +// 将 node 的 a 状态调整为优先级最高 +graph.priorityState(node, 'a'); +``` diff --git a/packages/site/docs/api/graphFunc/transform.en.md b/packages/site/docs/api/graphFunc/transform.en.md new file mode 100644 index 0000000000..f1bc7fc2c7 --- /dev/null +++ b/packages/site/docs/api/graphFunc/transform.en.md @@ -0,0 +1,222 @@ +--- +title: View Port Operation +order: 3 +--- + +### graph.getZoom() + +Get the current zoom ratio. + +**Return** + +- Type of return value: Number; +- The return value indicates the current zoom ratio of view port. The default value is 1. + +**Usage** + +```javascript +// The return value indicates the current zoom ratio +const zoom = graph.getZoom(); +``` + +### graph.zoom(ratio, center, animate, animateCfg) + +Change the scale of the graph with a relative ratio. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| ratio | Number | true | Relative zoom ratio | +| center | Object | false | The zoom center. If it is not assigned, (0, 0) will be regarded as the zoom center | +| animate | boolean | false | Whether move the graph with animation. If it is not assigned, animates following the graph's `animate`. | +| animateCfg | Object | false | The animation's configuraiton. Its configurations can be found in [Basic Animation Docs](/en/docs/manual/middle/animation). | + +**Usage** + +```javascript +// zoom to scale 3 at the center (100, 100) +graph.zoom(3, { x: 100, y: 100 }); + +// zoom to scale 0.5 at the origin (0, 0) of canvas drawing coordinate system, which is not the same as the lefttop of the viewport. To see the transformantion and relationships between three coordinate systems in G6, checkout out the doc: https://g6.antv.antgroup.com/en/manual/advanced/coordinate-system +graph.zoom(0.5); + +// zoom to scale 3 at the center (100, 100) with animation +graph.zoom(3, { x: 100, y: 100 }, true, { + duration: 100, +}); +``` + +### graph.zoomTo(toRatio, center) + +Scale the graph to a target ratio. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| toRatio | Number | true | The target ratio | +| center | Object | false | The zoom center. If it is not assigned, (0, 0) will be regarded as the zoom center | +| animate | boolean | false | Whether move the graph with animation. If it is not assigned, animates following the graph's `animate`. | +| animateCfg | Object | false | The animation's configuraiton. Its configurations can be found in [Basic Animation Docs](/en/docs/manual/middle/animation). | + +**Usage** + +```javascript +// Scale the graph 3 times at the center (100, 100) +graph.zoomTo(3, { x: 100, y: 100 }); + +// Scale the graph 0.5 times at the center (0, 0) +graph.zoomTo(0.5); + +// Scale the graph 3 times at the center (100, 100) with animation +graph.zoomTo(3, { x: 100, y: 100 }, true, { + duration: 100, +}); +``` + + +### graph.changeSize(width, height) + +Change the size of the canvas. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | ------ | -------- | ------------------------- | +| width | Number | true | The width of the canvas. | +| height | Number | true | The height of the canvas. | + +**Usage** + +```javascript +graph.changeSize(600, 350); +``` + +### graph.translate(dx, dy, animate, animateCfg) + +Move the canvas with **relative displacement**. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ----------------------------------------- | +| dx | Number | true | Displacement in the horizontal direction. | +| dy | Number | true | Displacement in the vertical direction. | +| animate | boolean | false | Whether translate the graph with animation. | +| animateCfg | Object | false | The animation's configuraiton. Its configurations can be found in [Basic Animation Docs](/en/docs/manual/middle/animation). If it is not assigned, animates following the graph's `animateCfg`. | + +**Usage** + +```javascript +graph.translate(100, 100); + +// 带动画 +graph.translate(100, 100, true, { + duration: 100, +}); +``` + +### graph.moveTo(x, y, animate, animateCfg) + +Move the canvas to a **fixed position**. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| x | Number | true | Displacement in the horizontal direction. | +| y | Number | true | Displacement in the vertical direction. | +| animate | boolean | false | Whether move the graph with animation. If it is not assigned, animates following the graph's `animate`. | +| animateCfg | Object | false | The animation's configuraiton. Its configurations can be found in [Basic Animation Docs](/en/docs/manual/middle/animation). If it is not assigned, animates following the graph's `animateCfg`. | + +**Usage** + +```javascript +graph.moveTo(200, 300); + +// with animation +graph.moveTo(200, 300, true, { + duration: 100, +}); +``` + +### graph.fitView(padding, rules, animate, animateCfg) + +Fit the graph to the view port. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| padding | Number / Array | false | The padding of [top, right, bottom, left]. | +| rules | { onlyOutOfViewPort?: boolean; direction?: 'x' / 'y' / 'both'; ratioRule?: 'max' / 'min} | false | rules of fitView | +| animate | boolean | false | _Supported by v4.6.15._ Whether move the graph with animation. If it is not assigned, animates following the graph's `animate`. | +| animateCfg | Object | false | _Supported by v4.6.15._ The animation's configuraiton. Its configurations can be found in [Basic Animation Docs](/en/docs/manual/middle/animation). If it is not assigned, animates following the graph's `animateCfg`. | + +**Usage** + +```javascript +// When padding is a number, top = right = bottom = left = 20 +graph.fitView(20); + +// Equal to graph.fitView(20) +graph.fitView([20]); + +// When padding is an array with 2 values, top = bottom = 20, right = left = 10 +graph.fitView([20, 10]); + +// When padding is an array with four values +graph.fitView([20, 10, 20, 15]); + +// Use fitViewByRules, default rules: onlyOutOfViewPort = false, direction = 'both', ratioRule = 'min' +graph.fitViewByRule(0, {}); + +// use fitViewByRules, custom rules +graph.fitView(0, { onlyOutOfViewPort: true, direction: 'y' }); +``` + +### graph.fitCenter() + +_Supported by v3.5.1._ Translate the graph to align its center with the canvas. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| animate | boolean | false | _Supported by v4.6.15._ Whether move the graph with animation. If it is not assigned, animates following the graph's `animate`. | +| animateCfg | Object | false | _Supported by v4.6.15._ The animation's configuraiton. Its configurations can be found in [Basic Animation Docs](/en/docs/manual/middle/animation). If it is not assigned, animates following the graph's `animateCfg`. | + +**Usage** + +```javascript +// Call the following function after rendering and animation +graph.fitCenter(); +``` + +### graph.focusItem(item, animate, animateCfg) + +Move the graph to center at the item. This operation can be used as easing animation after searching a node. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| item | string / Object | true | The id or the instance of the item. | +| animate | boolean | false | Whether move the graph with animation. If it is not assigned, animates following the graph's `animate`. | +| animateCfg | Object | false | The animation's configuraiton. Its configurations can be found in [Basic Animation Docs](/en/docs/manual/middle/animation). If it is not assigned, animates following the graph's `animateCfg`. | + +**Usage** + +```javascript +graph.focusItem(item); + +// focus with animation +graph.focusItem(item, true); + +// focus with animation and animation's configuration +graph.focusItem(item, true, { + easing: 'easeCubic', + duration: 400, +}); +``` diff --git a/packages/site/docs/api/graphFunc/transform.zh.md b/packages/site/docs/api/graphFunc/transform.zh.md new file mode 100644 index 0000000000..3187418ea2 --- /dev/null +++ b/packages/site/docs/api/graphFunc/transform.zh.md @@ -0,0 +1,223 @@ +--- +title: 视口操作 +order: 3 +--- + +### graph.getZoom() + +获取当前视口的缩放比例。 + +该方法无参数。 + +**返回值** + +- 返回值类型:Number; +- 返回值表示当前视口的缩放比例, 默认值为 `1`。 + +**用法** + +```javascript +// 返回值zoom表示当前视口的缩放比例 +const zoom = graph.getZoom(); +``` + +### graph.zoom(ratio, center, animate, animateCfg) + +改变视口的缩放比例,在当前画布比例下缩放,是相对比例。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| ratio | Number | true | 缩放比例 | +| center | Object | false | 以 `center` 的 `x`、`y` 坐标为中心缩放,如果省略了 `center` 参数,则以元素当前位置为中心缩放 | +| animate | Boolean | false | 是否开启动画 | +| animateCfg | GraphAnimateConfig | false | 若带有动画,可配置动画,参见[基础动画教程](/zh/docs/manual/middle/animation)。若未配置,则跟随 graph 的 `animateCfg` 参数 | + +**用法** + +```javascript +// 以 (100, 100) 为中心点,放大到 3 +graph.zoom(3, { x: 100, y: 100 }); + +// 以绘制坐标系的原点 (0, 0) 为缩放中心,缩小到 0.5。注意绘制坐标系的原点 != 视窗左上角,可用 graph.getCanvasByViewport 进行转换。各坐标系解析文档: https://g6.antv.antgroup.com/manual/advanced/coordinate-system +graph.zoom(0.5); + +// 带动画以 (100, 100) 为中心点,放大到 3 +graph.zoom(3, { x: 100, y: 100 }, true, { + duration: 100, +}); +``` + +### graph.zoomTo(toRatio, center, animate, animateCfg) + +缩放视窗窗口到一个固定比例。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| toRatio | Number | true | 固定比例值 | +| center | Object | false | 以 `center` 的 `x`、`y` 坐标为中心缩放,如果省略了 `center` 参数,则以元素当前位置为中心缩放 | +| animate | Boolean | false | 是否开启动画 | +| animateCfg | GraphAnimateConfig | false | 若带有动画,可配置动画,参见[基础动画教程](/zh/docs/manual/middle/animation)。若未配置,则跟随 graph 的 `animateCfg` 参数 | + +**用法** + +```javascript +// 以 (100, 100) 为中心点,放大3倍 +graph.zoomTo(3, { x: 100, y: 100 }); + +// 以当前元素位置为中心,缩小到 0.5 +graph.zoomTo(0.5); + +// 带动画以 (100, 100) 为中心点,放大3倍 +graph.zoomTo(3, { x: 100, y: 100 }, true, { + duration: 100, +}); +``` + +### graph.changeSize(width, height) + +改变画布大小。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | ------ | -------- | -------- | +| width | Number | true | 画布宽度 | +| height | Number | true | 画布高度 | + +**用法** + +```javascript +graph.changeSize(600, 350); +``` + +### graph.translate(dx, dy, animate, animateCfg) + +采用**相对位移**来平移画布。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ------------ | +| dx | Number | true | 水平方向位移 | +| dy | Number | true | 垂直方向位移 | +| animate | Boolean | false | 是否开启动画 | +| animateCfg | GraphAnimateConfig | false | 若带有动画,可配置动画,参见[基础动画教程](/zh/docs/manual/middle/animation)。若未配置,则跟随 graph 的 `animateCfg` 参数 | + +**用法** + +```javascript +graph.translate(100, 100); + +// 带动画 +graph.translate(100, 100, true, { + duration: 100, +}); +``` + +### graph.moveTo(x, y, animate, animateCfg) + +采用**绝对位移**将画布移动到指定坐标。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| x | Number | true | 水平方向坐标 | +| y | Number | true | 垂直方向坐标 | +| animate | boolean | false | 是否带有动画。若未配置,则跟随 graph 的 `animate` 参数 | +| animateCfg | Object | false | 若带有动画,可配置动画,参见[基础动画教程](/zh/docs/manual/middle/animation)。若未配置,则跟随 graph 的 `animateCfg` 参数 | + +**用法** + +```javascript +graph.moveTo(200, 300); + +// 带动画 +graph.moveTo(200, 300, true, { + duration: 100, +}); +``` + +### graph.fitView(padding, rules, animate, animateCfg) + +让画布内容适应视口。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| padding | Number / Array | false | [top, right, bottom, left] 四个方向上的间距值 | +| rules | { onlyOutOfViewPort?: boolean; direction?: 'x' / 'y' / 'both'; ratioRule?: 'max' / 'min} | false | fitView 的规则 | +| animate | boolean | false | *v4.6.15 后支持。*是否带有动画。若未配置,则跟随 graph 的 `animate` 参数 | +| animateCfg | Object | false | *v4.6.15 后支持。*若带有动画,可配置动画,参见[基础动画教程](/zh/docs/manual/middle/animation)。若未配置,则跟随 graph 的 `animateCfg` 参数 | + +**用法** + +```javascript +// padding 只设置为一个值,则表示 top = right = bottom = left = 20 +graph.fitView(20); + +// 等价于 graph.fitView(20) +graph.fitView([20]); + +// padding 设置为数组,只传 2 个值,则 top = bottom = 20, right = left = 10 +graph.fitView([20, 10]); + +// padding 设置为数组,四个方向值都指定 +graph.fitView([20, 10, 20, 15]); + +// 使用fitViewByRules, 默认rules: onlyOutOfViewPort = false, direction = 'both', ratioRule = 'min' +graph.fitView(0, {}); + +// 使用fitViewByRules, 自定义rules +graph.fitView(0, { onlyOutOfViewPort: true, direction: 'y' }); +``` + +### graph.fitCenter(animate, animateCfg) + +*v3.5.1 后支持。*平移图到中心将对齐到画布中心,但不缩放。优先级低于 fitView。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| animate | boolean | false | *v4.6.15 后支持。*是否带有动画。若未配置,则跟随 graph 的 `animate` 参数 | +| animateCfg | Object | false | *v4.6.15 后支持。*若带有动画,可配置动画,参见[基础动画教程](/zh/docs/manual/middle/animation)。若未配置,则跟随 graph 的 `animateCfg` 参数 | + +**用法** + +```javascript +// 在渲染和动画完成后调用 +graph.fitCenter(); +``` + +### graph.focusItem(item, animate, animateCfg) + +移动图,使得 item 对齐到视口中心,该方法可用于做搜索后的缓动动画。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| item | string / Object | true | 元素 ID 或元素实例 | +| animate | boolean | false | 是否带有动画。若未配置,则跟随 graph 的 `animate` 参数 | +| animateCfg | Object | false | 若带有动画,可配置动画,参见[基础动画教程](/zh/docs/manual/middle/animation)。若未配置,则跟随 graph 的 `animateCfg` 参数 | + +**用法** + +```javascript +graph.focusItem(item); + +// 动画地移动 +graph.focusItem(item, true); + +// 动画地移动,并配置动画 +graph.focusItem(item, true, { + easing: 'easeCubic', + duration: 400, +}); +``` diff --git a/packages/site/docs/api/graphFunc/watermarker.en.md b/packages/site/docs/api/graphFunc/watermarker.en.md new file mode 100644 index 0000000000..5c66e11aae --- /dev/null +++ b/packages/site/docs/api/graphFunc/watermarker.en.md @@ -0,0 +1,63 @@ +--- +title: Water Marker +order: 16 +--- + +### graph.setTextWaterMarker(texts, config) + +Add text water marker for the canvas. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| texts | String[] / string / undefined | true | The text array for the water marker, the each item in the array with take one line | +| config | Object | false | The configurations for the text water marker, the properties are listed below | + +`config` is the configurations for the text water marker with: + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| width | Number | false | The width of a single marker, which controls the horizontal space between two single markers, `150` by default | +| height | Number | false | The height of a single marker, which controls the vertical space between two single markers, `100` by default | +| compatible | Boolean | false | Whether compatible with the browsers which does not support `pointer-events`, `false` by default | +| text | Object | false | The style sttributes for the text shapes, the default value is: `{ x: 0, y: 60, lineHeight: 20, rotate: 20, fontSize: 14, fontFamily: 'Microsoft YaHei', fill: 'rgba(0, 0, 0, 0.1)', baseline: 'Middle', }` | + +**Usage** + +```javascript +graph.setTextWaterMarker(['AntV', 'G6']); +``` + +### graph.setImageWaterMarker(imgURL, config) + +Add image water markers for the graph. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| imgURL | String / undefined | true | The url of the image for the water marker, the default value is `'https://gw.alipayobjects.com/os/s/prod/antv/assets/image/logo-with-text-73b8a.svg'` | +| config | Object | false | The configurations for the image watermarker, the properties are listed below | + +`config` is the configurations for the image water marker with: + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| width | Number | false | The width of a single marker, which controls the horizontal space between two single markers, `150` by default | +| height | Number | false | The height of a single marker, which controls the vertical space between two single markers, `130` by default | +| compatible | Boolean | false | Whether compatible with the browsers which does not support `pointer-events`, `false` by default | +| image | Object | false | The style sttributes for the image shapes, the default value is: `{ x: 0, y: 0, width: 30, height: 20, rotate: 0 }` | + +**Usage** + +```javascript +graph.setImageWaterMarker( + 'https://gw.alipayobjects.com/os/s/prod/antv/assets/image/logo-with-text-73b8a.svg', + { + width: 300, + height: 200, + image: { rotate: Math.PI / 3 }, + }, +); +``` diff --git a/packages/site/docs/api/graphFunc/watermarker.zh.md b/packages/site/docs/api/graphFunc/watermarker.zh.md new file mode 100644 index 0000000000..8c526679a3 --- /dev/null +++ b/packages/site/docs/api/graphFunc/watermarker.zh.md @@ -0,0 +1,63 @@ +--- +title: 增加水印 +order: 16 +--- + +### graph.setTextWaterMarker(texts, config) + +为画布添加文字水印。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | -------- | -------- | ---------------------------------------- | +| texts | String[] / string / undefined | true | 水印文字内容数组,数组中的不同项将会换行 | +| config | Object | false | 文字水印配置项,可选,具体字段见下方 | + +其中,config 为文字水印配置项,具体字段: + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| width | Number | false | 单个水印的宽,可控制单个水印之间的水平间距,默认 `150` | +| height | Number | false | 单个水印的高,可控制单个水印之间的竖直间距,默认 `100` | +| compatible | Boolean | false | 是否需要兼容不支持 `pointer-events` 属性的浏览器,默认为 `false` | +| text | Object | false | 文本图形的样式属性,默认为:`{ x: 0, y: 60, lineHeight: 20, rotate: 20, fontSize: 14, fontFamily: 'Microsoft YaHei', fill: 'rgba(0, 0, 0, 0.1)', baseline: 'Middle', }` | + +**用法** + +```javascript +graph.setTextWaterMarker(['AntV', 'G6']); +``` + +### graph.setImageWaterMarker(imgURL, config) + +为画布添加图片水印。注意,使用 `downloadImage` 或 `downloadFullImage` 下载图片将不会带有水印。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| imgURL | String / undefined | true | 水印图片,默认为 'https://gw.alipayobjects.com/os/s/prod/antv/assets/image/logo-with-text-73b8a.svg' | +| config | Object | false | 图片水印配置项,可选,具体字段见下方 | + +其中,config 为图片水印配置项,具体字段: + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| width | Number | false | 单个水印的宽,可控制单个水印之间的水平间距,默认 `150` | +| height | Number | false | 单个水印的高,可控制单个水印之间的竖直间距,默认 `130` | +| compatible | Boolean | false | 是否需要兼容不支持 `pointer-events` 属性的浏览器,默认为 `false` | +| image | Object | false | 图片图形的样式属性,默认为:`{ x: 0, y: 0, width: 30, height: 20, rotate: 0 }` | + +**用法** + +```javascript +graph.setImageWaterMarker( + 'https://gw.alipayobjects.com/os/s/prod/antv/assets/image/logo-with-text-73b8a.svg', + { + width: 300, + height: 200, + image: { rotate: Math.PI / 3 }, + }, +); +``` diff --git a/packages/site/docs/api/graphLayout/circular.en.md b/packages/site/docs/api/graphLayout/circular.en.md new file mode 100644 index 0000000000..47e8d3bd58 --- /dev/null +++ b/packages/site/docs/api/graphLayout/circular.en.md @@ -0,0 +1,67 @@ +--- +title: Circular +order: 4 +--- + +Circular layout arranges the node on a circle. By tuning the configurations, user can adjust the node ordering method, division number, radial layout, and so on. G6 implements it according to the paper: A framework and algorithms for circular drawings of graphs. + +
+img +img +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'circular', + center: [200, 200], // The center of the graph by default + radius: null, + startRadius: 10, + endRadius: 100, + clockwise: false, + divisions: 5, + ordering: 'degree', + angleRatio: 1, + }, +}); +``` + +## layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +## layoutCfg.radius + +**Type**: Number
**Default**: null
**Required**: false
**Description**: The radius of the circle. If the `raidus` exists, `startRadius` and `endRadius` do not take effect. + +## layoutCfg.startRadius + +**Type**: Number
**Default**: null
**Required**: false
**Description**: The start radius of spiral layout + +## layoutCfg.endRadius + +**Type**: Number
**Default**: null
**Required**: false
**Description**: The end radius of spiral layout + +## layoutCfg.clockwise + +**Type**: Boolean
**Default**: true
**Required**: false
**Description**: Whether to layout clockwisely + +## layoutCfg.divisions + +**Type**: Number
**Default**: 1
**Required**: false
**Description**: The division number of the nodes on the circle. Takes effect when `endRadius - startRadius !== 0` + +## layoutCfg.ordering + +**Type**: String
**Default**: false
**Options**: null | 'topology' | 'degree'
**Required**: false
**Description**: The ordering method for nodes. `null` by default, which means the nodes are arranged in data order. 'topology' means in topology order; 'degree' means in degree order. + +## layoutCfg.angleRatio + +**Type**: Number
**Default**: 1
**Required**: false
**Description**: How many 2\*PIs Between the first node and the last node + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. \ No newline at end of file diff --git a/packages/site/docs/api/graphLayout/circular.zh.md b/packages/site/docs/api/graphLayout/circular.zh.md new file mode 100644 index 0000000000..88512cfe17 --- /dev/null +++ b/packages/site/docs/api/graphLayout/circular.zh.md @@ -0,0 +1,67 @@ +--- +title: 环形 Circular +order: 4 +--- + +Circular 布局将所有节点布局在一个圆环上,可以选择节点在圆环上的排列顺序。可以通过参数的配置扩展出环的分组布局、螺旋形布局等。原文链接: A framework and algorithms for circular drawings of graphs。 + +
+img +img +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'circular', + center: [200, 200], // 可选,默认为图的中心 + radius: null, // 可选 + startRadius: 10, // 可选 + endRadius: 100, // 可选 + clockwise: false, // 可选 + divisions: 5, // 可选 + ordering: 'degree', // 可选 + angleRatio: 1, // 可选 + }, +}); +``` + +## layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +## layoutCfg.radius + +**类型**: Number
**默认值**:null
**是否必须**:false
**说明**:圆的半径。若设置了 radius,则 startRadius 与 endRadius 不生效 + +## layoutCfg.startRadius + +**类型**: Number
**默认值**:null
**是否必须**:false
**说明**:螺旋状布局的起始半径 + +## layoutCfg.endRadius + +**类型**:Number
**默认值**:null
**是否必须**:false
**说明**:螺旋状布局的结束半径 + +## layoutCfg.clockwise + +**类型**:Boolean
**默认值**:true
**是否必须**:false
**说明**:是否顺时针排列 + +## layoutCfg.divisions + +**类型**:Number
**默认值**:1
**是否必须**:false
**说明**:节点在环上的分段数(几个段将均匀分布),在 endRadius - startRadius != 0 时生效 + +## layoutCfg.ordering + +**类型**:String
**默认值**:false
**可选值**:null | 'topology' | 'degree'
**是否必须**:false
**说明**:节点在环上排序的依据。默认 null 代表直接使用数据中的顺序。'topology' 按照拓扑排序。'degree' 按照度数大小排序 + +## layoutCfg.angleRatio + +**类型**: Number
**默认值**:1
**是否必须**:false
**说明**:从第一个节点到最后节点之间相隔多少个 2\*PI + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 diff --git a/packages/site/docs/api/graphLayout/comboCombined.en.md b/packages/site/docs/api/graphLayout/comboCombined.en.md new file mode 100644 index 0000000000..8127f6e28f --- /dev/null +++ b/packages/site/docs/api/graphLayout/comboCombined.en.md @@ -0,0 +1,109 @@ +--- +title: ComboCombined +order: 14 +--- + +_It is a new feature of V4.6._ ComboCombined supports configuring layout method for items inside a combo and layout method for the outer combo and nodes. By default, the inner layout is Concentric layout, and the outer layout is Gforce. When you assigning inner layout by yourself, please use the sync layout methods, such as Circular, Concentric, Grid, Dagre, MDS, Radial, or any custom sync layout. You can also assign custom layout method for the outer layout. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + groupByTypes: false, // If you want to have a combo graph with reasonable visual levels of nodes, edges, and combo, set groupByTypes to false + layout: { + type: 'comboCombined', + center: [ 200, 200 ], // The center of the graph by default + onLayoutEnd: () => { + console.log('combo force layout done'); + } + } +); +``` + +### layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +### layoutCfg.nodeSize + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The diameter of the node. It is used for preventing node overlappings. If `nodeSize` is not assigned, the size property in node data will take effect. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default + +### layoutCfg.spacing + +**Type**: Number / Function
**Default**: 0
**Required**: false
**Example**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**Description**: Takes effect when `preventNodeOverlap` or `preventOverlap` is `true`. It is the minimum distance between nodes/combos to prevent node/combo overlappings. It can be a function to define different distances for different nodes (example 2) + +
+ +### layoutCfg.comboPadding + +**Type**: Number / Function
**Default**: 10
**Required**: false
**Example**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a combo + if (d.id === 'combo1') { + return 100; + } + return 10; +}; +``` + +### layoutCfg.outerLayout + +```javascript +outerLayout: new G6.Layout['gForce']({ + ... // the parameters for the gForce layout +}); +``` + +**Type**: Object
**Default**: GForce Instance
**Required**: false
**Description**: The outer layout instance. Refer to the corresponding layout docs. The default configuration of the `outerLayout` is: + +```javascript +outerLayout: new G6.Layout['gForce']({ + gravity: 1, + factor: 2, + linkDistance: (edge: any, source: any, target: any) => { + const nodeSize = ((source.size?.[0] || 30) + (target.size?.[0] || 30)) / 2; + return Math.min(nodeSize * 1.5, 700); + } +}); +``` + +### layoutCfg.innerLayout + +```javascript +innerLayout: new G6.Layout['grid']({ + ... // the parameters for the grid layout +}); +``` + +**Type**:Object
**Default**:Concentric Instance
**Required**:false
**Description**: The layout method for the items inside a combo, should be a sync layout method. Refer to the corresponding layout docs. The default configuration of the `outerLayout` is: + +```javascript +outerLayout: new G6.Layout['concentric']({ + sortBy: 'id' +}); +``` + +
**Description**: The padding value inside each combo. It is not about rendering, only used for force calculation + + +### layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. diff --git a/packages/site/docs/api/graphLayout/comboCombined.zh.md b/packages/site/docs/api/graphLayout/comboCombined.zh.md new file mode 100644 index 0000000000..ce9b201705 --- /dev/null +++ b/packages/site/docs/api/graphLayout/comboCombined.zh.md @@ -0,0 +1,108 @@ +--- +title: Combo 复合布局 ComboCombined +order: 14 +--- + +*V4.6 新增功能。*ComboCombined 支持自由配置 combo 内部元素的布局以及最外层 combo 和节点之间的布局,默认情况下将使用 Concentric 同心圆布局作为内部布局,gForce 力导向布局作为外部布局。能够达到较好的效果以及稳定性。当您指定内部布局时,请使用同步的布局算法,可从以下布局中选择:Circular,Concentric,Grid,Dagre,MDS,Radial,或任何同步的自定义布局。也可以自定义布局作为内部/外部布局。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + groupByTypes: false, // 若希望在带有 combo 的图上,节点、边、combo 的层级符合常规逻辑,需要将 groupByTypes 设置为 false + layout: { + type: 'comboCombined', + center: [ 200, 200 ], // 可选,默认为图的中心 + onLayoutEnd: () => { // 可选 + console.log('combo force layout done'); + } + } +); +``` + +### layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +### layoutCfg.nodeSize + +**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:节点大小(直径)。用于碰撞检测。若不指定,则根据传入的节点的 size 属性计算。若即不指定,节点中也没有 `size`,则默认大小为 `10` + +### layoutCfg.spacing + +**类型**: Number / Function
**默认值**: 0
**是否必须**: false
**示例**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**描述**: `preventNodeOverlap` 或 `preventOverlap` 为 `true` 时生效, 防止重叠时节点/ combo 边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距, 如示例 2 所示 + +### layoutCfg.comboPadding + +**类型**: Number / Function
**默认值**: 10
**是否必须**: false
**示例**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a combo + if (d.id === 'combo1') { + return 100; + } + return 10; +}; +``` + +
**描述**: Combo 内部的 padding 值,不用于渲染,仅用于计算力。推荐设置为与视图上 combo 内部 padding 值相同的值 + +### layoutCfg.outerLayout + +```javascript +outerLayout: new G6.Layout['gForce']({ + ... // 该布局的参数 +}); +``` + +**类型**:Object
**默认值**:GForce 实例
**是否必须**:false
**说明**:最外层的布局算法,默认为 gForce。具体参数详见被使用布局的文档。 +默认情况下 gForce 布局将使用以下参数: + +```javascript +outerLayout: new G6.Layout['gForce']({ + gravity: 1, + factor: 2, + linkDistance: (edge: any, source: any, target: any) => { + const nodeSize = ((source.size?.[0] || 30) + (target.size?.[0] || 30)) / 2; + return Math.min(nodeSize * 1.5, 700); + } +}); +``` + +### layoutCfg.innerLayout + +```javascript +innerLayout: new G6.Layout['grid']({ + ... // 该布局的参数 +}); +``` + +**类型**:Object
**默认值**:Concentric 实例
**是否必须**:false
**说明**:combo 内部的布局算法,需要使用同步的布局算法,默认为 concentric。具体参数详见被使用布局的文档。 +默认情况下 concentric 布局将使用以下参数: + +```javascript +outerLayout: new G6.Layout['concentric']({ + sortBy: 'id' +}); +``` + +### layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 diff --git a/packages/site/docs/api/graphLayout/comboForce.en.md b/packages/site/docs/api/graphLayout/comboForce.en.md new file mode 100644 index 0000000000..41a4ac3bec --- /dev/null +++ b/packages/site/docs/api/graphLayout/comboForce.en.md @@ -0,0 +1,178 @@ +--- +title: ComboForce +order: 13 +--- + +_It is a new feature of V3.5._ Combo Force is designed for the graph with combos based on classical force directed layout algorith. It modifies the forces between nodes according to their combo infomation to achieve a final result with clustering nodes inside each combo and no overlappings. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + groupByTypes: false, // If you want to have a combo graph with reasonable visual levels of nodes, edges, and combo, set groupByTypes to false + layout: { + type: 'comboForce', + center: [ 200, 200 ], // The center of the graph by default + linkDistance: 50, // Edge length + nodeStrength: 30, + edgeStrength: 0.1, + onTick: () => { + console.log('ticking'); + }, + onLayoutEnd: () => { + console.log('combo force layout done'); + } + } +); +``` + +### layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +### layoutCfg.maxIteration + +**Type**: Number
**Example**: 100
**Default**: 100
**Required**: false
**Description**: The max number of the interations + +### layoutCfg.linkDistance + +**Type**: Number / Function
**Default**: 10
**Required**: false
**Description**: The edge length + +### layoutCfg.nodeStrength + +**Type**: Number / Function
**Default**: 30
**Required**: false
**Description**: The strength of node force + +### layoutCfg.edgeStrength + +**Type**: Number / Function
**Default**: 0.2
**Required**: false
**Description**: The strength of edge force + +### layoutCfg.preventOverlap + +**Type**: Number
**Default**: false
**Required**: false
**Description**: Whether to prevent node overlappings and combo overlappings. If it is assign `true`, `preventNodeOverlap` and `preventComboOverlap` will be set to `true`. See the API of `preventNodeOverlap` and `preventComboOverlap` for more detail + +### layoutCfg.preventNodeOverlap + +**Type**: Number
**Default**: true
**Required**: false
**Description**: Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned + +### layoutCfg.preventComboOverlap + +**Type**: Number
**Default**: true
**Required**: false
**Description**: Whether to prevent combo overlappings + +### layoutCfg.collideStrength + +**Type**: Number
**Default**: undefined
**Required**: false
**Description**: The unified strength of force for preventing node overlappings and combo overlappings. The range is [0, 1]. If it is not undefined, the `nodeCollideStrength` and `comboCollideStrength` will be set to the same value + +### layoutCfg.nodeCollideStrength + +**Type**: Number
**Default**: 0.5
**Required**: false
**Description**: The strength of force for preventing node overlappings. The range is [0, 1] + +### layoutCfg.comboCollideStrength + +**Type**: Number
**Default**: 0.5
**Required**: false
**Description**: The strength of force for preventing combo overlappings. The range is [0, 1] + +### layoutCfg.nodeSize + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The diameter of the node. It is used for preventing node overlappings. If `nodeSize` is not assigned, the size property in node data will take effect. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default + +### layoutCfg.nodeSpacing + +**Type**: Number / Function
**Default**: 0
**Required**: false
**Example**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**Description**: Takes effect when `preventNodeOverlap` or `preventOverlap` is `true`. It is the minimum distance between nodes to prevent node overlappings. It can be a function to define different distances for different nodes (example 2) + +
+img + +### layoutCfg.comboSpacing + +**Type**: Number / Function
**Default**: 0
**Required**: false
**Example**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a combo + if (d.id === 'combo1') { + return 100; + } + return 10; +}; +``` + +
**Description**: Takes effect when `preventComboOverlap` or `preventOverlap` is `true`. It is the minimum distance between combos to prevent combo overlappings. It can be a function to define different distances for different combos (example 2) + +### layoutCfg.comboPadding + +**Type**: Number / Function
**Default**: 10
**Required**: false
**Example**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a combo + if (d.id === 'combo1') { + return 100; + } + return 10; +}; +``` + +
**Description**: The padding value inside each combo. It is not about rendering, only used for force calculation + +### layoutCfg.alpha + +**Type**: Number
**Default**: 1
**Required**: false
**Description**: The current alpha of convergence + +### layoutCfg.alphaDecay + +**Type**: Number
**Default**: 0.028
**Required**: false
**Description**: The decay ratio of alpha for convergence. The range is [0, 1]. 0.028 corresponds to 300 iterations + +### layoutCfg.alphaMin + +**Type**: Number
**Default**: 0.001
**Required**: false
**Description**: The threshold to stop the iteration + +### layoutCfg.onTick + +**Type**: Function
**Default**: {}
**Required**: false
**Description**: The callback function of each iteration + +### layoutCfg.onLayoutEnd + +**Type**: Function
**Default**: {}
**Required**: false
**Description**: The callback function after layout + +### layoutCfg.gravity + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The gravity, which will affect the compactness of the layout + +### layoutCfg.comboGravity + +**Type**: Number
**Default**: 30
**Required**: false
**Description**: The gravity of each combo, which will affect the compactness of each combo + +### layoutCfg.optimizeRangeFactor + +**Type**: Number
**Default**: 1
**Required**: false
**Description**: When the distance between two nodes is larger than `optimizeRangeFactor * width`, the forces between them will not be calculated any more. A proper value for `optimizeRangeFactor` will lead to less calculation to optimize the performance of the layout + +### layoutCfg.depthAttractiveForceScale + +**Type**: Number
**Default**: 0.5
**Required**: false
**Description**: The scale for adjusting the strength of attractive force between nodes with different depths. The range is [0, 1]. Lager the depth difference, smaller the attractive force strength + +### layoutCfg.depthRepulsiveForceScale + +**Type**: Number
**Default**: 2
**Required**: false
**Description**: The scale for adjusting the strength of repulsive force between nodes with different depths. The range is [1, Infinity]. Lager the depth difference, larger the attractive force strength + +### layoutCfg.velocityDecay + +**Type**: Number
**Default**: 0.6
**Required**: false
**Description**: The decay speed of the moving velocity of nodes + +### layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. diff --git a/packages/site/docs/api/graphLayout/comboForce.zh.md b/packages/site/docs/api/graphLayout/comboForce.zh.md new file mode 100644 index 0000000000..7ea2d52355 --- /dev/null +++ b/packages/site/docs/api/graphLayout/comboForce.zh.md @@ -0,0 +1,177 @@ +--- +title: Combo 力导向 ComboForce +order: 13 +--- + +*V3.5 新增功能。*ComboForce 是基于力导向的专用于带有 combo 的图的布局算法。通过自研改造经典力导向算法,将根据节点的 combo 信息,施加不同的力以达到同 combo 节点尽可能聚集,不同 combo 之间尽可能无重叠的布局。 + +⚠️ 注意:G6 3.1 版本中实例化 Graph 时,新增了 `nodeStateStyles` 及  `edgeStateStyles` 两个配置项,删除了 `nodeStyle` 和 `edgeStyle` ,使用 3.1 以下版本的同学,只需要将  `nodeStyle` 改成 `nodeStateStyles` ,将  `edgeStyle` 改成  `edgeStateStyles` ,配置内容保持不变。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + groupByTypes: false, // 若希望在带有 combo 的图上,节点、边、combo 的层级符合常规逻辑,需要将 groupByTypes 设置为 false + layout: { + type: 'comboForce', + center: [ 200, 200 ], // 可选,默认为图的中心 + linkDistance: 50, // 可选,边长 + nodeStrength: 30, // 可选 + edgeStrength: 0.1, // 可选 + onTick: () => { // 可选 + console.log('ticking'); + }, + onLayoutEnd: () => { // 可选 + console.log('combo force layout done'); + } + } +); +``` + +### layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +### layoutCfg.maxIteration + +**类型**: Number
**默认值**:100
**是否必须**:false
**说明**:最大迭代次数 + +### layoutCfg.linkDistance + +**类型**: Number / Function
**默认值**:10
**是否必须**:false
**说明**:边长度 + +### layoutCfg.nodeStrength + +**类型**: Number / Function
**默认值**:30
**是否必须**:false
**说明**:节点作用力 + +### layoutCfg.edgeStrength + +**类型**:Number / Function
**默认值**:0.2
**是否必须**:false
**说明**:边的作用力 + +### layoutCfg.preventOverlap + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否防止节点之间以及 combo 之间的重叠,若开启,则 `preventNodeOverlap` 与 `preventComboOverlap` 将均被开启。详见 `preventNodeOverlap` 与 `preventComboOverlap` 介绍 + +### layoutCfg.preventNodeOverlap + +**类型**:Boolean
**默认值**:true
**是否必须**:false
**说明**:是否防止节点之间的重叠。必须配合下面属性 `nodeSize` 或节点数据中的 `size` 属性,只有在数据中设置了 `size` 或在该布局中配置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测 + +### layoutCfg.preventComboOverlap + +**类型**:Boolean
**默认值**:true
**是否必须**:false
**说明**:是否防止 combo 之间的重叠 + +### layoutCfg.collideStrength + +**类型**:Number
**默认值**:undefined
**是否必须**:false
**说明**:统一设置防止节点之间以及 combo 之间重叠的力强度,范围 [0, 1]。若 `collideStrength` 不为 `undefined`,则 `nodeCollideStrength` 与 `comboCollideStrength` 将均被设置为统一的值 + +### layoutCfg.nodeCollideStrength + +**类型**:Number
**默认值**:0.5
**是否必须**:false
**说明**:设置防止节点之间重叠的力强度,范围 [0, 1] + +### layoutCfg.comboCollideStrength + +**类型**:Number
**默认值**:0.5
**是否必须**:false
**说明**:防止 combo 之间重叠的力强度,范围 [0, 1] + +### layoutCfg.nodeSize + +**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:节点大小(直径)。用于碰撞检测。若不指定,则根据传入的节点的 size 属性计算。若即不指定,节点中也没有 `size`,则默认大小为 `10` + +### layoutCfg.nodeSpacing + +**类型**: Number / Function
**默认值**: 0
**是否必须**: false
**示例**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**描述**: `preventNodeOverlap` 或 `preventOverlap` 为 `true` 时生效, 防止重叠时节点边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距, 如示例 2 所示 + +### layoutCfg.comboSpacing + +**类型**: Number / Function
**默认值**: 0
**是否必须**: false
**示例**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a combo + if (d.id === 'combo1') { + return 100; + } + return 10; +}; +``` + +
**描述**: `preventComboOverlap` 或 `preventOverlap` 为 `true` 时生效, 防止重叠时 combo 边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距, 如示例 2 所示 + +### layoutCfg.comboPadding + +**类型**: Number / Function
**默认值**: 10
**是否必须**: false
**示例**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a combo + if (d.id === 'combo1') { + return 100; + } + return 10; +}; +``` + +
**描述**: Combo 内部的 padding 值,不用于渲染,仅用于计算力。推荐设置为与视图上 combo 内部 padding 值相同的值 + +### layoutCfg.alpha + +**类型**:Number
**默认值**:1
**是否必须**:false
**说明**:当前的迭代收敛阈值 + +### layoutCfg.alphaDecay + +**类型**:Number
**默认值**:0.028
**是否必须**:false
**说明**:迭代阈值的衰减率。范围 [0, 1]。0.028 对应迭代数为 300 + +### layoutCfg.alphaMin + +**类型**:Number
**默认值**:0.001
**是否必须**:false
**说明**:停止迭代的阈值 + +### layoutCfg.onTick + +**类型**:Function
**默认值**:{}
**是否必须**:false
**说明**:每一次迭代的回调函数 + +### layoutCfg.onLayoutEnd + +**类型**:Function
**默认值**:{}
**是否必须**:false
**说明**:布局完成后的回调函数 + +### layoutCfg.gravity + +**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:重力的大小,影响布局的紧凑程度 + +### layoutCfg.comboGravity + +**类型**: Number
**默认值**:30
**是否必须**:false
**说明**:每个 combo 内部的重力大小,影响聚类的紧凑程度 + +### layoutCfg.optimizeRangeFactor + +**类型**: Number
**默认值**:1
**是否必须**:false
**说明**:优化计算性能,两节点间距超过 `optimizeRangeFactor * width` 则不再计算斥力和重叠斥力。通过合理设置该参数可以较少计算量 + +### layoutCfg.depthAttractiveForceScale + +**类型**: Number
**默认值**:0.5
**是否必须**:false
**说明**:根据边两端节点层级差距的调整引力的系数的因子,取值范围 [0, 1]。层级差距越大,引力越小 + +### layoutCfg.depthRepulsiveForceScale + +**类型**: Number
**默认值**:2
**是否必须**:false
**说明**:根据边两端节点层级差距的调整斥力系数的因子,取值范围 [1, Infinity]。层级差距越大,斥力越大 + +### layoutCfg.velocityDecay + +**类型**: Number
**默认值**:0.6
**是否必须**:false
**说明**:节点运动速度衰减参数 + +### layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 diff --git a/packages/site/docs/api/graphLayout/concentric.en.md b/packages/site/docs/api/graphLayout/concentric.en.md new file mode 100644 index 0000000000..2ae520d8d9 --- /dev/null +++ b/packages/site/docs/api/graphLayout/concentric.en.md @@ -0,0 +1,75 @@ +--- +title: Concentric +order: 6 +--- + +Concentric arranges the nodes on several concentric circles. By tuning the parameters, users could order the nodes according to some property of node data, degree by default. Larger the value, more center the node will be placed. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'concentric', + center: [200, 200], // The center of the graph by default + linkDistance: 50, // The edge length + preventOverlap: true, // nodeSize or size in data is required for preventOverlap: true + nodeSize: 30, + sweep: 10, + equidistant: false, + startAngle: 0, + clockwise: false, + maxLevelDiff: 10, + sortBy: 'degree', + workerEnabled: true, // Whether to activate web-worker + }, +}); +``` + +## layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +## layoutCfg.preventOverlap + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned. If the size in node data does not exist either, `nodeSize` is assigned to 30 by default + +## layoutCfg.nodeSize + +**Type**: Number
**Default**: 30
**Required**: false
**Description**: The diameter of the node. It is used for preventing node overlappings + +## layoutCfg.minNodeSpacing + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The minimum separation between adjacent circles + +## layoutCfg.sweep + +**Type**: Number
**Default**: undefined
**Required**: false
**Description**: How many radians should be between the first and last node (defaults to full circle). If it is undefined, 2 _ Math.PI _ (1 - 1 / |level.nodes|) will be used, where level.nodes is nodes set of each level, |level.nodes| is the number of nodes of the level + +## layoutCfg.equidistant + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether levels have an equal radial distance between them, may cause bounding box overflow + +## layoutCfg.startAngle + +**Type**: Number
**Default**: 3 / 2 \* Math.PI
**Required**: false
**Description**: Where nodes start in radians + +## layoutCfg.clockwise + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Place the nodes in clockwise or not + +## layoutCfg.maxLevelDiff + +**Type**: Number
**默认值:**undefined
**Required**: false
**Description**: The sum of concentric values in each level. If it is undefined, maxValue / 4 will take place, where maxValue is the max value of ordering properties. For example, if `sortBy` is `'degree'`, maxValue is the max degree value of all the nodes + +## layoutCfg.sortBy + +**Type**: String
**Default**: undefined
**Required**: false
**Description**: Order the nodes according to this parameter. It is the property's name of node. The node with higher value will be placed to the center. If it is undefined, the algorithm will order the nodes by their degree + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. diff --git a/packages/site/docs/api/graphLayout/concentric.zh.md b/packages/site/docs/api/graphLayout/concentric.zh.md new file mode 100644 index 0000000000..742817362f --- /dev/null +++ b/packages/site/docs/api/graphLayout/concentric.zh.md @@ -0,0 +1,75 @@ +--- +title: 同心圆 Concentric +order: 6 +--- + +Concentric 布局为同心圆布局,用户可以指定节点某个属性为排序依据(默认为节点度数 degree),该属性值越高,则该节点布局后的位置中心。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'concentric', + center: [ 200, 200 ], // 可选, + linkDistance: 50, // 可选,边长 + preventOverlap: true, // 可选,必须配合 nodeSize + nodeSize: 30, // 可选 + sweep: 10, // 可选 + equidistant: false, // 可选 + startAngle: 0, // 可选 + clockwise: false, // 可选 + maxLevelDiff: 10, // 可选 + sortBy: 'degree' // 可选 + workerEnabled: true // 可选,开启 web-worker + } +}); +``` + +## layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +## layoutCfg.preventOverlap + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否防止重叠,必须配合下面属性 `nodeSize`,只有设置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测 + +## layoutCfg.nodeSize + +**类型**: Number
**默认值**:30
**是否必须**:false
**说明**:节点大小(直径)。用于防止节点重叠时的碰撞检测 + +## layoutCfg.minNodeSpacing + +**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:环与环之间最小间距,用于调整半径 + +## layoutCfg.sweep + +**类型**: Number
**默认值**:undefined
**是否必须**:false
**说明**:第一个节点与最后一个节点之间的弧度差。若为 undefined ,则将会被设置为  2 _ Math.PI _ (1 - 1 / |level.nodes|) ,其中 level.nodes 为该算法计算出的每一层的节点,|level.nodes| 代表该层节点数量 + +## layoutCfg.equidistant + +**类型**: Boolean
**默认值**:false
**是否必须**:false
**说明**:环与环之间的距离是否相等 + +## layoutCfg.startAngle + +**类型**: Number
**默认值**:3 / 2 \* Math.PI
**是否必须**:false
**说明**:开始方式节点的弧度 + +## layoutCfg.clockwise + +**类型**: Boolean
**默认值**:false
**是否必须**:false
**说明**:是否按照顺时针排列 + +## layoutCfg.maxLevelDiff + +**类型**: Number
**默认值:**undefined
**是否必须**:false
**说明**:每一层同心值的求和。若为 undefined,则将会被设置为 maxValue / 4 ,其中 maxValue 为最大的排序依据的属性值。例如,若 `sortBy` 为 `'degree'`,则 maxValue 为所有节点中度数最大的节点的度数 + +## layoutCfg.sortBy + +**类型**: String
**默认值**:undefined
**是否必须**:false
**说明**:指定排序的依据(节点属性名),数值越高则该节点被放置得越中心。若为 undefined,则会计算节点的度数,度数越高,节点将被放置得越中心 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 diff --git a/packages/site/docs/api/graphLayout/dagre.en.md b/packages/site/docs/api/graphLayout/dagre.en.md new file mode 100644 index 0000000000..aac4f488f8 --- /dev/null +++ b/packages/site/docs/api/graphLayout/dagre.en.md @@ -0,0 +1,135 @@ +--- +title: Dagre +order: 7 +--- + +Dagre is an hierarchical layout. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'dagre', + rankdir: 'LR', // The center of the graph by default + align: 'DL', + nodesep: 20, + ranksep: 50, + controlPoints: true, + }, +}); +``` + +## layoutCfg.begin + +_Supported by G6 v4.5.1_ + +**Type**: Array
**Default**: undefined
**Required**: false
**Description**: The position for the left-top of the layout. + +## layoutCfg.rankdir + +**Type**: String
**Options**: 'TB' | 'BT' | 'LR' | 'RL'
**Default**: 'TB'
**Required**: false
**Description**: The layout direction. T:top; B:bottom; L:left; R:right. + +- 'TB':Layout the graph from the top to the bottom; +- 'BT':Layout the graph from the bottom to the top; +- 'LR':Layout the graph from the top left the right; +- 'RL':Layout the graph from the top right the left. + +
+ +## layoutCfg.align + +**Type**: String
**Options**: 'UL' | 'UR' | 'DL' | 'DR' | undefined
**Default**: 'UL'
**Required**: false
**Description**: The alignment of the nodes. U: upper; D: down; L: left; R: right + +- 'UL': aligns the nodes to the upper left; +- 'UR': aligns the nodes to the upper right; +- 'DL': aligns the nodes to the down left; +- 'DR': aligns the nodes to the upper right; +- undefined: default value, align to the center. + +## layoutCfg.nodesep + +**Type**: Number
**Default**: 50
**Required**: false
**Description**: The separation between nodes with unit px. When `rankdir` is `'TB'` or `'BT'`, `nodesep` represents the horizontal separations between nodes; When `rankdir` is `'LR'` or `'RL'`, `nodesep` represents the vertical separations between nodes + +## layoutCfg.ranksep + +**Type**: Number
**Default**: 50
**Required**: false
**Description**: The separations between adjacent levels with unit px. When `rankdir` is `'TB'` or `'BT'`, `ranksep` represents the vertical separations between adjacent levels; when `rankdir` is `'LR'` or `'RL'`, `rankdir` represents the horizontal separations between adjacent levels + +## layoutCfg.nodesepFunc + +**Type**: Function
**Default**: undefined
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 100; + return 10; +}; +``` + +
**Required**: false
**Description**: The function for node separation with unit px. You can adjust the separations between different node pairs by using this function instead of `nodesep`. When `rankdir` is `'LR'` or `'RL'`, `nodesep` represents the vertical separations between nodes. The priority of `` is higher than `nodesep`, which means if `nodesepFunc` is assigned, the `nodesep` will not take effect + +## layoutCfg.ranksepFunc + +**Type**: Function
**Default**: undefined
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 100; + return 10; +}; +``` + +
**Required**: false
**Description**: The function for level separation with unit px. You can adjust the separations between different adjacent levels by using this function instead of `ranksep`. When `rankdir` is `'TB'` or `'BT'`, `ranksep` represents the vertical separations between adjacent levels; when `rankdir` is `'LR'` or `'RL'`, `rankdir` represents the horizontal separations between adjacent levels. The priority of `ranksepFunc` is higher than `ranksep`, which means if `ranksepFunc` is assigned, the `ranksep` will not take effect + +## layoutCfg.controlPoints + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to keep the control points of layout + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. + +## layoutCfg.sortByCombo + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to sort the nodes in a level according to the `comboId` in their data. Enable `sortByCombo` to avoid combo overlappings + +## layoutCfg.nodeOrder + +_Supported by G6 v4.5.0_ + +**Type**: string[]
**Default** undefined
**Required**: false
**Description**: The refered order array for the nodes in the same layer, stores the id values of each node. If it is not specified, the order of the nodes will be decided by the dagre algorithm. + + +## layoutCfg.preset + +_Supported by G6 v4.5.0_ + +**Type**: + + +```javascript +{ + nodes: { + x: number, // position + y: number, // position + layer?: number, // specify the layer for the node, 0-indexed + _order?: number // if the preset comes from last dagre layout, the _order will be generated to state the order of the nodes in one layer + }[] +} +``` + + +
**Default** undefined
**Required**: false
**Description**: The refered node positions and other infomations for layout computing. It is usually used to keep the consistancy for data changing, where you could specify it with the result data from last layout. + + +## Specify the Layer for Node + +_Supported by G6 v4.5.0_ + +Configurate `layer` (0-indexed) in the node data to specify the layout layer for it. Notice that the `layer` SHOULD NOT violate DAG's properties (e.g. DO NOT assign a layer value for a target node greater or equal to cresponding source node.). The layout will be failed otherwise. diff --git a/packages/site/docs/api/graphLayout/dagre.zh.md b/packages/site/docs/api/graphLayout/dagre.zh.md new file mode 100644 index 0000000000..392e3c89b2 --- /dev/null +++ b/packages/site/docs/api/graphLayout/dagre.zh.md @@ -0,0 +1,131 @@ +--- +title: 层次 Dagre +order: 7 +--- + +Dagre 是一种层次布局。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'dagre', + rankdir: 'LR', // 可选,默认为图的中心 + align: 'DL', // 可选 + nodesep: 20, // 可选 + ranksep: 50, // 可选 + controlPoints: true, // 可选 + }, +}); +``` + +## layoutCfg.begin + +_自 G6 v4.5.1 起支持。_ + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:undefined
**是否必须**:false
**说明**:布局的左上角对齐位置。 + +## layoutCfg.rankdir + +**类型**: String
**可选值**:'TB' | 'BT' | 'LR' | 'RL'
**默认值**:'TB'
**是否必须**:false
**说明**:布局的方向。T:top(上);B:bottom(下);L:left(左);R:right(右)。 + +- 'TB':从上至下布局; +- 'BT':从下至上布局; +- 'LR':从左至右布局; +- 'RL':从右至左布局。 + +## layoutCfg.align + +**类型**: String
**可选值**:'UL' | 'UR' | 'DL' | 'DR' | undefined
**默认值**:'UL'
**是否必须**:false
**说明**:节点对齐方式。U:upper(上);D:down(下);L:left(左);R:right(右) + +- 'UL':对齐到左上角; +- 'UR':对齐到右上角; +- 'DL':对齐到左下角; +- 'DR':对齐到右下角; +- undefined:默认,中间对齐。 + +## layoutCfg.nodesep + +**类型**: Number
**默认值**:50
**是否必须**:false
**说明**:节点间距(px)。在`rankdir` 为 `'TB'` 或 `'BT'` 时是节点的水平间距;在`rankdir` 为 `'LR'` 或 `'RL'` 时代表节点的竖直方向间距 + +## layoutCfg.ranksep + +**类型**: Number
**默认值**:50
**是否必须**:false
**说明**:层间距(px)。在`rankdir` 为 `'TB'` 或 `'BT'` 时是竖直方向相邻层间距;在`rankdir` 为 `'LR'` 或 `'RL'` 时代表水平方向相邻层间距 + +## layoutCfg.nodesepFunc + +**类型**: Function
**默认值**:undefined
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 100; + return 10; +}; +``` + +
**是否必须**:false
**说明**:节点间距(px)的回调函数,通过该参数可以对不同节点设置不同的节点间距。在`rankdir` 为 'TB' 或 'BT' 时是节点的水平间距;在`rankdir` 为 'LR' 或 'RL' 时代表节点的竖直方向间距。优先级高于 `nodesep`,即若设置了 `nodesepFunc`,则 `nodesep` 不生效 + +## layoutCfg.ranksepFunc + +**类型**: Function
**默认值**:undefined
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 100; + return 10; +}; +``` + +
**是否必须**:false
**说明**:层间距(px)的回调函数,通过该参数可以对不同节点设置不同的层间距。在`rankdir` 为 'TB' 或 'BT' 时是竖直方向相邻层间距;在`rankdir` 为 'LR' 或 'RL' 时代表水平方向相邻层间距。优先级高于 `ranksep`,即若设置了 `ranksepFunc`,则 `ranksep` 不生效 + +## layoutCfg.controlPoints + +**类型**: Boolean
**默认值**:false
**是否必须**:false
**说明**:是否保留布局连线的控制点 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 + +## layoutCfg.sortByCombo + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 同一层节点是否根据每个节点数据中的 `comboId` 进行排序,以防止 combo 重叠 + +## layoutCfg.nodeOrder + +_自 G6 v4.5.0 起支持。_ + +**类型**: string[]
**默认值**: undefined
**是否必须**: false
**说明**: 同层节点顺序的参考数组,存放节点 id 值。若未指定,则将按照 dagre 本身机制排列同层节点顺序。 + + +## layoutCfg.preset + +_自 G6 v4.5.0 起支持。_ + +**类型**: + +```javascript +{ + nodes: { + x: number, // 位置 + y: number, // 位置 + layer?: number, // 指定层级 + _order?: number // 若为上一次 dagre 布局的输出,则有该字段,代表同层节点的顺序 + }[] +} +``` + +
**默认值**: undefined
**是否必须**: false
**说明**: 布局计算时参考的节点位置,一般用于切换数据时保证重新布局的连续性,可使用上一次布局的结果数据作为输入。 + + +## 指定节点层级 + +_自 G6 v4.5.0 起支持。_ + +在节点数据中配置 `layer` 字段(从 0 开始计数),可以为节点指定层级。注意,`layer` 的指定不能违背图结构与层次布局原则,即每一条边的起点的 `layer` 一定小于终点的 `layer` 值。若违背该规则,则可能导致布局失败。 diff --git a/packages/site/docs/api/graphLayout/force.en.md b/packages/site/docs/api/graphLayout/force.en.md new file mode 100644 index 0000000000..28829e57c2 --- /dev/null +++ b/packages/site/docs/api/graphLayout/force.en.md @@ -0,0 +1,134 @@ +--- +title: Force +order: 3 +--- + +Force is the classical force-dicrected layout algorithm, which corresponds to force-directed layout in d3.js. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'force', + center: [200, 200], // The center of the graph by default + linkDistance: 50, // Edge length + nodeStrength: 30, + edgeStrength: 0.1, + collideStrength: 0.8, + nodeSize: 30, + alpha: 0.3, + alphaDecay: 0.028, + alphaMin: 0.01, + forceSimulation: null, + onTick: () => { + console.log('ticking'); + }, + onLayoutEnd: () => { + console.log('force layout done'); + }, + }, +}); +``` + +If you want to fix the positions for some nodes during calculation, assign `fx` and `fy` for the nodes as fixing positions. [Demo for fixing the dragged node with force layout](/en/examples/net/forceDirected#basicForceDirectedDragFix). + +## layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +## layoutCfg.linkDistance + +**Type**: Number / Function
**Default**: 50
**Required**: false
**Description**: The edge length + +## layoutCfg.nodeStrength + +**Type**: Number / Function
**Default**: null
**Required**: false
**Description**: The strength of node force. Positive value means attractive force, negative value means repulsive force + +## layoutCfg.edgeStrength + +**Type**: Number / Function
**Default**: null
**Required**: false
**Description**: The strength of edge force, ranges from 0 to 1. Calculated according to the degree of nodes by default + +## layoutCfg.preventOverlap + +**Type**: Number
**Default**: false
**Required**: false
**Description**: Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned + +## layoutCfg.collideStrength + +**Type**: Number
**Default**: 1
**Required**: false
**Description**: The strength of force for preventing node overlappings. The range is [0, 1] + +## layoutCfg.nodeSize + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The diameter of the node. It is used for preventing node overlappings. If `nodeSize` is not assigned, the size property in node data will take effect. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default + +## layoutCfg.nodeSpacing + +**Type**: Number / Function
**Default**: 0
**Required**: false
The minimum space between two nodes when `preventOverlap` is true
**Example**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**Description**: Takes effect when `preventOverlap` is `true`. It is the minimum distance between nodes to prevent node overlappings. It can be a function to define different distances for different nodes (example 2) + +## layoutCfg.alpha + +**Type**: Number
**Default**: 0.3
**Required**: false
**Description**: The current alpha of convergence + +## layoutCfg.alphaDecay + +**Type**: Number
**Default**: 0.028
**Required**: false
**Description**: The decay ratio of alpha for convergence. The range is [0, 1]. 0.028 corresponds to 300 iterations + +## layoutCfg.alphaMin + +**Type**: Number
**Default**: 0.001
**Required**: false
**Description**: The threshold to stop the iteration + +## layoutCfg.clustering + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether run the force layout with clustering + +## layoutCfg.clusterNodeStrength + +**Type**: Number
**Default**: -1
**Required**: false
**Description**: The force between nodes. It will be repulsive force while it is negative + +## layoutCfg.clusterEdgeStrength + +**Type**: Number
**Default**: 0.1
**Required**: false
**Description**: The force along the edge + +## layoutCfg.clusterEdgeDistance + +**Type**: Number
**Default**: 100
**Required**: false
**Description**: The edge length between the clusters + +## layoutCfg.clusterNodeSize + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The node size(diameter) for clustering + +## layoutCfg.clusterFociStrength + +**Type**: Number
**Default**: 0.8
**Required**: false
**Description**: The force for the clustering foci + +## layoutCfg.forceSimulation + +**Type**: Object
**Default**: null
**Required**: false
**Description**: Customed force simulation. If it is not assigned, the force simulation of d3.js will take effect + +## layoutCfg.onTick + +**Type**: Function
**Default**: {}
**Required**: false
**Description**: The callback function of each iteration + +## layoutCfg.onLayoutEnd + +**Type**: Function
**Default**: {}
**Required**: false
**Description**: The callback function after layout + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. diff --git a/packages/site/docs/api/graphLayout/force.zh.md b/packages/site/docs/api/graphLayout/force.zh.md new file mode 100644 index 0000000000..2fab96dbe5 --- /dev/null +++ b/packages/site/docs/api/graphLayout/force.zh.md @@ -0,0 +1,135 @@ +--- +title: Force 力导向 +order: 3 +--- + +Force 布局经典的力导向布局方法,与 d3 的力导向布局方法相对应。其属性也与 d3.js 的力导布局参数相对应。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'force', + center: [ 200, 200 ], // 可选,默认为图的中心 + linkDistance: 50, // 可选,边长 + nodeStrength: 30, // 可选 + edgeStrength: 0.1, // 可选 + collideStrength: 0.8, // 可选 + nodeSize: 30, // 可选 + alpha: 0.3, // 可选 + alphaDecay: 0.028, // 可选 + alphaMin: 0.01, // 可选 + forceSimulation: null, // 可选 + onTick: () => { // 可选 + console.log('ticking'); + }, + onLayoutEnd: () => { // 可选 + console.log('force layout done'); + } + } +); +``` + +当你希望固定某个节点的位置,不受力的影响时,可以在该节点数据中配置 `fx` 与 `fy` 作为固定的坐标。[Force 布局固定被拖拽节点位置的 Demo](/zh/examples/net/forceDirected#basicForceDirectedDragFix)。 + +## layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +## layoutCfg.linkDistance + +**类型**: Number / Function
**默认值**:50
**是否必须**:false
**说明**:边长度 + +## layoutCfg.nodeStrength + +**类型**: Number / Function
**默认值**:null
**是否必须**:false
**说明**:节点作用力,正数代表节点之间的引力作用,负数代表节点之间的斥力作用 + +## layoutCfg.edgeStrength + +**类型**:Number / Function
**默认值**:null
**是否必须**:false
**说明**:边的作用力,范围是 0 到 1,默认根据节点的出入度自适应 + +## layoutCfg.preventOverlap + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否防止重叠,必须配合下面属性 `nodeSize` 或节点数据中的 `size` 属性,只有在数据中设置了 `size` 或在该布局中配置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测 + +## layoutCfg.collideStrength + +**类型**:Number
**默认值**:1
**是否必须**:false
**说明**:防止重叠的力强度,范围 [0, 1] + +## layoutCfg.nodeSize + +**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:节点大小(直径)。用于碰撞检测。若不指定,则根据传入的节点的 size 属性计算。若即不指定,节点中也没有 `size`,则默认大小为 `10` + +## layoutCfg.nodeSpacing + +**类型**: Number / Function
**默认值**: 0
**是否必须**: false

**示例**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
+**描述**: `preventOverlap` 为 `true` 时生效, 防止重叠时节点边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距, 如示例 2 所示 + +## layoutCfg.alpha + +**类型**:Number
**默认值**:0.3
**是否必须**:false
**说明**:当前的迭代收敛阈值 + +## layoutCfg.alphaDecay + +**类型**:Number
**默认值**:0.028
**是否必须**:false
**说明**:迭代阈值的衰减率。范围 [0, 1]。0.028 对应迭代数为 300 + +## layoutCfg.alphaMin + +**类型**:Number
**默认值**:0.001
**是否必须**:false
**说明**:停止迭代的阈值 + +## layoutCfg.clustering + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否按照聚类信息布局 + +## layoutCfg.clusterNodeStrength + +**类型**:Number
**默认值**:-1
**是否必须**:false
**说明**:聚类节点作用力。负数代表斥力 + +## layoutCfg.clusterEdgeStrength + +**类型**:Number
**默认值**:0.1
**是否必须**:false
**说明**:聚类边作用力 + +## layoutCfg.clusterEdgeDistance + +**类型**:Number
**默认值**:100
**是否必须**:false
**说明**:聚类边长度 + +## layoutCfg.clusterNodeSize + +**类型**:Number
**默认值**:10
**是否必须**:false
**说明**:聚类节点大小 / 直径,直径越大,越分散 + +## layoutCfg.clusterFociStrength + +**类型**:Number
**默认值**:0.8
**是否必须**:false
**说明**:用于 foci 的力 + +## layoutCfg.forceSimulation + +**类型**:Object
**默认值**:null
**是否必须**:false
**说明**:自定义 force 方法,若不指定,则使用 d3.js 的方法 + +## layoutCfg.onTick + +**类型**:Function
**默认值**:{}
**是否必须**:false
**说明**:每一次迭代的回调函数 + +## layoutCfg.onLayoutEnd + +**类型**:Function
**默认值**:{}
**是否必须**:false
**说明**:布局完成后的回调函数 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 diff --git a/packages/site/docs/api/graphLayout/force2.en.md b/packages/site/docs/api/graphLayout/force2.en.md new file mode 100644 index 0000000000..fe795df618 --- /dev/null +++ b/packages/site/docs/api/graphLayout/force2.en.md @@ -0,0 +1,196 @@ +--- +title: Force2 +order: 2 +--- + +Force2 implements the force-directed layout algorithm by G6 4.7.0. It comes from graphin-force, supports assign different masses and center gravities for different nodes freedomly. Comparing to graphin-force, the performance is improved greatly. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'force2', + center: [200, 200], // The center of the graph by default + linkDistance: 1, + nodeStrength: 1000, + edgeStrength: 200, + nodeSize: 30, + onTick: () => { + console.log('ticking'); + }, + onLayoutEnd: () => { + console.log('force layout done'); + }, + workerEnabled: true, // Whether to activate web-worker + ... // more options are shown below + }, +}); +``` + +If you want to fix the positions for some nodes during calculation, assign `fx` and `fy` for the nodes as fixing positions. [Demo for fixing node](/en/examples/net/forceDirected#gForceFix). + +## layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +## layoutCfg.animate + +**Type**: Boolean
**示例**:false
**Default**: :false
**Default**: false
**Description**: Whether refresh the node positions on the canvas each iteration. If it is `true`, the nodes on the canvas will looks like animating with forces + +## layoutCfg.linkDistance + +**Type**: Number / Function
**Default**: 1
**Required**: false
**Description**: The edge length + +## layoutCfg.nodeStrength + +**Type**: Number / Function
**Default**: 1000
**Required**: false
**Description**: The strength of node force. Positive value means repulsive force, negative value means attractive force (it is different from 'force') + +## layoutCfg.edgeStrength + +**Type**: Number / Function
**Default**: 200
**Required**: false
**Description**: The strength of edge force. Calculated according to the degree of nodes by default + +## layoutCfg.preventOverlap + +**Type**: Number
**Default**: false
**Required**: false
**Description**: Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned + +## layoutCfg.nodeSize + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The diameter of the node. It is used for preventing node overlappings. If `nodeSize` is not assigned, the size property in node data will take effect. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default + +## layoutCfg.nodeSpacing + +**Type**: Number / Function
**Default**: 0
**Required**: false
**Example**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**Description**: Takes effect when `preventOverlap` is `true`. It is the minimum distance between nodes to prevent node overlappings. It can be a function to define different distances for different nodes (example 2) + +## layoutCfg.minMovement + +**Type**: Number
**Default**: 0.5
**Required**: false
**Description**: When the average/minimum/maximum (according to `distanceThresholdMode`) movement of nodes in one iteration is smaller than `minMovement`, terminate the layout + +## layoutCfg.distanceThresholdMode + +**Type**: 'mean' | 'max' | 'min'
**Default**: 'mean'
**Default**: false
**Description**: The condition to judge with `minMovement`, `'mean'` means the layout stops while the nodes' average movement is smaller than `minMovement`, `'max'` / `'min'` means the layout stops while the nodes' maximum/minimum movement is smaller than `minMovement`. `'mean'` by default + +## layoutCfg.maxIteration + +**Type**: Number
**Default**: 1000
**Required**: false
**Description**: The max number of iterations. If the average movement do not reach `minMovement` but the iteration number is over `maxIteration`, terminate the layout + +## layoutCfg.damping + +**Type**: Number
**Default**: 0.9
**Required**: false
**Description**: Range [0, 1], affect the speed of decreasing node moving speed. Large the number, slower the decreasing + +## layoutCfg.interval + +**Type**: Number
**Default**: 0.02
**Default**: false
**Description**: Controls the speed of the nodes' movement in each iteration + +## layoutCfg.maxSpeed + +**Type**: Number
**Default**: 1000
**Required**: false
**Description**: The max speed in each iteration + +## layoutCfg.factor + +**Type**: Number
**Default**: 1
**Default**: false
**Description**: Coefficient for the repulsive force. Larger the number, larger the repulsive force + +## layoutCfg.coulombDisScale + +**Type**: Number
**Default**: 0.005
**Required**: false
**Description**: A parameter for repulsive force between nodes. Large the number, larger the repulsion + +## layoutCfg.getMass + +**Type**: Function
**Default**: undefined
**Required**: false
**Description**: It is a callback returns the mass of each node. If it is not assigned, the degree of each node will take effect. The usage is similar to `nodeSpacing` + +## layoutCfg.getCenter + +**Type**: Function
**Default**: undefined
**Required**: false
**Description**: It is a callback returns gravity center and the gravity strength for each node
**Example** + +```javascript +(d, degree) => { + // d is a node, degree is the degree of the node + if (d.clusterId === 'c1') return [100, 100, 10]; // x, y, strength + if (degree === 0) return [250, 250, 15]; + return [180, 180, 5]; // x, y, strength +}; +``` + +## layoutCfg.gravity + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The gravity strength to the `center` for all the nodes. Larger the number, more compact the nodes + + +## layoutCfg.centripetalOptions + +**Type**: CentripetalOptions (refers to below)
**Default**: refers to below
**Default**: false
**Description**: Configurations for the center forces, including the center coordinates and the force strengths for leaf nodes, discrete nodes, and other nodes + +Type `CentripetalOptions`: + +| Parameter | Type | Example | Default | Description | +| --- | --- | --- | --- | --- | +| single | number / Function | 2 | 2, | the center force strength for discrete nodes (with 0 degree) | +| leaf | number / Function | 2 | 2 | the center force strength for leaf nodes (with 1 degree) | +| others | number / Function | 1 | 1 | the center force strength for other nodes beside leaf and discrete nodes | +| center | Function | (node, nodes, edges) => ({ x: 10, y: 10 }) | center of the graph | the center force's coordinate. You can return different values for different nodes | + +Example for `centripetalOptions`: + +``` +centripetalOptions: { + // single, leaf, and others support function configuration, the parameters are the current node data, all the nodes' data, all the edges' data + single: (node, nodes, edges) => node.field1 || 1, + leaf: (node, nodes, edges) => node.field2 || 1, + others: (node, nodes, edges) => node.field3|| 1, + // the parameters are current node data, all the nodes' data, all the edges' data, width of the graph, height of the graph + center: (node, nodes, edges, width, height) => { + if (node.field4) return { x: width / 2, y: height / 2 }; + if (node.field5) return { x: node.field6, y: node.field7 }; + // ... + } +} +``` + +## layoutCfg.leafCluster + +**Type**: Boolean
**Default**: false
**Default**: false
**Description**: Whether to cluster the leaf nodes. If it is `true`, the value of `centripetalOptions.single` will be set to 100; The returned value of `getClusterNodeStrength` will be used for `centripetalOptions.leaf`; `getClusterNodeStrength.center` will take the average center for all the leaf nodes in current iteration + +## layoutCfg.clustering + +**Type**: Boolean
**Default**: false
**Default**: false
**Description**: Whehter cluster all the nodes according to `nodeClusterBy`. If it is `true`, the returned value of `getClusterNodeStrength` will be used for `centripetalOptions.single`, `centripetalOptions.leaf`, and `centripetalOptions.others`; `centripetalOptions.center` will take the average center of all the nodes in the same cluster + +## layoutCfg.nodeClusterBy + +**Type**: String
**Default**: undefined
**Default**: false
**Description**: The field name in the node data to cluster the nodes. Takes effect when `clustering` is `true`, and the `centripetalOptions` will be generated automatically. You could configure the strengths for different nodes with `clusterNodeStrength` + +## layoutCfg.clusterNodeStrength + +**Type**: Number | Function
**Default**: 20
**Default**: false
**Description**: The clustering center force strengths for different nodes, takes effect with `clustering` and `nodeClusterBy` + +## layoutCfg.monitor + +**Type**: `(params:{ energy: number, nodes: NodeData[], edges: EdgeData[], iterations: number }) => void`
**Default**: undefined
**Default**: false
**Description**: The callback function for each iteration, the parameters including the energy of the layout, all the nodes' data, all the edges' data, and the current iteration number. Note that the calculation for energy will take extra cost. If the `monitor` is not configured, the calculation will be ignore. + +## layoutCfg.onTick + +**Type**: Function
**Default**: {}
**默认**: false
**Description**: The callback function of each iteration + +## layoutCfg.onLayoutEnd + +**Type**: Function
**Default**: {}
**Required**: false
**Description**: The callback function after layout + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. + diff --git a/packages/site/docs/api/graphLayout/force2.zh.md b/packages/site/docs/api/graphLayout/force2.zh.md new file mode 100644 index 0000000000..cb620aa14f --- /dev/null +++ b/packages/site/docs/api/graphLayout/force2.zh.md @@ -0,0 +1,194 @@ +--- +title: Force2 力导向 +order: 2 +--- + +Force2 实现经典的力导向算法,G6 4.7.0 后支持。沉淀自 graphin-force,能够更加自由地支持设置节点质量、群组中心力等。相比 graphin-force,性能有显著提升。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'force2', + center: [ 200, 200 ], // 可选,默认为图的中心 + linkDistance: 50, // 可选,边长 + nodeStrength: 30, // 可选 + edgeStrength: 0.1, // 可选 + nodeSize: 30, // 可选 + onTick: () => { // 可选 + console.log('ticking'); + }, + onLayoutEnd: () => { // 可选 + console.log('force layout done'); + }, + workerEnabled: true, // 可选,开启 web-worker + ... // 更多参数见下方 + } +}); +``` + +当你希望固定某个节点的位置,不受力的影响时,可以在该节点数据中配置 `fx` 与 `fy` 作为固定的坐标。[Force2 布局固定被拖拽节点位置的 Demo](/zh/examples/net/forceDirected#force2Fix)。 + +## layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +## layoutCfg.animate + +**类型**: Boolean
**示例**:false
**默认值**::false
**是否必须**:false
**说明**:是否每次迭代都刷新画布,若为 `true` 则将表现出带有动画逐步布局的效果 + +## layoutCfg.linkDistance + +**类型**: Number / Function
**默认值**1
**是否必须**:false
**说明**:边长度 + +## layoutCfg.nodeStrength + +**类型**: Number / Function
**默认值**:1000
**是否必须**:false
**说明**:节点作用力,正数代表节点之间的斥力作用,负数代表节点之间的引力作用(注意与 'force' 相反) + +## layoutCfg.edgeStrength + +**类型**:Number / Function
**默认值**:200
**是否必须**:false
**说明**:边的作用力(引力)大小 + +## layoutCfg.preventOverlap + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否防止重叠,必须配合下面属性 `nodeSize` 或节点数据中的 `size` 属性,只有在数据中设置了 `size` 或在该布局中配置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测 + +## layoutCfg.nodeSize + +**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:节点大小(直径)。用于碰撞检测。若不指定,则根据传入的节点的 size 属性计算。若即不指定,节点中也没有 `size`,则默认大小为 `10` + +## layoutCfg.nodeSpacing + +**类型**: Number / Function
**默认值**: 0
**是否必须**: false
**示例**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**描述**: `preventOverlap` 为 `true` 时生效, 防止重叠时节点边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距, 如示例 2 所示 + +## layoutCfg.minMovement + +**类型**:Number
**默认值**:0.5
**是否必须**:false
**说明**:当一次迭代的平均/最大/最小(根据`distanceThresholdMode`决定)移动长度小于该值时停止迭代。数字越小,布局越收敛,所用时间将越长 + +## layoutCfg.distanceThresholdMode + +**类型**:'mean' | 'max' | 'min'
**默认值**:'mean'
**是否必须**:false
**说明**:`minMovement` 的使用条件,`'mean'` 代表平均移动距离小于 `minMovement` 时停止迭代,`'max'` / `'min'` 代表最大/最小移动距离小于时 `minMovement` 时停时迭代。默认为 `'mean'` + +## layoutCfg.maxIteration + +**类型**:Number
**默认值**:1000
**是否必须**:false
**说明**:最大迭代次数。当迭代次数超过该值,但平均移动长度仍然没有达到 minMovement,也将强制停止迭代 + +## layoutCfg.damping + +**类型**:Number
**默认值**:0.9
**是否必须**:false
**说明**:阻尼系数,取值范围 [0, 1]。数字越大,速度降低得越慢 + +## layoutCfg.interval + +**类型**:Number
**默认值**:0.02
**是否必须**:false
**说明**:控制每个迭代节点的移动速度 + +## layoutCfg.maxSpeed + +**类型**:Number
**默认值**:1000
**是否必须**:false
**说明**:一次迭代的最大移动长度 + +## layoutCfg.factor + +**类型**:Number
**默认值**:1
**是否必须**:false
**说明**:斥力系数,数值越大,斥力越大 + +## layoutCfg.coulombDisScale + +**类型**:Number
**默认值**:0.005
**是否必须**:false
**说明**:库伦系数,斥力的一个系数,数字越大,节点之间的斥力越大 + +## layoutCfg.getMass + +**类型**:Function
**默认值**:undefined
**是否必须**:false
**说明**:每个节点质量的回调函数,若不指定,则默认使用度数作为节点质量。使用方法与 `nodeSpacing` 类似,每个回调函数返回一个数值作为该节点的质量 + +## layoutCfg.getCenter + +**类型**:Function
**默认值**:undefined
**是否必须**:false
**说明**:每个节点中心力的 x、y、强度的回调函数,若不指定,则没有额外中心力
**示例**: + +```javascript +(d, degree) => { + // d is a node, degree is the degree of the node + if (d.clusterId === 'c1') return [100, 100, 10]; // x, y, strength + if (degree === 0) return [250, 250, 15]; + return [180, 180, 5]; // x, y, strength +}; +``` + +## layoutCfg.gravity + +**类型**:Number
**默认值**:10
**是否必须**:false
**说明**:中心力大小,指所有节点被吸引到 `center` 的力。数字越大,布局越紧凑 + +## layoutCfg.centripetalOptions + +**类型**:CentripetalOptions(见下文类型说明)
**默认值**:见下文
**是否必须**:false
**说明**:详细配置见下文。向心力配置,包括叶子节点、离散点、其他节点的向心中心及向心力大小 + +`CentripetalOptions` 类型说明: + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| single | number / Function | 2 | 2, | 离散节点(即度数为 0 的节点)受到的向心力大小 | +| leaf | number / Function | 2 | 2 | 叶子节点(即度数为 1 的节点)受到的向心力大小 | +| others | number / Function | 1 | 1 | 除离散节点、叶子节点以外的其他节点(即度数 > 1 的节点)受到的向心力大小 | +| center | Function | (node, nodes, edges) => ({ x: 10, y: 10 }) | 图的中心 | 向心力发出的位置,可根据节点、边的情况返回不同的值 | + +`centripetalOptions` 示例: + +``` +centripetalOptions: { + // single、leaf、others 的函数形式的参数为当前节点数据、所有节点数据、所有边数据 + single: (node, nodes, edges) => node.field1 || 1, + leaf: (node, nodes, edges) => node.field2 || 1, + others: (node, nodes, edges) => node.field3|| 1, + // 参数为当前节点数据、所有节点数据、所有边数据、画布宽度、画布高度 + center: (node, nodes, edges, width, height) => { + if (node.field4) return { x: width / 2, y: height / 2 }; + if (node.field5) return { x: node.field6, y: node.field7 }; + // ... + } +} +``` + +## layoutCfg.leafCluster + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否需要叶子结点聚类,若为 `true`,则 centripetalOptions.single 将为 100;centripetalOptions.leaf 将使用 `getClusterNodeStrength` 返回值;getClusterNodeStrength.center 将为叶子节点返回当前所有叶子节点的平均中心 + +## layoutCfg.clustering + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否需要全部节点聚类,若为 `true`,将使用 `nodeClusterBy` 配置的节点数据中的字段作为聚类依据。 centripetalOptions.single、centripetalOptions.leaf、centripetalOptions.others 将使用 `getClusterNodeStrength` 返回值;leaf、centripetalOptions.center 将使用当前节点所属聚类中所有节点的平均中心 + +## layoutCfg.nodeClusterBy + +**类型**:String
**默认值**:undefined
**是否必须**:false
**说明**:指定节点数据中的字段名称作为节点聚类的依据,`clustering` 为 `true` 时生效,自动生成 `centripetalOptions`,可配合 `clusterNodeStrength` 使用 + +## layoutCfg.clusterNodeStrength + +**类型**:Number | Function
**默认值**:20
**是否必须**:false
**说明**:配合 `clustering` 和 `nodeClusterBy` 使用,指定聚类向心力的大小 + +## layoutCfg.monitor + +**类型**:`(params:{ energy: number, nodes: NodeData[], edges: EdgeData[], iterations: number }) => void`
**默认值**:undefined
**是否必须**:false
**说明**:每个迭代的监控信息回调,`energy` 表示布局的收敛能量。若配置可能带来额外的计算能量性能消耗,不配置则不计算 + +## layoutCfg.onTick + +**类型**:Function
**默认值**:{}
**是否必须**:false
**说明**:每一次迭代的回调函数 + +## layoutCfg.onLayoutEnd + +**类型**:Function
**默认值**:{}
**是否必须**:false
**说明**:布局完成后的回调函数 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 diff --git a/packages/site/docs/api/graphLayout/forceAtlas2.en.md b/packages/site/docs/api/graphLayout/forceAtlas2.en.md new file mode 100644 index 0000000000..19a006f20a --- /dev/null +++ b/packages/site/docs/api/graphLayout/forceAtlas2.en.md @@ -0,0 +1,93 @@ +--- +title: Force Atlas 2 +order: 12 +--- + +_It is a new feature of V3.2.2._ FA2 is a kind of force directed layout, which performs better on the convergence and compactness. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'forceAtlas2', + width: 300, + height: 300, + }, +}); +``` + +## layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +## layoutCfg.width + +**Type**: Number
**Default**: The width of the graph
**Required**: false
**Description**: The width of the layout + +## layoutCfg.height + +**Type**: Number
**Default**: The height of the graph
**Required**: false
**Description**: The height of the layout + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. + +## layoutCfg.kr + +**Type**: Number
**Default**: 5
**Required**: false
**Description**: Repulsive parameter, smaller the kr, more compact the graph + +## layoutCfg.kg + +**Type**: Number
**Default**: 5
**Required**: false
**Description**: The parameter for the gravity. Larger kg, the graph will be more compact to the center + +## layoutCfg.ks + +**Type**: Number
**Default**: 0.1
**Required**: false
**Description**: The moving speed of the nodes during iterations + +## layoutCfg.tao + +**Type**: Number
**Default**: 0.1
**Required**: false
**Description**: The threshold of the swinging + +## layoutCfg.mode + +**Type**: 'normal' | 'linlog'
**Default**: 'normal'
**Required**: false
**Description**: Under 'linlog' mode, the cluster will be more compact +## layoutCfg.preventOverlap + +**Type**: boolean
**Default**: false
**Required**: false
**Description**: Whether prevent node overlappings + +## layoutCfg.dissuadeHubs + +**Type**: boolean
**Default**: false
**Required**: false
**Description**: Wheather to enable hub mode. If it is true, the nodes with larger in-degree will be placed on the center in higher priority + +## layoutCfg.barnesHut + +**Type**: boolean
**Default**: undefined
**Required**: false
**Description**: Whether to enable the barnes hut speedup, which is the quad-tree optimization. Due to the computation for quad-tree re-build in each iteration, we sugguest to enable it in large graph. It is undefined by deafult, when the number of nodes is larger than 250, it will be activated automatically. If it is set to be false, it will not be activated anyway + +## layoutCfg.prune + +**Type**: boolean
**Default**: undefined
**Required**: false
**Description**: Whether to enable auto pruning. It is undefined by default, which means when the number of nodes is larger than 100, it will be activated automatically. If it is set to be false, it will not be activated anyway + +## layoutCfg.maxIteration + +**Type**: number
**Default**: 0
**Required**: false
**Description**: The max iteration number. When it is set to be 0, the iteration number will be automatically adjusted according to the convergence + +## layoutCfg.getWidth + +**Type**: function
**Default**: undefined
**Required**: false
**Description**: The width function of the nodes, the parameter is the data model of a node + +## layoutCfg.getHeight + +**Type**: function
**Default**: undefined
**Required**: false
**Description**: The height function of the nodes, the parameter is the data model of a node + +## layoutCfg.onLayoutEnd + +**Type**: function
**Default**: undefined
**Required**: false
**Description**: The callback function of layout + +## layoutCfg.onTick + +**Type**: function
**Default**: undefined
**Required**: false
**Description**: The callback function for each iteration \ No newline at end of file diff --git a/packages/site/docs/api/graphLayout/forceAtlas2.zh.md b/packages/site/docs/api/graphLayout/forceAtlas2.zh.md new file mode 100644 index 0000000000..a784d003c3 --- /dev/null +++ b/packages/site/docs/api/graphLayout/forceAtlas2.zh.md @@ -0,0 +1,94 @@ +--- +title: Force Atlas 2 +order: 12 +--- + +*V4.2.2 新增功能。*Force Atlas 2 是一种力导向布局的变形,比 force 收敛地更好,更紧凑。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'forceAtlas2', + width: 300, + height: 300, + }, +}); +``` + +## layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +## layoutCfg.width + +**类型**: Number
**默认值**:图的宽度
**是否必须**:false
**说明**:布局的宽度范围 + +## layoutCfg.height + +**类型**: Number
**默认值**:图的高度
**是否必须**:false
**说明**:布局的高度范围 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 + +## layoutCfg.kr + +**类型**: Number
**默认值**:5
**是否必须**:false
**说明**:斥力系数,可用于调整布局的紧凑程度。kr 越大,布局越松散 + +## layoutCfg.kg + +**类型**: Number
**默认值**:5
**是否必须**:false
**说明**:重力系数。kg 越大,布局越聚集在中心 + +## layoutCfg.ks + +**类型**: Number
**默认值**:0.1
**是否必须**:false
**说明**:控制迭代过程中,节点移动的速度 + +## layoutCfg.tao + +**类型**: Number
**默认值**:0.1
**是否必须**:false
**说明**:迭代接近收敛时停止震荡的容忍度 + +## layoutCfg.mode + +**类型**: 'normal' | 'linlog'
**默认值**:'normal'
**是否必须**:false
**说明**:'linlog' 模式下,聚类将更加紧凑 + +## layoutCfg.preventOverlap + +**类型**: boolean
**默认值**:false
**是否必须**:false
**说明**:是否防止节点重叠 + +## layoutCfg.dissuadeHubs + +**类型**: boolean
**默认值**:false
**是否必须**:false
**说明**:是否打开 hub 模式。若为 true,相比与出度大的节点,入度大的节点将会有更高的优先级被放置在中心位置 + +## layoutCfg.barnesHut + +**类型**: boolean
**默认值**:undefined
**是否必须**:false
**说明**:是否打开 barnes hut 加速,即四叉树加速。由于每次迭代需要更新构建四叉树,建议在较大规模图上打开。默认情况下为 undefined,当节点数量大于 250 时它将会被激活。设置为 false 则不会自动被激活 + +## layoutCfg.prune + +**类型**: boolean
**默认值**:undefined
**是否必须**:false
**说明**:是否开启自动剪枝模式。默认情况下为 undefined,当节点数量大于 100 时它将会被激活。注意,剪枝能够提高收敛速度,但可能会降低图的布局质量。设置为 false 则不会自动被激活 + +## layoutCfg.maxIteration + +**类型**: number
**默认值**:0
**是否必须**:false
**说明**:最大迭代次数,若为 0 则将自动调整 + +## layoutCfg.getWidth + +**类型**: function
**默认值**:undefined
**是否必须**:false
**说明**:节点宽度的函数,参数为节点数据 + +## layoutCfg.getHeight + +**类型**: function
**默认值**:undefined
**是否必须**:false
**说明**:节点高度的函数,参数为节点数据 + +## layoutCfg.onLayoutEnd + +**类型**: function
**默认值**:undefined
**是否必须**:false
**说明**:布局结束后的回调函数 + +## layoutCfg.onTick + +**类型**: function
**默认值**:undefined
**是否必须**:false
**说明**:布局每次迭代的回调函数 \ No newline at end of file diff --git a/packages/site/docs/api/graphLayout/fruchterman.en.md b/packages/site/docs/api/graphLayout/fruchterman.en.md new file mode 100644 index 0000000000..4d1f2acb68 --- /dev/null +++ b/packages/site/docs/api/graphLayout/fruchterman.en.md @@ -0,0 +1,62 @@ +--- +title: Fruchterman +order: 8 +--- + +Fruchterman is a kind of force-directed layout. G6 implements it according to the paper Graph Drawing by Force-directed Placement. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'fruchterman', + center: [200, 200], // The center of the graph by default + gravity: 20, + speed: 2, + clustering: true, + clusterGravity: 30, + maxIteration: 2000, + workerEnabled: true, // Whether to activate web-worker + gpuEnabled: true, // Whether to enable the GPU parallel computing, supported by G6 4.0 + }, +}); +``` + +If you want to fix the positions for some nodes during calculation, assign `fx` and `fy` for the nodes as fixing positions. [Demo for fixing node](/en/examples/net/fruchtermanLayout#fructhermanFix). + +## layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +## layoutCfg.maxIteration + +**Type**: Number
**Default**: 1000
**Required**: false
**Description**: The maximum iteration number + +## layoutCfg.gravity + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The gravity, which will affect the compactness of the layout + +## layoutCfg.speed + +**Type**: Number
**Default**: 1
**Required**: false
**Description**: The moving speed of each iteraction. Large value of the speed might lead to violent swing + +## layoutCfg.clustering + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to layout by cluster + +## layoutCfg.clusterGravity + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The gravity of each cluster, which will affect the compactness of each cluster. Takes effect only when `clustering` is `true` + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. + +## layoutCfg.gpuEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the GPU parallel computing, supported by G6 4.0. If the machine or browser does not support GPU computing, it will be degraded to CPU computing automatically. The performance improvement: diff --git a/packages/site/docs/api/graphLayout/fruchterman.zh.md b/packages/site/docs/api/graphLayout/fruchterman.zh.md new file mode 100644 index 0000000000..c0e4d84c05 --- /dev/null +++ b/packages/site/docs/api/graphLayout/fruchterman.zh.md @@ -0,0 +1,62 @@ +--- +title: Fruchterman +order: 8 +--- + +Fruchterman 布局是一种力导布局。算法原文: Graph Drawing by Force-directed Placement + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'fruchterman', + center: [200, 200], // 可选,默认为图的中心 + gravity: 20, // 可选 + speed: 2, // 可选 + clustering: true, // 可选 + clusterGravity: 30, // 可选 + maxIteration: 2000, // 可选,迭代次数 + workerEnabled: true, // 可选,开启 web-worker + gpuEnabled: true, // 可选,开启 GPU 并行计算,G6 4.0 支持 + }, +}); +``` + +当你希望固定某个节点的位置,不受力的影响时,可以在该节点数据中配置 `fx` 与 `fy` 作为固定的坐标。[Fruchterman 布局固定被拖拽节点位置的 Demo](/zh/examples/net/fruchtermanLayout#fructhermanFix)。 + +## layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +## layoutCfg.maxIteration + +**类型**: Number
**默认值**:1000
**是否必须**:false
**说明**:最大迭代次数 + +## layoutCfg.gravity + +**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:重力的大小,影响布局的紧凑程度 + +## layoutCfg.speed + +**类型**: Number
**默认值**:1
**是否必须**:false
**说明**:每次迭代节点移动的速度。速度太快可能会导致强烈震荡 + +## layoutCfg.clustering + +**类型**: Boolean
**默认值**:false
**是否必须**:false
**说明**:是否按照聚类布局 + +## layoutCfg.clusterGravity + +**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:聚类内部的重力大小,影响聚类的紧凑程度,在 `clustering` 为 `true` 时生效 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 + +## layoutCfg.gpuEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 GPU 并行计算。若用户的机器或浏览器不支持 GPU 计算,将会自动降级为 CPU 计算。自 G6 4.0 起支持,性能提升概览: diff --git a/packages/site/docs/api/graphLayout/gforce.en.md b/packages/site/docs/api/graphLayout/gforce.en.md new file mode 100644 index 0000000000..6d98d218f0 --- /dev/null +++ b/packages/site/docs/api/graphLayout/gforce.en.md @@ -0,0 +1,133 @@ +--- +title: GForce +order: 11 +--- + +GForce implements the classical force-directed layout algorithm by G6 4.0. It supports assign different masses and center gravities for different nodes freedomly. More importantly, it supports GPU parallel acceleration. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'gForce', + center: [200, 200], // The center of the graph by default + linkDistance: 1, + nodeStrength: 1000, + edgeStrength: 200, + nodeSize: 30, + onTick: () => { + console.log('ticking'); + }, + onLayoutEnd: () => { + console.log('force layout done'); + }, + workerEnabled: true, // Whether to activate web-worker + gpuEnabled: true // Whether to enable the GPU parallel computing, supported by G6 4.0 + ... // more options are shown below + }, +}); +``` + +If you want to fix the positions for some nodes during calculation, assign `fx` and `fy` for the nodes as fixing positions. [Demo for fixing node](/en/examples/net/forceDirected#gForceFix). + +## layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +## layoutCfg.linkDistance + +**Type**: Number / Function
**Default**: 1
**Required**: false
**Description**: The edge length + +## layoutCfg.nodeStrength + +**Type**: Number / Function
**Default**: 1000
**Required**: false
**Description**: The strength of node force. Positive value means repulsive force, negative value means attractive force (it is different from 'force') + +## layoutCfg.edgeStrength + +**Type**: Number / Function
**Default**: 200
**Required**: false
**Description**: The strength of edge force. Calculated according to the degree of nodes by default + +## layoutCfg.preventOverlap + +**Type**: Number
**Default**: false
**Required**: false
**Description**: Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned + +## layoutCfg.nodeSize + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The diameter of the node. It is used for preventing node overlappings. If `nodeSize` is not assigned, the size property in node data will take effect. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default + +## layoutCfg.nodeSpacing + +**Type**: Number / Function
**Default**: 0
**Required**: false
**Example**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**Description**: Takes effect when `preventOverlap` is `true`. It is the minimum distance between nodes to prevent node overlappings. It can be a function to define different distances for different nodes (example 2) + +## layoutCfg.minMovement + +**Type**: Number
**Default**: 0.5
**Required**: false
**Description**: When the average movement of nodes in one iteration is smaller than `minMovement`, terminate the layout + +## layoutCfg.maxIteration + +**Type**: Number
**Default**: 1000
**Required**: false
**Description**: The max number of iterations. If the average movement do not reach `minMovement` but the iteration number is over `maxIteration`, terminate the layout + +## layoutCfg.damping + +**Type**: Number
**Default**: 0.9
**Required**: false
**Description**: Range [0, 1], affect the speed of decreasing node moving speed. Large the number, slower the decreasing + +## layoutCfg.maxSpeed + +**Type**: Number
**Default**: 1000
**Required**: false
**Description**: The max speed in each iteration + +## layoutCfg.coulombDisScale + +**Type**: Number
**Default**: 0.005
**Required**: false
**Description**: A parameter for repulsive force between nodes. Large the number, larger the repulsion + +## layoutCfg.getMass + +**Type**: Function
**Default**: undefined
**Required**: false
**Description**: It is a callback returns the mass of each node. If it is not assigned, the degree of each node will take effect. The usage is similar to `nodeSpacing` + +## layoutCfg.getCenter + +**Type**: Function
**Default**: undefined
**Required**: false
**Description**: It is a callback returns gravity center and the gravity strength for each node
**Example** + +```javascript +(d, degree) => { + // d is a node, degree is the degree of the node + if (d.clusterId === 'c1') return [100, 100, 10]; // x, y, strength + if (degree === 0) return [250, 250, 15]; + return [180, 180, 5]; // x, y, strength +}; +``` + +## layoutCfg.gravity + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The gravity strength to the `center` for all the nodes. Larger the number, more compact the nodes + +## layoutCfg.onTick + +**Type**: Function
**Default**: {}
**Required**: false
**Description**: The callback function of each iteration + +## layoutCfg.onLayoutEnd + +**Type**: Function
**Default**: {}
**Required**: false
**Description**: The callback function after layout + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. + +## layoutCfg.gpuEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the GPU parallel computing, supported by G6 4.0. If the machine or browser does not support GPU computing, it will be degraded to CPU computing automatically. The performance improvement: diff --git a/packages/site/docs/api/graphLayout/gforce.zh.md b/packages/site/docs/api/graphLayout/gforce.zh.md new file mode 100644 index 0000000000..e7e1433df1 --- /dev/null +++ b/packages/site/docs/api/graphLayout/gforce.zh.md @@ -0,0 +1,133 @@ +--- +title: GForce +order: 11 +--- + +GForce 实现了经典的力导向算法,G6 4.0 支持。能够更加自由地支持设置节点质量、群组中心力等。更重要的是,它支持 GPU 并行计算。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'gForce', + center: [ 200, 200 ], // 可选,默认为图的中心 + linkDistance: 50, // 可选,边长 + nodeStrength: 30, // 可选 + edgeStrength: 0.1, // 可选 + nodeSize: 30, // 可选 + onTick: () => { // 可选 + console.log('ticking'); + }, + onLayoutEnd: () => { // 可选 + console.log('force layout done'); + }, + workerEnabled: true, // 可选,开启 web-worker + gpuEnabled: true // 可选,开启 GPU 并行计算,G6 4.0 支持 + ... // 更多参数见下方 + } +}); +``` + +当你希望固定某个节点的位置,不受力的影响时,可以在该节点数据中配置 `fx` 与 `fy` 作为固定的坐标。[GForce 布局固定被拖拽节点位置的 Demo](/zh/examples/net/forceDirected#gForceFix)。 + +## layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +## layoutCfg.linkDistance + +**类型**: Number / Function
**默认值**1
**是否必须**:false
**说明**:边长度 + +## layoutCfg.nodeStrength + +**类型**: Number / Function
**默认值**:1000
**是否必须**:false
**说明**:节点作用力,正数代表节点之间的斥力作用,负数代表节点之间的引力作用(注意与 'force' 相反) + +## layoutCfg.edgeStrength + +**类型**:Number / Function
**默认值**:200
**是否必须**:false
**说明**:边的作用力(引力)大小 + +## layoutCfg.preventOverlap + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否防止重叠,必须配合下面属性 `nodeSize` 或节点数据中的 `size` 属性,只有在数据中设置了 `size` 或在该布局中配置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测 + +## layoutCfg.nodeSize + +**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:节点大小(直径)。用于碰撞检测。若不指定,则根据传入的节点的 size 属性计算。若即不指定,节点中也没有 `size`,则默认大小为 `10` + +## layoutCfg.nodeSpacing + +**类型**: Number / Function
**默认值**: 0
**是否必须**: false
**示例**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**描述**: `preventOverlap` 为 `true` 时生效, 防止重叠时节点边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距, 如示例 2 所示 + +## layoutCfg.minMovement + +**类型**:Number
**默认值**:0.5
**是否必须**:false
**说明**:当一次迭代的平均移动长度小于该值时停止迭代。数字越小,布局越收敛,所用时间将越长 + +## layoutCfg.maxIteration + +**类型**:Number
**默认值**:1000
**是否必须**:false
**说明**:最大迭代次数。当迭代次数超过该值,但平均移动长度仍然没有达到 minMovement,也将强制停止迭代 + +## layoutCfg.damping + +**类型**:Number
**默认值**:0.9
**是否必须**:false
**说明**:阻尼系数,取值范围 [0, 1]。数字越大,速度降低得越慢 + +## layoutCfg.maxSpeed + +**类型**:Number
**默认值**:1000
**是否必须**:false
**说明**:一次迭代的最大移动长度 + +## layoutCfg.coulombDisScale + +**类型**:Number
**默认值**:0.005
**是否必须**:false
**说明**:库伦系数,斥力的一个系数,数字越大,节点之间的斥力越大 + +## layoutCfg.getMass + +**类型**:Function
**默认值**:undefined
**是否必须**:false
**说明**:每个节点质量的回调函数,若不指定,则默认使用度数作为节点质量。使用方法与 `nodeSpacing` 类似,每个回调函数返回一个数值作为该节点的质量 + +## layoutCfg.getCenter + +**类型**:Function
**默认值**:undefined
**是否必须**:false
**说明**:每个节点中心力的 x、y、强度的回调函数,若不指定,则没有额外中心力
**示例**: + +```javascript +(d, degree) => { + // d is a node, degree is the degree of the node + if (d.clusterId === 'c1') return [100, 100, 10]; // x, y, strength + if (degree === 0) return [250, 250, 15]; + return [180, 180, 5]; // x, y, strength +}; +``` + +## layoutCfg.gravity + +**类型**:Number
**默认值**:10
**是否必须**:false
**说明**:中心力大小,指所有节点被吸引到 `center` 的力。数字越大,布局越紧凑 + +## layoutCfg.onTick + +**类型**:Function
**默认值**:{}
**是否必须**:false
**说明**:每一次迭代的回调函数 + +## layoutCfg.onLayoutEnd + +**类型**:Function
**默认值**:{}
**是否必须**:false
**说明**:布局完成后的回调函数 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 + +## layoutCfg.gpuEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 GPU 并行计算。若用户的机器或浏览器不支持 GPU 计算,将会自动降级为 CPU 计算。自 G6 4.0 起支持,性能提升概览: diff --git a/packages/site/docs/api/graphLayout/grid.en.md b/packages/site/docs/api/graphLayout/grid.en.md new file mode 100644 index 0000000000..1ce5d4ba05 --- /dev/null +++ b/packages/site/docs/api/graphLayout/grid.en.md @@ -0,0 +1,64 @@ +--- +title: Grid +order: 10 +--- + +Grid orders the nodes according to the configurations and arranged them onto grid. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'grid', + begin: [0, 0], + preventOverlap: true, // nodeSize or size in data is required for preventOverlap: true + preventOverlapPdding: 20, + nodeSize: 30, + condense: false, + rows: 5, + cols: 5, + sortBy: 'degree', + }, +}); +``` + +## layoutCfg.begin + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: [ 0, 0 ]
**Required**: false
**Description**: The place where the grid begin (left top) + +## layoutCfg.preventOverlap + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned. If the size in node data does not exist either, `nodeSize` is assigned to 30 by default + +## layoutCfg.nodeSize + +**Type**: Number
**Default**: 30
**Required**: false
**Description**: The diameter of the node. It is used for preventing node overlappings + +## layoutCfg.preventOverlapPadding + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The minimum padding between nodes to prevent node overlappings. Takes effect when `preventOverlap` is `true` + +## layoutCfg.condense + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Wheter to utilize the minimum space of the canvas. `false` means utilizing the full space, `true` means utilizing the minimum space. + +## layoutCfg.rows + +**Type**: Number
**Default**: undefined
**Required**: false
**Description**: The row number of the grid. If `rows` is undefined, the algorithm will calculate it according to the space and node numbers automatically + +## layoutCfg.cols + +**Type**: Number
**Default**: undefined
**Required**: false
**Description**: The column number of the grid. If `cols` is undefined, the algorithm will calculate it according to the space and node numbers automatically + +## layoutCfg.sortBy + +**Type**: String
**Default**: undefined
**Required**: false
**Description**: The ordering method for nodes. Smaller the index in the ordered array, more center the node will be placed. If `sortBy` is undefined, the algorithm order the nodes according to their degrees + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. diff --git a/packages/site/docs/api/graphLayout/grid.zh.md b/packages/site/docs/api/graphLayout/grid.zh.md new file mode 100644 index 0000000000..5b9909a172 --- /dev/null +++ b/packages/site/docs/api/graphLayout/grid.zh.md @@ -0,0 +1,65 @@ +--- +title: 格子 Grid +order: 10 +--- + +Grid 布局是将所有节点通过某种指定属性排序后,整齐地放置在网格上。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'grid', + begin: [ 0, 0 ], // 可选, + preventOverlap: true, // 可选,必须配合 nodeSize + preventOverlapPdding: 20, // 可选 + nodeSize: 30, // 可选 + condense: false, // 可选 + rows: 5, // 可选 + cols: 5, // 可选 + sortBy: 'degree' // 可选 + workerEnabled: true // 可选,开启 web-worker + } +}); +``` + +## layoutCfg.begin + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:[ 0, 0 ]
**是否必须**:false
**说明**:网格开始位置(左上角) + +## layoutCfg.preventOverlap + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否防止重叠,必须配合下面属性 `nodeSize`,只有设置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测 + +## layoutCfg.nodeSize + +**类型**: Number
**默认值**:30
**是否必须**:false
**说明**:节点大小(直径)。用于防止节点重叠时的碰撞检测 + +## layoutCfg.preventOverlapPadding + +**类型**:Number
**默认值**:10
**是否必须**:false
**说明**:避免重叠时节点的间距 `padding`。`preventOverlap` 为 `true` 时生效 + +## layoutCfg.condense + +**类型**: Boolean
**默认值**:false
**是否必须**:false
**说明**:为 `false` 时表示利用所有可用画布空间,为 `true` 时表示利用最小的画布空间 + +## layoutCfg.rows + +**类型**: Number
**默认值**:undefined
**是否必须**:false
**说明**:网格的行数,为 undefined 时算法根据节点数量、布局空间、`cols`(若指定)自动计算 + +## layoutCfg.cols + +**类型**: Number
**默认值**:undefined
**是否必须**:false
**说明**:网格的列数,为 undefined 时算法根据节点数量、布局空间、`rows`(若指定)自动计算 + +## layoutCfg.sortBy + +**类型**: String
**默认值**:undefined
**是否必须**:false
**说明**:指定排序的依据(节点属性名),数值越高则该节点被放置得越中心。若为 undefined,则会计算节点的度数,度数越高,节点将被放置得越中心 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 diff --git a/packages/site/docs/api/graphLayout/guide.en.md b/packages/site/docs/api/graphLayout/guide.en.md new file mode 100644 index 0000000000..72a7a47d45 --- /dev/null +++ b/packages/site/docs/api/graphLayout/guide.en.md @@ -0,0 +1,114 @@ +--- +title: Outline +order: 0 +--- + +## Built-in Layout Overview + +G6 provides several built-in layout algorithms as listed below. They can be [configured to `layout` when instantiate the Graph](#configure-to-gaph), or be [instantiated independently](#instantiate-independently). If the built-in layouts cannot meet your requirement, you can also [custom a layout](/en/docs/api/registerLayout). + +Notice that the layouts for Graph cannot be used on TreeGraph. + +- [Random Layout](./random): Randomizes the node postions; +- [GForce Layout](./gforce): Classical force-directed layout supports GPU parallel computing, supported by G6 4.0; +- [Force Layout](./force): Classical force-directed layout; +- [Force Atlas 2 Layout](./forceAtlas2): FA2 is a kind of force directed layout, which performs better on the convergence and compactness; +- [Fruchterman Layout](./fruchterman): A kind of force-directed layout; +- [Circular Layout](./circular): Arranges the nodes on a circle; +- [Radial Layout](./radial): Arranges the nodes around a focus node radially; +- [MDS Layout](./mds): Multidemensional Scaling; +- [Dagre Layout](./dagre): Arranges the nodes hierarchically; +- [Concentric Layout](./concentric): Arranges the nodes on concentric circles; +- [Grid Layout](./grid): Arranges the nodes on grid. +- [Combo Force Layout](./comboForce):_New feature of V3.5_ Designed for graph with combos. +- [Combo Combined Layout](./comboCombined):_New feature of V4.6_ Designed for graph with combos. Support configuring the layout for items inside a combo and the layout for the outer combos and nodes. + +## Configure to Gaph + +Configure the `layout` when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + ... // Other configurations + layout: { // Object, configuration for layout. random by default + type: 'force', + preventOverlap: true, + nodeSize: 30, + // workerEnabled: true, // Whether enable webworker + // gpuEnabled: true // Whether enable GPU version. supported by G6 4.0, and only support gForce and fruchterman layout. If the machine or browser does not support GPU computing, it will be degraded to CPU computing automatically. + ... // Other layout configurations + } +}); +``` + +The configurations of each layout algorithms are different. Please refer to corresponding API of each layout in this directory.
When `layout` is not assigned on graph: + +- If there are `x` and `y` in node data, the graph will render with these information; +- If there is no positions information in node data, the graph will arrange nodes with Random Layout by default. + +## Instantiate Independently + +The functions in this section should be concerned in these two situation: + +- When you want to applay a layout algorithm to your data but not for Graph, you can instantiate the layout independently by calling `const layout = new G6.Layout['layoutName']`. +- When you want to custom a new type of layout by `G6.registerLayout`, some functions you should override. + +### Initialize + +#### init(data) + +Initialize the layout. + +**Paramter** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ----------------------- | +| data | Object | true | The data for the layout | + +#### getDefaultCfg() + +Get the default configurations of the layout. + +**Return** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | -------------------------- | +| cfg | Object | true | The default configurations | + +### Layout + +#### execute() + +Execute the layout. + +#### layout(data) + +Execute layout according to the data. + +**Paramter** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ----------------------- | +| data | Object | true | The data to be arranged | + +### Update + +#### updateCfg(cfg) + +Update the configurations for layout. + +**Paramter** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ------------------ | +| cfg | Object | true | New configurations | + +### Destroy + +### destroy() + +Destroy the layout. + +## AI Layout Prediction + + diff --git a/packages/site/docs/api/graphLayout/guide.zh.md b/packages/site/docs/api/graphLayout/guide.zh.md new file mode 100644 index 0000000000..25e1337bee --- /dev/null +++ b/packages/site/docs/api/graphLayout/guide.zh.md @@ -0,0 +1,114 @@ +--- +title: 导览及使用 +order: 0 +--- + +## 内置布局导览 + +G6 提供了以下内置布局算法。可以在[实例化图时配置](#实例化图时使用布局),或[独立使用](#单独使用布局)。当内置布局算法不满足需求时,还可以[自定义布局](/zh/docs/api/registerLayout)。 + +注意,Graph 布局与 TreeGaph 布局相互不通用。 + +- [Random Layout](./random):随机布局; +- **[GForce Layout](./gforce):G6 4.0 支持的经典力导向布局,支持 GPU 并行计算:** + + > 力导向布局:一个布局网络中,粒子与粒子之间具有引力和斥力,从初始的随机无序的布局不断演变,逐渐趋于平衡稳定的布局方式称之为力导向布局。适用于描述事物间关系,比如人物关系、计算机网络关系等。 + +- [Force Layout](./force):引用 d3 的经典力导向布局; +- [Force Atlas 2 Layout](./forceAtlas2):FA2 力导向布局,比 force 收敛地更好,更紧凑; +- [Circular Layout](./circular):环形布局; +- [Radial Layout](./radial):辐射状布局; +- [MDS Layout](./mds):高维数据降维算法布局; +- [Fruchterman Layout](./fruchterman):Fruchterman 布局,一种力导布局; +- [Dagre Layout](./dagre):层次布局; +- [Concentric Layout](./concentric):同心圆布局,将重要(默认以度数为度量)的节点放置在布局中心; +- [Grid Layout](./grid):格子布局,将节点有序(默认是数据顺序)排列在格子上; +- [Combo Force Layout](./comboForce):*V3.5 新增。*适用于带有 combo 图的力导向布局。 +- [Combo Combined Layout](./comboCombined):*V4.6 新增。*适用于带有 combo 的图,可自由组合内外布局,默认情况下可以有较好的效果,推荐有 combo 的图使用该布局。 + +## 实例化图时使用布局 + +在 G6 中使用布局,在实例化图时配置 `layout` 属性。例如: + +```javascript +const graph = new G6.Graph({ + // ... // 其他配置项 + layout: { // Object,可选,布局的方法及其配置项,默认为 random 布局。 + type: 'force', + preventOverlap: true, + nodeSize: 30, + // workerEnabled: true, // 是否启用 webworker + // gpuEnabled: true // 是否使用 gpu 版本的布局算法,G6 4.0 支持,目前仅支持 gForce 及 fruchterman。若用户的机器或浏览器不支持 GPU 计算,将会自动降级为 CPU 计算 + ... // 其他配置 + } +}); +``` + +每种布局方法的配置项不尽相同,具体参见本目录下每种布局的 API。
当实例化图时没有配置 `layout` 时: + +- 若数据中节点有位置信息(`x` 和 `y`),则按照数据的位置信息进行绘制; +- 若数据中节点没有位置信息,则默认使用 Random Layout 进行布局。 + +## 单独使用布局 + +以下方法为通过 `const layout = new G6.Layout['layoutName']` 单独使用布局时,或自定义布局时可能需要复写的方法。如果上述两种情况,仅在实例化图时通过配置 `layout` 使用内置布局方法时,以下方法由 G6 控制并调用,用户不需要了解。 + +### 初始化 + +#### init(data) + +初始化布局。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ---------------- | +| data | object | true | 布局中使用的数据 | + +#### getDefaultCfg() + +返回布局的默认参数。 + +**返回值** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | -------------- | +| cfg | object | true | 布局的默认参数 | + +### 布局 + +#### execute() + +执行布局算法。 + +#### layout(data) + +根据传入的数据进行布局。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ---------------- | +| data | object | true | 布局中使用的数据 | + +### 更新 + +#### updateCfg(cfg) + +更新布局参数。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ------------ | +| cfg | object | true | 新的布局配置 | + +### 销毁 + +#### destroy() + +销毁布局。 + +## AI 智能布局推荐 + + diff --git a/packages/site/docs/api/graphLayout/mds.en.md b/packages/site/docs/api/graphLayout/mds.en.md new file mode 100644 index 0000000000..0012b49f4f --- /dev/null +++ b/packages/site/docs/api/graphLayout/mds.en.md @@ -0,0 +1,31 @@ +--- +title: MDS +order: 9 +--- + +MDS (Multidimensional scaling) is used for project high dimensional data onto low dimensional space.
img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'mds', + workerEnabled: true, // Whether to activate web-worker + }, +}); +``` + +## layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +## layoutCfg.linkDistance + +**Type**: Number
**Default**: 50
**Required**: false
**Description**: The edge length + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. diff --git a/packages/site/docs/api/graphLayout/mds.zh.md b/packages/site/docs/api/graphLayout/mds.zh.md new file mode 100644 index 0000000000..c94c4645df --- /dev/null +++ b/packages/site/docs/api/graphLayout/mds.zh.md @@ -0,0 +1,31 @@ +--- +title: 高维数据降维 MDS +order: 9 +--- + +MDS 布局是高维数据降维算法布局,该算法全称  Multidimensional Scaling 。
img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'mds', + workerEnabled: true, // 可选,开启 web-worker + }, +}); +``` + +## layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +## layoutCfg.linkDistance + +**类型**: Number
**默认值**:50
**是否必须**:false
**说明**:边长度 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 diff --git a/packages/site/docs/api/graphLayout/radial.en.md b/packages/site/docs/api/graphLayout/radial.en.md new file mode 100644 index 0000000000..957105567d --- /dev/null +++ b/packages/site/docs/api/graphLayout/radial.en.md @@ -0,0 +1,102 @@ +--- +title: Radial +order: 5 +--- + +Radial layout arranges the nodes to concentrics centered at a focus node according to their shortest path length to the focus node. G6 implements it according to the paper: More Flexible Radial Layout. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'radial', + center: [200, 200], // The center of the graph by default + linkDistance: 50, // The edge length + maxIteration: 1000, + focusNode: 'node11', + unitRadius: 100, + preventOverlap: true, // nodeSize or size in data is required for preventOverlap: true + nodeSize: 30, + strictRadial: false, + workerEnabled: true, // Whether to activate web-worker + }, +}); +``` + +## layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout. + +## layoutCfg.linkDistance + +**Type**: Number
**Default**: 50
**Required**: false
**Description**: The edge length. + +## layoutCfg.maxIteration + +**Type**: Number
**Default**: 1000
**Required**: false
**Description**: The max iteration number. + +## layoutCfg.focusNode + +**Type**: String | Object
**Default**: null
**Required**: false
**Description**: The focus node of the radial layout. The first node of the data is the default value. It can be the id of a node or the node item. + +## layoutCfg.unitRadius + +**Type**: Number
**Default**: 100
**Required**: false
**Description**: The separation between adjacent circles. If `unitRadius` is not assigned, the layout will fill the canvas automatically. + +## layoutCfg.preventOverlap + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned. + +## layoutCfg.nodeSize + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The diameter of the node. It is used for preventing node overlappings + +## layoutCfg.nodeSpacing + +**Type**: Number / Function
**Default**: 0
**Required**: false
**Example**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**Description**: Takes effect when `preventOverlap` is `true`. It is the minimum distance between nodes to prevent node overlappings. It can be a function to define different distances for different nodes (example 2) + +## layoutCfg.maxPreventOverlapIteration + +**Type**: Number
**Default**: 200
**Required**: false
**Description**: The maximum iteration number of preventing node overlappings + +## layoutCfg.strictRadial + +**Type**: Boolean
**Default**: true
**Required**: false
**Description**: Whether to layout the graph as strict radial, which means the nodes will be arranged on each circle strictly. Takes effect only when `preventOverlap` is `true` + +- When `preventOverlap` is `true`, and `strictRadial` is `false`, the overlapped nodes are arranged along their circles strictly. But for the situation that there are too many nodes on a circle to be arranged, the overlappings might not be eliminated completely +- When `preventOverlap` is `true`, and `strictRadial` is `true` , the overlapped nodes can be arranged around their circle with small offsets. + +img +img +img + +> (Left)preventOverlap = false.(Center)preventOverlap = false, strictRadial = true. (Right)preventOverlap = false, strictRadial = false. + +## layoutCfg.sortBy + +**Type**: String
**Default**: undefined
**Required**: false
**Description**: Sort the nodes of the same level. `undefined` by default, which means place the nodes with connections as close as possible; `'data'` means place the node according to the ordering in data, the closer the nodes in data ordering, the closer the nodes will be placed. `sortBy` also can be assigned to any name of property in nodes data, such as `'cluster'`, `'name'` and so on (make sure the property exists in the data) + +## layoutCfg.sortStrength + +**Type**: Number
**Default**: 10
**Required**: false
**Description**: The strength to sort the nodes in the same circle. Larger number means place the nodes with smaller distance of `sortBy` more closely. Takes effect only when `sortBy` is not `undefined` + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. diff --git a/packages/site/docs/api/graphLayout/radial.zh.md b/packages/site/docs/api/graphLayout/radial.zh.md new file mode 100644 index 0000000000..7f1a8b7e06 --- /dev/null +++ b/packages/site/docs/api/graphLayout/radial.zh.md @@ -0,0 +1,102 @@ +--- +title: 辐射形 Radial +order: 5 +--- + +Radial 布局是将图布局成辐射状的布局方法。以一个 focusNode 为中心,其余节点按照与 focusNode 的度数关系排列在不同距离的环上。距离 focusNode 一度的节点布局在与其最近的第一个环上,距离 focusNode 二度的节点布局在第二个环上,以此类推。算法原文链接: More Flexible Radial Layout。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'radial', + center: [ 200, 200 ], // 可选,默认为图的中心 + linkDistance: 50, // 可选,边长 + maxIteration: 1000, // 可选 + focusNode: 'node11', // 可选 + unitRadius: 100, // 可选 + preventOverlap: true, // 可选,必须配合 nodeSize + nodeSize: 30, // 可选 + strictRadial: false // 可选 + workerEnabled: true // 可选,开启 web-worker + } +}); +``` + +## layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +## layoutCfg.linkDistance + +**类型**: Number
**默认值**:50
**是否必须**:false
**说明**:边长度 + +## layoutCfg.maxIteration + +**类型**: Number
**默认值**:1000
**是否必须**:false
**说明**:停止迭代到最大迭代数 + +## layoutCfg.focusNode + +**类型**:String | Object
**默认值**:null
**是否必须**:false
**说明**:辐射的中心点,默认为数据中第一个节点。可以传入节点 id 或节点本身 + +## layoutCfg.unitRadius + +**类型**:Number
**默认值**:100
**是否必须**:false
**说明**:每一圈距离上一圈的距离。默认填充整个画布,即根据图的大小决定 + +## layoutCfg.preventOverlap + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否防止重叠,必须配合下面属性 `nodeSize`,只有设置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测 + +## layoutCfg.nodeSize + +**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:节点大小(直径)。用于防止节点重叠时的碰撞检测 + +## layoutCfg.nodeSpacing + +**类型**: Number / Function
**默认值**: 0
**是否必须**: false
**示例**: Example 1: 10
Example 2: + +```javascript +(d) => { + // d is a node + if (d.id === 'node1') { + return 100; + } + return 10; +}; +``` + +
**描述**: `preventOverlap` 为 `true` 时生效, 防止重叠时节点边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距, 如示例 2 所示 + +## layoutCfg.maxPreventOverlapIteration + +**类型**: Number
**默认值**:200
**是否必须**:false
**说明**:防止重叠步骤的最大迭代次数 + +## layoutCfg.strictRadial + +**类型**: Boolean
**默认值**:true
**是否必须**:false
**说明**:是否必须是严格的 radial 布局,及每一层的节点严格布局在一个环上。`preventOverlap` 为 `true` 时生效。 + +- 当 `preventOverlap` 为 `true`,且 `strictRadial` 为 `false` 时,有重叠的节点严格沿着所在的环展开,但在一个环上若节点过多,可能无法完全避免节点重叠。 +- 当 `preventOverlap` 为 `true`,且 `strictRadial` 为 `true`  时,允许同环上重叠的节点不严格沿着该环布局,可以在该环的前后偏移以避免重叠。 + +img +img +img + +> (左)preventOverlap = false。(中)preventOverlap = false,strictRadial = true。(右)preventOverlap = false,strictRadial = false。 + +## layoutCfg.sortBy + +**类型**: String
**默认值**: undefined
**是否必须**: false
**说明**: 同层节点布局后相距远近的依据。默认 `undefined` ,表示根据数据的拓扑结构(节点间最短路径)排布,即关系越近/点对间最短路径越小的节点将会被尽可能排列在一起;`'data'` 表示按照节点在数据中的顺序排列,即在数据顺序上靠近的节点将会尽可能排列在一起;也可以指定为节点数据中的某个字段名,例如 `'cluster'`、`'name'` 等(必须在数据中存在) + +## layoutCfg.sortStrength + +**类型**: Number
**默认值**: 10
**是否必须**: false
**说明**: 同层节点根据 `sortBy` 排列的强度,数值越大,`sortBy` 指定的方式计算出距离越小的越靠近。`sortBy` 不为 `undefined` 时生效 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 diff --git a/packages/site/docs/api/graphLayout/random.en.md b/packages/site/docs/api/graphLayout/random.en.md new file mode 100644 index 0000000000..d11432e236 --- /dev/null +++ b/packages/site/docs/api/graphLayout/random.en.md @@ -0,0 +1,38 @@ +--- +title: Random +order: 1 +--- + +Random is the default layout in G6. It will take effect when `layout` is not assigned to the Graph instance and there is no position information in node data. + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'random', + width: 300, + height: 300, + }, +}); +``` + +## layoutCfg.center + +**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout + +## layoutCfg.width + +**Type**: Number
**Default**: The width of the graph
**Required**: false
**Description**: The width of the layout + +## layoutCfg.height + +**Type**: Number
**Default**: The height of the graph
**Required**: false
**Description**: The height of the layout + +## layoutCfg.workerEnabled + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction. +⚠️ Notice: When `workerEnabled: true`, all the function type parameters are not supported. diff --git a/packages/site/docs/api/graphLayout/random.zh.md b/packages/site/docs/api/graphLayout/random.zh.md new file mode 100644 index 0000000000..9df4cf707d --- /dev/null +++ b/packages/site/docs/api/graphLayout/random.zh.md @@ -0,0 +1,38 @@ +--- +title: Random 随机 +order: 1 +--- + +Random 布局是 G6 中的默认布局方法。当实例化图时没有指定布局方法,且数据中也不存在位置信息时,G6 将自动使用 Random 布局。 + +img + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 600, + layout: { + type: 'random', + width: 300, + height: 300, + }, +}); +``` + +## layoutCfg.center + +**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心 + +## layoutCfg.width + +**类型**: Number
**默认值**:图的宽度
**是否必须**:false
**说明**:布局的宽度范围 + +## layoutCfg.height + +**类型**: Number
**默认值**:图的高度
**是否必须**:false
**说明**:布局的高度范围 + +## layoutCfg.workerEnabled + +**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。 +⚠️ 注意: `workerEnabled: true` 时,不支持所有函数类型的参数。 diff --git a/packages/site/docs/api/registerItem.en.md b/packages/site/docs/api/registerItem.en.md new file mode 100644 index 0000000000..193860c463 --- /dev/null +++ b/packages/site/docs/api/registerItem.en.md @@ -0,0 +1,305 @@ +--- +title: G6.registerNode/Edge/Combo +order: 7 +--- + +This document shows the functions that should be implemented or rewrited when custom nodes by `G6.registerNode` or custom edges by `G6.registerEdge`. + +## G6.registerNode(nodeName, options, extendedNodeName) + +When the built-in nodes cannot satisfy your requirments, custom a type of node by `G6.registerNode(nodeName, options, extendedNodeName)`. + +### Parameters + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| nodeName | String | true | The unique name of the custom node. | +| options | Object | true | The configurations of custom node, include functions of complete life cycles. Please refer to [Shape Doc](/en/docs/manual/middle/elements/shape/shape-keyshape) and [Custom Item API](/en/docs/api/registerItem). | +| extendedNodeName | String | false | Specifies the inherited node type of the custom node. Declare this property if you want to extend a built-in node. [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) document. | + +### Usage + +```javascript +G6.registerNode( + 'nodeName', + { + /** + * Draw this type of node with label + * @param {Object} cfg The configurations of this type of node + * @param {G.Group} group Graphics group, the container of the shapes of the node + * @return {G.Shape} The keyShape of the type of node. The keyShape can be obtained by node.get('keyShape') + */ + draw(cfg, group) {}, + /** + * Operations to be executed after drawing. No operation by default + * @param {Object} cfg The configurations of this type of node + * @param {G.Group} group Graphics group, the container of the shapes of the node + */ + afterDraw(cfg, group) {}, + /** + * Update the node with label + * @override + * @param {Object} cfg The configurations of this type of node + * @param {Node} node The node + */ + update(cfg, node) {}, + /** + * Operations to be executed after updating. + * @override + * @param {Object} cfg The configurations of this type of node + * @param {Node} node The node + */ + afterUpdate(cfg, node) {}, + /** + * After graph.setItemState(item, state, value) is called, this function will do some responses. + * @param {String} name The name of state + * @param {Object} value The value of the state + * @param {Node} node The node + */ + setState(name, value, node) {}, + /** + * Get the anchor points + * @param {Object} cfg The configurations of this type of node + * @return {Array|null} The array of anchor points. There is no anchor points if it is null. + */ + getAnchorPoints(cfg) {}, + }, + 'extendedNodeName', +); +``` + +## G6.registerEdge(edgeName, options, extendedEdgeName) + +When the built-in edges cannot satisfy your requirments, custom a type of edge by `G6.registerEdge(edgeName, options, extendedEdgeName)`. + +### Parameters + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| edgeName | String | true | The unique name of the custom edge. | +| options | Object | true | The configurations of custom edge, include functions of complete life cycles. Please refer to [Shape Doc](/en/docs/manual/middle/elements/shape/shape-keyshape) and [Custom Item API](/en/docs/api/registerItem). | +| extendedEdgeName | String | false | Specifies the inherited node type of the custom node. Declare this property if you want to extend the a built-in edge. [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge) document. | + +### Usage + +```javascript +G6.registerEdge( + 'edgeName', + { + /** + * Draw this type of edge with label + * @param {Object} cfg The configurations of this type of edge + * @param {G.Group} group Graphics group, the container of the shapes of the edge + * @return {G.Shape} The keyShape of the type of edge. The keyShape can be obtained by edge.get('keyShape') + */ + draw(cfg, group) {}, + /** + * Operations to be executed after drawing. No operation by default + * @param {Object} cfg The configurations of this type of edge + * @param {G.Group} group Graphics group, the container of the shapes of the edge + */ + afterDraw(cfg, group) {}, + /** + * Update the edge with label + * @override + * @param {Object} cfg The configurations of this type of edge + * @param {Edge} edge The edge + */ + update(cfg, edge) {}, + /** + * Operations to be executed after updating. + * @override + * @param {Object} cfg The configurations of this type of edge + * @param {Edge} edge The edge + */ + afterUpdate(cfg, edge) {}, + /** + * After [`graph.setItemState(item, state, value)`] is called, this function will do some responses. + * @param {String} name The name of state + * @param {Object} value The value of the state + * @param {Edge} edge The edge + */ + setState(name, value, edge) {}, + }, + 'extendedEdgeName', +); +``` + +## G6.registerCombo(comboName, options, extendedComboName) + +When the built-in combos cannot satisfy your requirments, custom a type of combo by `G6.registerCombo(comboName, options, extendedComboName)`. + +### Parameters + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| comboName | String | true | The unique name of the custom combo. | +| options | Object | true | The configurations of custom combo, include functions of complete life cycles. Please refer to [Shape Doc](/en/docs/manual/middle/elements/shape/shape-keyshape) and [Custom Item API](/en/docs/api/registerItem). | +| extendedComboName | String | false | Specifies the inherited combo type of the custom combo. Declare this property if you want to extend a built-in combo. [Built-in Combos](/en/docs/manual/middle/elements/combos/defaultCombo) document. | + +### Usage + +```javascript +G6.registerCombo( + 'comboName', + { + /** + * Draw this type of combo with label + * @param {Object} cfg The configurations of this type of combo + * @param {G.Group} group Graphics group, the container of the shapes in the combo + * @return {G.Shape} The keyShape of the type of combo. The keyShape can be obtained by combo.get('keyShape') + */ + draw(cfg, group) {}, + /** + * Operations to be executed after drawing. No operation by default + * @param {Object} cfg The configurations of this type of combo + * @param {G.Group} group Graphics group, the container of the shapes in the combo + */ + afterDraw(cfg, group) {}, + /** + * Update the combo with label + * @override + * @param {Object} cfg The configurations of this type of combo + * @param {Combo} combo The combo + */ + update(cfg, combo) {}, + /** + * Operations to be executed after updating. + * @override + * @param {Object} cfg The configurations of this type of combo + * @param {Combo} combo The combo + */ + afterUpdate(cfg, combo) {}, + /** + * After graph.setItemState(item, state, value) is called, this function will do some responses. + * @param {String} name The name of state + * @param {Object} value The value of the state + * @param {Combo} combo The combo + */ + setState(name, value, combo) {}, + /** + * Get the anchor points + * @param {Object} cfg The configurations of this type of combo + * @return {Array|null} The array of anchor points. There is no anchor points if it is null. + */ + getAnchorPoints(cfg) {}, + }, + 'extendedComboName', +); +``` + +## Usage + +The following code is an example of customizing a type of edge: + +```javascript +import G6 from '@antv/g6'; +G6.registerEdge( + 'edgeName', + { + labelPosition: 'center', + labelAutoRotate: true, + draw(cfg, group) { + // The other functions such as drawShape anddrawLabel can be called in draw(cfg, group) + this.drawShape(); + const labelStyle = this.getLabelStyle(cfg); + // ... + }, + drawShape(cfg, group) { + // + }, + getLabelStyle(cfg) { + // Return the label's style + return {}; + }, + update(cfg, item) { + // Update the item according + }, + }, + 'line', +); +``` + +## Properties + +### labelPosition + +The relative positions of label to the item. `'center'` by default. + +- When registering a type of node by `registerNode`, options of `labelPosition` includes: `'top'`, `'bottom'`, `'left'`, `'right'` and `'center'`; +- When registering a type of edge by `registerEdge`, options of `labelPosition` includes: `'start'`, `'end'` and `'center'`. + +### labelAutoRotate + +> Takes effect only when `registerEdge`. + +Whether to rotate the label according to the edge. `false` by default. + +**Tips: this is an unique property for edge.** + +## Draw Functions + +The parameters for the four functions about draw are the same. Please refer to `draw()`. + +### draw(cfg, group) + +Draw the node or edge, including the label on the it. Return `keyShape` of it. + +**Parameters** + +| Name | Type | Required | Description | +| ----- | ------- | -------- | --------------------------------------- | +| cfg | Object | true | The configurations of the node or edge. | +| group | G.Group | true | The contianer of the node or edge. | + +### afterDraw(cfg, group) + +This function will be called after the node or edge being drawed. It is appropriate for extending graphics or animations for built-in node or edge. + +This [demo](/en/examples/scatter/edge) shows how to add animations in afterDraw. The API about shape's animate can be refered to the [Animate API of G](https://g.antv.vision/en/docs/api/general/element/#%E5%8A%A8%E7%94%BB%E6%96%B9%E6%B3%95) which is the rendering engine of G6. + +## Update Functions + +### update(cfg, item) + +Update the node or edge, including the label on it. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------- | -------- | ---------------------------------------- | +| cfg | Object | true | The configurations for the node or edge. | +| item | G6.Item | true | The item instance of the node or edge. | + +### afterUpdate(cfg, item) + +This function will be called after the node or edge being updated. + +This [demo](/en/examples/scatter/edge) shows how to add animations. The API about shape's animate can be refered to the [Animate API of G](https://g.antv.vision/en/docs/api/general/element/#%E5%8A%A8%E7%94%BB%E6%96%B9%E6%B3%95) which is the rendering engine of G6. + +### shouldUpdate(type) + +Whether to allow the node or edge to be updated. + +**Paramters** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ------------------------------------------------ | +| type | String | true | The type of the item. Options:`'node'`, `'edge'` | + +**Return** + +- The type of return value: Boolean; +- Allow the node or edge to be updated if it returns `true`. + +### setState(name, value, item) + +After [`graph.setItemState(item, state, value)`](/en/docs/api/graphFunc/state#graphsetitemstateitem-state-value) is called, this function will do some responses. + +**Paramters** + +| Name | Type | Required | Description | +| ----- | ------- | -------- | --------------------------------- | +| name | String | true | The name of the state. | +| value | Boolean | true | The value of the state. | +| item | G6.Item | true | The instance of the node or edge. | diff --git a/packages/site/docs/api/registerItem.zh.md b/packages/site/docs/api/registerItem.zh.md new file mode 100644 index 0000000000..d0714b7d2b --- /dev/null +++ b/packages/site/docs/api/registerItem.zh.md @@ -0,0 +1,308 @@ +--- +title: 自定义元素 G6.registerX +order: 7 +--- + +本文介绍的相关方法是在自定义节点(registerNode)或自定义边(registerEdge)的过程中需要部分实现或复写的方法。 + +**友情提示:** 以下属性和 API 方法,全部用于自定义节点和边时候使用,即作为 `G6.registerNode` / `G6.registerEdge` 的第二个参数中的方法。 + +本文介绍 G6 的自定义机制,包括自定义节点、自定义边、自定义 combo、自定义交互行为、自定义布局的相关方法。它们都被挂载在 G6 全局上,通过 `G6.registerXXX` 进行调用。 + +## G6.registerNode(nodeName, options, extendedNodeName) + +当内置节点不满足需求时,可以通过 `G6.registerNode(nodeName, options, extendedNodeName)` 方法自定义节点。 + +### 参数 + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| nodeName | String | true | 自定义节点名称,需保持唯一性。 | +| options | Object | true | 自定义节点时的配置项,配置项中包括完整的生命周期方法,具体请参考:[Shape Doc](/zh/docs/manual/middle/elements/shape/shape-keyshape) 和 [自定义节点与边 API](/zh/docs/api/registerItem)。 | +| extendNodeName | String | false | 自定义节点时可基于内置节点进行定义,该字段表示内置节点名称,所有内置节点请参考:[内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 教程。 | + +### 用法 + +```javascript +G6.registerNode( + 'nodeName', + { + /** + * 绘制节点,包含文本 + * @param {Object} cfg 节点的配置项 + * @param {G.Group} group 图形分组,节点中的图形对象的容器 + * @return {G.Shape} 绘制的图形,通过 node.get('keyShape') 可以获取到 + */ + draw(cfg, group) {}, + /** + * 绘制后的附加操作,默认没有任何操作 + * @param {Object} cfg 节点的配置项 + * @param {G.Group} group 图形分组,节点中的图形对象的容器 + */ + afterDraw(cfg, group) {}, + /** + * 更新节点,包含文本 + * @override + * @param {Object} cfg 节点的配置项 + * @param {Node} node 节点 + */ + update(cfg, node) {}, + /** + * 更新节点后的操作,一般同 afterDraw 配合使用 + * @override + * @param {Object} cfg 节点的配置项 + * @param {Node} node 节点 + */ + afterUpdate(cfg, node) {}, + /** + * 设置节点的状态,主要是交互状态,业务状态请在 draw 方法中实现 + * 单图形的节点仅考虑 selected、active 状态,有其他状态需求的用户自己复写这个方法 + * @param {String} name 状态名称 + * @param {Object} value 状态值 + * @param {Node} node 节点 + */ + setState(name, value, node) {}, + /** + * 获取锚点(相关边的连入点) + * @param {Object} cfg 节点的配置项 + * @return {Array|null} 锚点(相关边的连入点)的数组,如果为 null,则没有锚点 + */ + getAnchorPoints(cfg) {}, + }, + 'extendedNodeName', +); +``` + +## G6.registerEdge(edgeName, options, extendedEdgeName) + +当内置的边不能满足需求时,可以通过 `registerEdge(edgeName, options, extendedEdgeName)` 方法注册自定义的边。 + +### 参数 + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| edgeName | String | true | 自定义边的名称 | +| options | Object | true | 自定义边时的配置项,配置项中包括完整的生命周期方法,具体请参考:[Shape Doc](/zh/docs/manual/middle/elements/shape/shape-keyshape) 和 [自定义节点与边 API](/zh/docs/api/registerItem)。 | +| extendedEdgeName | String | false | 自定义边时可基于内置边进行定义,该字段表示内置边的名称,所有内置边请参考:[内置边](/zh/docs/manual/middle/elements/edges/defaultEdge) 教程。 | + +### 用法 + +```javascript +G6.registerEdge( + 'edgeName', + { + /** + * 绘制边,包含文本 + * @param {Object} cfg 边的配置项 + * @param {G.Group} group 图形分组,边中的图形对象的容器 + * @return {G.Shape} 绘制的图形,通过 node.get('keyShape') 可以获取到 + */ + draw(cfg, group) {}, + /** + * 绘制后的附加操作,默认没有任何操作 + * @param {Object} cfg 边的配置项 + * @param {G.Group} group 图形分组,边中的图形对象的容器 + */ + afterDraw(cfg, group) {}, + /** + * 更新边,包含文本 + * @override + * @param {Object} cfg 边的配置项 + * @param {Edge} edge 边 + */ + update(cfg, edge) {}, + /** + * 更新边后的操作,一般同 afterDraw 配合使用 + * @override + * @param {Object} cfg 边的配置项 + * @param {Edge} edge 边 + */ + afterUpdate(cfg, edge) {}, + /** + * 设置边的状态,主要是交互状态,业务状态请在 draw 方法中实现 + * 单图形的边仅考虑 selected、active 状态,有其他状态需求的用户自己复写这个方法 + * @param {String} name 状态名称 + * @param {Object} value 状态值 + * @param {Edge} edge 边 + */ + setState(name, value, edge) {}, + }, + 'extendedEdgeName', +); +``` + +## G6.registerCombo(comboName, options, extendedComboName) + +当内置 Combo 不满足需求时,可以通过 `G6.registerCombo(comboName, options, extendedComboName)` 方法自定义 Combo。 + +### 参数 + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| comboName | String | true | 自定义 combo 的名称,需保持唯一性。 | +| options | Object | true | 自定义 combo 时的配置项,配置项中包括完整的生命周期方法,具体请参考:[Shape Doc](/zh/docs/manual/middle/elements/shape/shape-keyshape) 和 [自定义节点与边 API](/zh/docs/api/registerItem)。 | +| extendedComboName | String | false | 自定义节点时可基于内置 combo 进行定义,该字段表示内置 combo 名称,所有内置 Combo 请参考:[内置 Combo](/zh/docs/manual/middle/elements/combos/defaultCombo) 教程。 | + +### 用法 + +```javascript +G6.registerCombo( + 'comboName', + { + /** + * 绘制 combo,包含文本 + * @param {Object} cfg combo 的配置项 + * @param {G.Group} group 图形分组,combo 中的图形对象的容器 + * @return {G.Shape} 绘制的图形,通过 combo.get('keyShape') 可以获取到 + */ + draw(cfg, group) {}, + /** + * 绘制后的附加操作,默认没有任何操作 + * @param {Object} cfg combo 的配置项 + * @param {G.Group} group 图形分组,combo 中的图形对象的容器 + */ + afterDraw(cfg, group) {}, + /** + * 更新 combo ,combo 文本 + * @override + * @param {Object} cfg combo 的配置项 + * @param {Combo} combo combo item + */ + update(cfg, combo) {}, + /** + * 更新 combo 后的操作,一般同 afterDraw 配合使用 + * @override + * @param {Object} cfg combo 的配置项 + * @param {Combo} combo combo item + */ + afterUpdate(cfg, combo) {}, + /** + * 设置 combo 的状态,主要是交互状态,业务状态请在 draw 方法中实现 + * 单图形的 combo 仅考虑 selected、active 状态,有其他状态需求的用户自己复写这个方法 + * @param {String} name 状态名称 + * @param {Object} value 状态值 + * @param {Combo} combo combo item + */ + setState(name, value, combo) {}, + /** + * 获取锚点(相关边的连入点) + * @param {Object} cfg combo 的配置项 + * @return {Array|null} 锚点(相关边的连入点)的数组,如果为 null,则没有锚点 + */ + getAnchorPoints(cfg) {}, + }, + 'extendedComboName', +); +``` + +## 用法 + +下面以注册边为例: + +```javascript +import G6 from '@antv/g6'; +G6.registerEdge( + 'edgeName', + { + labelPosition: 'center', + labelAutoRotate: true, + draw(cfg, group) { + // 定义的其他方法,都可以在draw里面调用, 如 drawShape、drawLabel 等。 + this.drawShape(); + const labelStyle = this.getLabelStyle(cfg); + // ... + }, + drawShape(cfg, group) { + // + }, + getLabelStyle(cfg) { + // 根据业务返回 label 的样式 + return {}; + }, + update(cfg, item) { + // 更新绘制的元素 + }, + }, + 'line', +); +``` + +## 属性 + +### labelPosition + +文本相对于图形的位置,默认值为 `'center'`。 + +- 当使用 `registerNode` 注册节点时,`labelPosition` 可选值包括:`'top'`、`'bottom'`、`'left'`、`'right'` 和 `'center'`; +- 当使用 `registerEdge` 注册边时,`labelPosition` 可选值包括:`'start'`、`'end'` 和 `'center'`。 + +### labelAutoRotate + +> 只有在 `registerEdge` 时生效。 + +文本是否跟着线自动旋转,默认值为 `false`。 + +**提示:edge 特有。** + +## 绘制函数 + +绘制部分四个 API 的参数完全相同,参数说明部分参考 `draw()` 方法。 + +### draw(cfg, group) + +绘制节点和边,包括节点和边上的文本,返回图形的 `keyShape`。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------- | -------- | ---------------- | +| cfg | Object | true | 节点或边的配置项 | +| group | G.Group | true | 节点或边的容器 | + +### afterDraw(cfg, group) + +绘制完成以后的操作,用户可继承现有的节点或边,在 `afterDraw()` 方法中扩展图形或添加动画。可参考在 afterDraw 中添加动画的 [demo](/zh/examples/scatter/edge)。图形动画 API 详见 G6 的渲染引擎 [G 的动画相关 API](https://g.antv.vision/zh/docs/api/general/element/#%E5%8A%A8%E7%94%BB%E6%96%B9%E6%B3%95)。 + +## 更新函数 + +### update(cfg, item) + +更新节点或边,包括节点或边上的文本。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------- | -------- | ---------------- | +| cfg | Object | true | 节点或边的配置项 | +| item | G6.Item | true | 节点或边的实例 | + +### afterUpdate(cfg, item) + +更新完以后的操作,如扩展图形或添加动画。可参考添加动画的 [demo](/zh/examples/scatter/edge)。图形动画 API 详见 G6 的渲染引擎 [G 的动画相关 API](https://g.antv.vision/zh/docs/api/general/element/#%E5%8A%A8%E7%94%BB%E6%96%B9%E6%B3%95)。 + +### shouldUpdate(type) + +是否允许更新。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ------------------------------ | +| type | String | true | 元素类型,`'node'` 或 `'edge'` | + +**返回值** + +- 返回值类型:Boolean; +- 返回 `true`,则允许更新,否则不允许更新。 + +### setState(name, value, item) + +用于响应外部对元素状态的改变。当外部调用 [`graph.setItemState(item, state, value)`](/zh/docs/api/graphFunc/state#graphsetitemstateitem-state-value) 时,该函数作出相关响应。主要是交互状态,业务状态请在 `draw()` 方法中实现。单图形的节点仅考虑 `'selected'` 、`'active'` 状态,有其他状态需求的用户可以复写该方法。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ----- | ------- | -------- | ------------------------------------------ | +| name | String | true | 状态名称 | +| value | Boolean | true | 状态是否可用,为 `true` 时可用,否则不可用 | +| item | G6.Item | true | 节点或边的实例 | diff --git a/packages/site/docs/api/registerLayout.en.md b/packages/site/docs/api/registerLayout.en.md new file mode 100644 index 0000000000..1a8e8f1df7 --- /dev/null +++ b/packages/site/docs/api/registerLayout.en.md @@ -0,0 +1,126 @@ +--- +title: G6.registerLayout +order: 11 +--- + +## G6.registerLayout(layoutName, layout) + +When the built-in Layouts cannot satisfy your requirments, custom a type of Layout by `G6.registerLayout(layoutName, layout)`. + +### Parameters + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| layoutName | String | true | The name of the custom layout. | +| layout | Object | true | The configurations of the custom layout. For more information, please refer to [Layout API](/en/docs/manual/middle/layout/custom-layout). | + +### Usage + +```javascript +G6.registerLayout('layoutName', { + /** + * The default configurations will be mixed by configurations from user + */ + getDefaultCfg() { + return {}; + }, + /** + * Initialize + * @param {Object} data The data + */ + init(data) { + const self = this; + self.nodes = data.nodes; + self.edges = data.edges; + }, + /** + * Execute the layout + */ + execute() { + // TODO + }, + /** + * Layout with the data + * @param {Object} data The data + */ + layout(data) { + const self = this; + self.init(data); + self.execute(); + }, + /** + * Update the configurations of the layout, but it does not execute the layout + * @param {Object} cfg The new configurations + */ + updateCfg(cfg) { + const self = this; + Util.mix(self, cfg); + }, + /** + * Destroy the layout + */ + destroy() { + const self = this; + self.positions = null; + self.nodes = null; + self.edges = null; + self.destroyed = true; + }, +}); +``` + +## Initialize + +### init(data) + +Initialize the layout. + +**Paramter** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ----------------------- | +| data | Object | true | The data for the layout | + +### getDefaultCfg() + +Get the default configurations of the layout. + +**Return** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | -------------------------- | +| cfg | Object | true | The default configurations | + +## Layout + +### execute() + +Execute the layout. + +### layout(data) + +Execute layout according to the data. + +**Paramter** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ----------------------- | +| data | Object | true | The data to be arranged | + +## Update + +### updateCfg(cfg) + +Update the configurations for layout. + +**Paramter** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ------------------ | +| cfg | Object | true | New configurations | + +## Destroy + +### destroy() + +Destroy the layout. diff --git a/packages/site/docs/api/registerLayout.zh.md b/packages/site/docs/api/registerLayout.zh.md new file mode 100644 index 0000000000..c502075fad --- /dev/null +++ b/packages/site/docs/api/registerLayout.zh.md @@ -0,0 +1,128 @@ +--- +title: 自定义布局 G6.registerLayout +order: 11 +--- + +## G6.registerLayout(layoutName, layout) + +当内置布局不满足需求时,可以通过 `G6.registerLayout(layoutName, layout)` 方法自定义布局。 + +### 参数 + +| 名称 | 类型 | 是否必选 | 描述 | +| --- | --- | --- | --- | +| layoutName | String | true | 自定义布局名称。 | +| layout | Object | true | 自定义布局的配置项,配置项中包括的方法及作用具体请参考:[Layout API](/zh/docs/manual/middle/layout/custom-layout)。 | + +### 用法 + +```javascript +G6.registerLayout('layoutName', { + /** + * 定义自定义行为的默认参数,会与用户传入的参数进行合并 + */ + getDefaultCfg() { + return {}; + }, + /** + * 初始化 + * @param {Object} data 数据 + */ + init(data) { + const self = this; + self.nodes = data.nodes; + self.edges = data.edges; + }, + /** + * 执行布局 + */ + execute() { + // TODO + }, + /** + * 根据传入的数据进行布局 + * @param {Object} data 数据 + */ + layout(data) { + const self = this; + self.init(data); + self.execute(); + }, + /** + * 更新布局配置,但不执行布局 + * @param {Object} cfg 需要更新的配置项 + */ + updateCfg(cfg) { + const self = this; + Util.mix(self, cfg); + }, + /** + * 销毁 + */ + destroy() { + const self = this; + self.positions = null; + self.nodes = null; + self.edges = null; + self.destroyed = true; + }, +}); +``` + +以下方法为自定义布局时可能需要复写的方法。如果非自定义,使用内置布局方法时,以下方法由 G6 控制并调用,用户不需要了解。 + +## 初始化 + +### init(data) + +初始化布局。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ---------------- | +| data | object | true | 布局中使用的数据 | + +### getDefaultCfg() + +返回布局的默认参数。 + +**返回值** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | -------------- | +| cfg | object | true | 布局的默认参数 | + +## 布局 + +### execute() + +执行布局算法。 + +### layout(data) + +根据传入的数据进行布局。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ---------------- | +| data | object | true | 布局中使用的数据 | + +## 更新 + +### updateCfg(cfg) + +更新布局参数。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ------------ | +| cfg | object | true | 新的布局配置 | + +## 销毁 + +### destroy() + +销毁布局。 diff --git a/packages/site/docs/api/shapeMethods.en.md b/packages/site/docs/api/shapeMethods.en.md new file mode 100644 index 0000000000..94bb240be2 --- /dev/null +++ b/packages/site/docs/api/shapeMethods.en.md @@ -0,0 +1,63 @@ +--- +title: The Common Functions of Shapes +order: 9 +--- + +### attr() + +Get or set the shape's attributes. + +### attr(name) + +Get the shape's attribute named `name`. + +```javascript +const width = shape.attr('width'); +``` + +### attr(name, value) + +Update the shape's attribute named `name` with `value`. + +### attr({...}) + +Update the shape's multiple attributes. + +```javascript +rect.attr({ + fill: '#999', + stroke: '#666', +}); +``` + +### setClip(clipCfg) + +Sets and returns the clip object. + +`clipCfg` + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| type | The type of shape of clipping | String | Options: `'circle'`, `'rect'`, `'ellipse'` | +| x | The x coordinate of the clipping shape | Number | 0 by default. Only takes effect when the `type` is `'circle'`, `'rect'`, or `'ellipse'` | +| y | The y coordinate of the clipping shape | Number | 0 by default. Only takes effect when the `type` is `'circle'`, `'rect'`, or `'ellipse' | +| show | Whether to clip the image | Boolean | Do not clip by default. | +| r | The radius of circle clipping | Number | Takes effect when the `type` is `'circle'` | +| width | The width of the clipping | Number | Takes effect when the `type` is `'rect'` | +| height | The height of the clipping | Number | Takes effect when the `type` is `'rect'` | +| rx | The major radius of the ellipse clipping | Number | Takes effect when the `type` is `'ellipse'` | +| ry | The minor radius of the ellipse clipping | Number | Takes effect when the `type` is `'ellipse'` | + +```javascript +shape.setClip({ + type: 'circle', // circle, rect, ellipse, Polygon, path clip + attrs: { + r: 10, + x: 0, + y: 0, + }, +``` + +### getClip() + +Get the clip object. diff --git a/packages/site/docs/api/shapeMethods.zh.md b/packages/site/docs/api/shapeMethods.zh.md new file mode 100644 index 0000000000..ca2c370ba3 --- /dev/null +++ b/packages/site/docs/api/shapeMethods.zh.md @@ -0,0 +1,65 @@ +--- +title: 各图形的通用方法 Shape Func +order: 9 +--- + +### attr() + +设置或获取实例的绘图属性。 + +### attr(name) + +获取实例的属性值。 + +```javascript +const width = shape.attr('width'); +``` + +### attr(name, value) + +更新实例的单个绘图属性。 + +### attr({...}) + +批量更新实例绘图属性。 + +```javascript +rect.attr({ + fill: '#999', + stroke: '#666' +}); +``` + +### setClip(clipCfg) + +设置并返回裁剪对象。 + +`clipCfg` 配置项 + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| type | 裁剪的图片形状 | String | 支持 `'circle'`、`'rect'`、`'ellipse'` | +| x | 裁剪图形的 x 坐标 | Number | 默认为 0,类型为 `'circle'`、`'rect'`、`'ellipse'` 时生效 | +| y | 裁剪图形的 y 坐标 | Number | 默认为 0,类型为 `'circle'`、`'rect'`、`'ellipse'` 时生效 | +| show | 是否启用裁剪功能 | Boolean | 默认不裁剪,值为 `false` | +| r | 剪裁圆形的半径 | Number | 剪裁 type 为  `'circle'` 时生效 | +| width | 剪裁矩形的宽度 | Number | 剪裁 type 为 `'rect'` 时生效 | +| height | 剪裁矩形的长度 | Number | 剪裁 type 为 `'rect'` 时生效 | +| rx | 剪裁椭圆的长轴半径 | Number | 剪裁 type 为 `'ellipse'` 时生效 | +| ry | 剪裁椭圆的短轴半径 | Number | 剪裁 type 为 `'ellipse'` 时生效 | + +用法 + +```javascript +shape.setClip({ + type: 'circle', // 支持 circle、rect、ellipse、Polygon 及自定义 path clip + attrs: { + r: 10, + x: 0, + y: 0, + }, +``` + +### getClip() + +获取裁剪对象。 diff --git a/packages/site/docs/api/shapeProperties.en.md b/packages/site/docs/api/shapeProperties.en.md new file mode 100644 index 0000000000..656ec56e5f --- /dev/null +++ b/packages/site/docs/api/shapeProperties.en.md @@ -0,0 +1,554 @@ +--- +title: Shape Style Properties +order: 8 +--- + +Shape is the basic element on an item (node/edge). The `style` of a node or an edge corresponds to the shape properties of its keyShape (key shape). The `style` in `labelCfg` of a label on a node or an edge corresponds to the properties of text shape. + +```javascript +group.addShape('rect', { + attrs: { + fill: 'red', + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowColor: 'blue', + shadowBlur: 10, + opacity: 0.8, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', +}); +``` + +G6 has these shapes: + +- [circle](#circle); +- [rect](#rect); +- [ellipse](#ellipse); +- [polygon](#polygon); +- [image](#image); +- [marker](#marker); +- [path](#path); +- [text](#text); +- [dom(svg)](#dom-svg): DOM (available only when the `renderer` of Graph instance is `'svg'`). + +## Common Property + +### name + + _String_ **required** + +Must be assigned in G6 3.3 and later versions. It can be any string you want but must be unique in a custom node/edge/combo type. Otherwise, the style updating might be wrong. + +### fill + + _String_ **optional** + +The color(RGB or Hex) or [gradient](/en/docs/manual/middle/elements/advanced-style/gradient) color for filling. The corresponding property in canvas is `fillStyle`. Examples: `rgb(18, 150, 231)`,`#c193af`,`l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff`, `r(0.5, 0.5, 0.1) 0:#ffffff 1:#1890ff`. + +### stroke + + _String_ **optional** + +The color(RGB or Hex) or [gradient](/en/docs/manual/middle/elements/advanced-style/gradient) color for stroke. The corresponding property in canvas is `strokeStyle`. Examples: `rgb(18, 150, 231)`,`#c193af`,`l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff`, `r(0.5, 0.5, 0.1) 0:#ffffff 1:#1890ff`. + +### lineWidth + + _Number_ **optional** + +The width of the stroke. + +### lineDash + + _Number | Number[]_ **optional** + +The lineDash of the stroke. If its type is `Number[]`, the elements in the array are the lengths of the lineDash. + +### shadowColor + + _String_ **optional** + +The color of the shadow. + +### shadowBlur + + _Number_ **optional** + +The blur level for shadow. Larger the value, more blur. + +### shadowOffsetX + + _Number_ **optional** + +The horizontal offset of the shadow. + +### shadowOffsetY + + _Number_ **optional** + +The vertical offset of the shadow. + +### opacity + + _Number_ **optional** + +The opacity (alpha value) of the shape. The corresponding property in canvas is `globalAlpha`. + +### fillOpacity + + _Number_ **optional** + +The filling opacity (alpha value) of the shape. The priority is higher than `opacity`. Range [0, 1]. + +### strokeOpacity + + _Number_ **optional** + +The stroke opacity (alpha value) of the shape. The priority is higher than `opacity`. Range [0, 1]. + +### cursor + + _String_ **optional** + +The type of the mouse when hovering the node. The options are the same as [cursor in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor). + +## Circle + +```javascript +group.addShape('circle', { + attrs: { + x: 100, + y: 100, + r: 50, + fill: 'blue', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', +}); +``` + +### x + + _Number_ **optional** + +The x of the center of the circle. + +### y + + _Number_ **optional** + +The y of the center of the circle. + +### r + + _Number_ **optional** + +The radius of the circle. + +## Ellipse + +```javascript +group.addShape('ellipse', { + attrs: { + x: 100, + y: 100, + rx: 50, + ry: 50, + fill: 'blue', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'ellipse-shape', +}); +``` + +### x + + _Number_ **optional** + +The x of the center of the ellipse. + +### y + + _Number_ **optional** + +The y of the center of the ellipse. + +### rx + + _Number_ **optional** + +The horizontal raidus of the ellipse. + +### ry + + _Number_ **optional** + +The vertical raidus of the ellipse. + +## Image + +```javascript +group.addShape('image', { + attrs: { + x: 0, + y: 0, + img: 'https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'image-shape', +}); +``` + +### x + + _Number_ **optional** + +The x of the left top of the image. + +### y + + _Number_ **optional** + +The y of the left top of the image. + +### width + + _Number_ **optional** + +The width of the image. + +### height + + _Number_ **optional** + +The height of the image. + +### img + + _String_ **optional** + +The source of the image.G6 supports multiple image formats:
- url
- ImageData
- Image
- canvas
. + +## Marker + +```javascript +// use the built-in symbol +group.addShape('marker', { + attrs: { + x: 10, + y: 10, + r: 10, + symbol: 'triangle-down', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'marker-shape', +}); + +// custom the symbol with path +group.addShape('marker', { + attrs: { + x: 10, + y: 10, + r: 10, + symbol: function (x, y, r) { + return [['M', x, y], ['L', x + r, y + r], ['L', x + r * 2, y], ['Z']]; + }, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'marker-shape', +}); +``` + +### x + + _Number_ **optional** + +The x of the center of the marker. + +### y + + _Number_ **optional** + +The y of the center of the marker. + +### r + + _Number_ **optional** + +The radius of the marker. + +### symbol + + _String | Function_ **optional** + +The shape name.There are several built-in shapes: `'circle'`, `'square'`, `'diamond'`, `'triangle'`, `'triangle-down'`, you can use them with the String names. And user could customize a shape as marker. + +## Polygon + +```javascript +group.addShape('polygon', { + attrs: { + points: [ + [30, 30], + [40, 20], + [30, 50], + [60, 100], + ], + fill: 'red', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'polygon-shape', +}); +``` + +### points + + _Array_ **optional** + +The coordinates of the points on the polygon. + +## Rect + +```javascript +group.addShape('rect', { + attrs: { + x: 150, + y: 150, + width: 150, + height: 150, + stroke: 'black', + radius: [2, 4], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', +}); +``` + +### x + + _Number_ **optional** + +The x of left top of the rect. + +### y + + _Number_ **optional** + +The y of left top of the rect. + +### width + + _Number_ **optional** + +The width of the rect. + +### height + + _Number_ **optional** + +The height of the rect. + +### radius + + _Number | Number[]_ **optional** + +The border radius. It can be an integer or an array, representing the border radii of lefttop, righttop, rightbottom, leftbotton respectively.
- `radius = 1` or `radius = [ 1 ]` is equal to `radius = [ 1, 1, 1, 1 ]`
- `radius = [ 1, 2 ]` is equal to `radius = [ 1, 2, 1, 2 ]`
- `radius: [ 1, 2, 3 ]` is equal to `radius: [ 1, 2, 3, 2 ]`
+ +## Path + +⚠️Attention: When the edge is too thin to be hitted by mouse, set **lineAppendWidth** to enlarge the hitting area. + +```javascript +group.addShape('path', { + attrs: { + startArrow: { + // The custom arrow is a path points at (0, 0), and its tail points to the positive direction of x-axis + path: 'M 0,0 L 20,10 L 20,-10 Z', + // the offset of the arrow, nagtive value means the arrow is moved alone the positive direction of x-axis + // d: -10 + }, + endArrow: { + // The custom arrow is a path points at (0, 0), and its tail points to the positive direction of x-axis + path: 'M 0,0 L 20,10 L 20,-10 Z', + // the offset of the arrow, nagtive value means the arrow is moved alone the positive direction of x-axis + // d: -10 + }, + path: [ + ['M', 100, 100], + ['L', 200, 200], + ], + stroke: '#000', + lineWidth: 8, + lineAppendWidth: 5, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', +}); +``` + +### path + + _String | Array_ **optional** + +The path. Refer to [SVG path](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths). + +### startArrow + + _Boolean | Object_ **optional** + +The arrow on the start of the path. When `startArrow` is `true`, show a default arrow on the start of the path. User can custom an arrow by path. + +### endArrow + + _Boolean | Object_ **optional** + +The arrow on the end of the path. When `endArrow` is `true`, show a default arrow on the end of the path. User can custom an arrow by path. + +### lineAppendWidth + + _Number_ **optional** + +The hitting area of the path. Enlarge the hitting area by enlarging its value. + +### lineCap + + _String_ **optional** _default:_ `'miter'` + +The style of two ends of the path. Options:
- `'bevel'`
- `'round'`
- `'miter'`(default) + +### lineJoin + + _String_ **optional** _default:_ `'miter'` + +The style of the intersection of two path. Options:
- `'bevel'`
- `'round'`
- `'miter'`(default) + +### lineWidth + + _Number_ **optional** + +The line width of the current path. + +### miterLimit + + _Number_ **optional** + +The maximum miter length. + +### lineDash + + _Number | Number[]_ **optional** + +The style of the dash line. It is an array that describes the length of gaps and line segments. If the number of the elements in the array is odd, the elements will be dulplicated. Such as [5, 15, 25] will be regarded as [5, 15, 25, 5, 15, 25]. + +## Text + +```javascript +group.addShape('text', { + attrs: { + text: 'test text', + fill: 'red', + fontWeight: 400, + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowColor: 'blue', + shadowBlur: 10, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', +}); +``` + +### text + + _String_ **optional** + +The text content of the text shape. + + +### textAlign + + _String_ **optional** + +The align way of the text. Options: `'center'` / `'end'` / `'left'` / `'right'` / `'start'`. `'start'` by default. + +### textBaseline + + _String_ **optional** + +The base line of the text. Options:
`'top'` / `'middle'` / `'bottom'` / `'alphabetic'` / `'hanging'`. `'bottom'` by default. + +### fontStyle + + _String_ **optional** + +The font style of the text. The corresponding property in CSS is `font-style`. + +### fontVariant + + _String_ **optional** + +The font variant of the text. The corresponding property in CSS is `font-variant`. + +### fontWeight + + _Number_ **optional** + +The font weight of the text. The corresponding property in CSS is `font-weight`. + +### fontSize + + _Number_ **optional** + +The font size of the text. The corresponding property in CSS is `font-size`. + +### fontFamily + + _String_ **optional** + +The font family of the text. The corresponding property in CSS is `font-family`. + +### lineHeight + + _Number_ **optional** + +Line height of the text. The corresponding property in CSS is `line-height`. + +## DOM (svg) + +> This shape is available only when the `renderer` is assgined to `'svg'` for graph instance. + +⚠️ Attention: + +- Only support native HTML DOM, but not react or other components; +- If you custom a Node type or an Edge type with dom shape, please use the original DOM events instead of events of G6. +- In Safari, if you assign `position:relative` for the a dom node, the rendered position might be unexpected. It is related to the [foreignObject bug of Safari](https://bugs.webkit.org/show_bug.cgi?id=23113). [Issus](https://github.com/antvis/G6/issues/2990). + + +```javascript +group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + // DOM's html + html: ` +
+
+ img +
+ ${cfg.label} +
+ `, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'dom-shape', + draggable: true, +}); +``` + +### html + + _String_ **optional** + +The HTML value for DOM shape. diff --git a/packages/site/docs/api/shapeProperties.zh.md b/packages/site/docs/api/shapeProperties.zh.md new file mode 100644 index 0000000000..a696906413 --- /dev/null +++ b/packages/site/docs/api/shapeProperties.zh.md @@ -0,0 +1,555 @@ +--- +title: 图形样式属性 Shape Attr +order: 8 +--- + +图形是组成图上一个元素(节点/边)的基本单位。节点/边的 `style` 属性即对应了各自 keyShape(关键图形)的图形属性。节点或边上标签 `labelCfg` 中的 `style` 属性对应了 text 图形的图形属性。除一些[通用属性](#通用属性)外,不同图形有各自的特殊属性。 + +```javascript +group.addShape('rect', { + attrs: { + fill: 'red', + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowColor: 'blue', + shadowBlur: 10, + opacity: 0.8, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'rect-shape', +}); +``` + +G6 支持以下图形: + +- [circle](#圆图形-circle):圆; +- [rect](#矩形图形-rect):矩形; +- [ellipse](#椭圆图形-ellipse):椭圆; +- [polygon](#多边形图形-polygon):多边形; +- [image](#图片图形-image):图片; +- [marker](#标记图形-marker):标记; +- [path](#路径-path):路径; +- [text](#文本-text):文本; +- [dom(svg)](#dom-svg):DOM(图渲染方式 `renderer` 为 `'svg'` 时可用)。 + +## 通用属性 + +### name + + _String_ **required** + +图形名称标识,G6 3.3 版本以上必须配置。**且在统一自定义元素类型中,值必须唯一。** 否则可能导致图形样式更新与恢复的错误。 + +### fill + + _String_ **optional** + +设置用于填充绘画的颜色(RGB 或 16 进制)、[渐变](/zh/docs/manual/middle/elements/advanced-style/gradient)或模式,对应 Canvas 属性 `fillStyle` 。取值示例:`rgb(18, 150, 231)`,`#c193af`,`l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff`, `r(0.5, 0.5, 0.1) 0:#ffffff 1:#1890ff`。 + +### stroke + + _String_ **optional** + +设置用于笔触的颜色(RGB 或 16 进制)、[渐变](/zh/docs/manual/middle/elements/advanced-style/gradient)或模式,对应 Canvas 属性 `strokeStyle`。取值示例:`rgb(18, 150, 231)`,`#c193af`,`l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff`, `r(0.5, 0.5, 0.1) 0:#ffffff 1:#1890ff`。 + +### lineWidth + + _Number_ **optional** + +描边宽度。 + +### lineDash + + _Number | Number[]_ **optional** + +描边虚线,Number[] 类型中数组元素分别代表实、虚长度。 + +### shadowColor + + _String_ **optional** + +设置用于阴影的颜色。 + +### shadowBlur + + _Number_ **optional** + +设置用于阴影的模糊级别,数值越大,越模糊。 + +### shadowOffsetX + + _Number_ **optional** + +设置阴影距形状的水平距离。 + +### shadowOffsetY + + _Number_ **optional** + +设置阴影距形状的垂直距离。 + +### opacity + + _Number_ **optional** + +设置绘图的当前 alpha 或透明值,范围 [0, 1],对应 Canvas 属性 `globalAlpha`。 + +### fillOpacity + + _Number_ **optional** + +设置填充的 alpha 或透明值,优先级高于 opacity,范围 [0, 1]。 + +### strokeOpacity + + _Number_ **optional** + +设置描边的 alpha 或透明值,优先级高于 opacity,范围 [0, 1]。 + +### cursor + + _String_ **optional** + +鼠标在该节点上时的鼠标样式,[CSS 的 cursor](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) 选项都支持。 + +## 圆图形 Circle + +```javascript +group.addShape('circle', { + attrs: { + x: 100, + y: 100, + r: 50, + fill: 'blue', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'circle-shape', +}); +``` + +### x + + _Number_ **optional** + +圆心的 x 坐标。 + +### y + + _Number_ **optional** + +圆心的 y 坐标。 + +### r + + _Number_ **optional** + +圆的半径。 + +## 椭圆图形 Ellipse + +```javascript +group.addShape('ellipse', { + attrs: { + x: 100, + y: 100, + rx: 50, + ry: 50, + fill: 'blue', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'ellipse-shape', +}); +``` + +### x + + _Number_ **optional** + +圆心的 x 坐标。 + +### y + + _Number_ **optional** + +圆心的 y 坐标。 + +### rx + + _Number_ **optional** + +水平半径。 + +### ry + + _Number_ **optional** + +垂直半径。 + +## 图片图形 Image + +```javascript +group.addShape('image', { + attrs: { + x: 0, + y: 0, + img: 'https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'image-shape', +}); +``` + +### x + + _Number_ **optional** + +图片左上角的 x 坐标。 + +### y + + _Number_ **optional** + +图片左上角的 y 坐标。 + +### width + + _Number_ **optional** + +图片宽度。 + +### height + + _Number_ **optional** + +图片高度。 + +### img + + _String_ **optional** + +图片源,G6 支持多种格式的图片:url,ImageData,Image,canvas。 + +## 标记图形 Marker + +```javascript +// 使用内置 symbol +group.addShape('marker', { + attrs: { + x: 10, + y: 10, + r: 10, + symbol: 'triangle-down', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'marker-shape', +}); + +// 使用路径自定义 symbol +group.addShape('marker', { + attrs: { + x: 10, + y: 10, + r: 10, + symbol: function (x, y, r) { + return [['M', x, y], ['L', x + r, y + r], ['L', x + r * 2, y], ['Z']]; + }, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'marker-shape', +}); +``` + +### x + + _Number_ **optional** + +标记图形左上角的 x 坐标。 + +### y + + _Number_ **optional** + +标记图形左上角的 y 坐标。 + +### r + + _Number_ **optional** + +形状半径。 + +### symbol + + _String | Function_ **optional** + +指定形状。我们已经内置了一些常用形状,如圆形 `'circle'`,矩形  `'square'`,菱形  `'diamond'`,三角形  `'triangle'`,倒三角形 `'triangle-down'`,这些内置形状只需要直接将响应 String 赋值给 symbol。也可以是自定义的 path 路径的函数。 + +## 多边形图形 Polygon + +### points + +```javascript +group.addShape('polygon', { + attrs: { + points: [ + [30, 30], + [40, 20], + [30, 50], + [60, 100], + ], + fill: 'red', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'polygon-shape', +}); +``` + +### points + + _Array_ **optional** + +多边形的所有端点坐标。 + +## 矩形图形 Rect + +```javascript +group.addShape('rect', { + attrs: { + x: 150, + y: 150, + width: 150, + height: 150, + stroke: 'black', + radius: [2, 4], + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'rect-shape', +}); +``` + +### x + + _Number_ **optional** + +矩形左上角的 x 坐标。 + +### y + + _Number_ **optional** + +矩形左上角的 y 坐标。 + +### width + + _Number_ **optional** + +矩形的宽度。 + +### height + + _Number_ **optional** + +矩形的高度。 + +### radius + + _Number | Number[]_ **optional** + +定义圆角。支持整数或数组形式,分别对应左上、右上、右下、左下角的半径:
- radius 缩写为 1 或 [ 1 ] 相当于 [ 1, 1, 1, 1 ]
- radius 缩写为 [ 1, 2 ] 相当于 [ 1, 2, 1, 2 ]
- radius 缩写为 [ 1, 2, 3 ] 相当于 [ 1, 2, 3, 2 ]
。 + +## 线条 Path + +⚠️ 注意: 当边太细交互不易命中时,请设置 **lineAppendWidth** 属性值。 + +```javascript +group.addShape('path', { + attrs: { + startArrow: { + // 自定义箭头指向(0, 0),尾部朝向 x 轴正方向的 path + path: 'M 0,0 L 20,10 L 20,-10 Z', + // 箭头的偏移量,负值代表向 x 轴正方向移动 + // d: -10, + }, + endArrow: { + // 自定义箭头指向(0, 0),尾部朝向 x 轴正方向的 path + path: 'M 0,0 L 20,10 L 20,-10 Z', + // 箭头的偏移量,负值代表向 x 轴正方向移动 + // d: -10, + }, + path: [ + ['M', 100, 100], + ['L', 200, 200], + ], + stroke: '#000', + lineWidth: 8, + lineAppendWidth: 5, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'path-shape', +}); +``` + +### path + + _String | Array_ **optional** + +线条路径,可以是 String 形式,也可以是线段的数组。格式参考:[SVG path](https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Paths)。 + +### startArrow + + _Boolean | Object_ **optional** + +起始端的箭头,为 `true` 时为默认的箭头效果,也可以是一个自定义箭头。 + +### endArrow + + _Boolean | Object_ **optional** + +末尾端的箭头,为 `true` 时为默认的箭头效果,也可以是一个自定义箭头。 + +### lineAppendWidth + + _Number_ **optional** + +边的击中范围。提升边的击中范围,扩展响应范围,数值越大,响应范围越广。 + +### lineCap + + _String_ **optional** + +设置线条的结束端点样式。可选:
- `'bevel'`: 斜角
- `'round'`: 圆角
- `'miter'`: 尖角 (默认)。 + +### lineJoin + + _String_ **optional** + +设置两条线相交时,所创建的拐角形状。可选:
- `'bevel'`: 斜角
- `'round'`: 圆角
- `'miter'`: 尖角 (默认)。 + +### lineWidth + + _Number_ **optional** + +设置当前的线条宽度。 + +### miterLimit + + _Number_ **optional** + +设置最大斜接长度。 + +### lineDash + + _Number | Number[]_ **optional** + +设置线的虚线样式,可以指定一个数组。一组描述交替绘制线段和间距(坐标空间单位)长度的数字。 如果数组元素的数量是奇数, 数组的元素会被复制并重复。例如, [5, 15, 25] 会变成 [5, 15, 25, 5, 15, 25]。可参考[setLineDash](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash)。 + +## 文本 Text + +```javascript +group.addShape('text', { + attrs: { + text: 'test text', + fill: 'red', + fontWeight: 400, + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowColor: 'blue', + shadowBlur: 10, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'text-shape', +}); +``` + +### text + + _String_ **optional** + +文本文字内容。 + + +### textAlign + + _String_ **optional** + +设置文本内容的当前对齐方式。支持的属性:`center` / `end` / `left` / `right` / `start`,默认值为 `start`。 + +### textBaseline + + _String_ **optional** + +设置在绘制文本时使用的当前文本基线。支持的属性:
`top` / `middle` / `bottom` / `alphabetic` / `hanging`。默认值为 `bottom`。 + +### fontStyle + + _String_ **optional** + +字体样式。对应 `font-style`。 + +### fontVariant + + _String_ **optional** + +设置为小型大写字母字体。对应 `font-variant`。 + +### fontWeight + + _Number_ **optional** + +字体粗细。对应 `font-weight`。 + +### fontSize + + _Number_ **optional** + +字体大小。对应 `font-size`。 + +### fontFamily + + _String_ **optional** + +字体系列。对应 `font-family`。 + +### lineHeight + + _Number_ **optional** + +行高。对应 `line-height`。 + +## DOM (svg) + +> 仅在 Graph 的 `renderer` 为 `'svg'` 时可以使用。 + +⚠️ 注意: + +- 只支持原生 HTML DOM,不支持各类 react、vue 组件; +- 使用 `'dom'` 进行自定义的节点或边,不支持 G6 的交互事件,请使用原生 DOM 的交互事件; +- 在 Safari 中,若 dom 节点被设置了 `position:relative`,将会导致渲染异常。该问题与 [Safari 的 foreignObject bug](https://bugs.webkit.org/show_bug.cgi?id=23113) 有关。[Issus](https://github.com/antvis/G6/issues/2990)。 + +```javascript +group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + // DOM's html + html: ` +
+
+ img +
+ ${cfg.label} +
+ `, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'dom-shape', + draggable: true, +}); +``` + +### html + + _String_ **optional** + +DOM 的 HTML 值。 diff --git a/packages/site/docs/api/treeGraphLayout/compactBox.en.md b/packages/site/docs/api/treeGraphLayout/compactBox.en.md new file mode 100644 index 0000000000..eab4eccbe5 --- /dev/null +++ b/packages/site/docs/api/treeGraphLayout/compactBox.en.md @@ -0,0 +1,127 @@ +--- +title: CompactBox +order: 1 +--- + +CompactBox is the default layout for TreeGraph. It will consider the bounding box of each node when layout. It comes from classical Reingold–Tilford tidy layout algorithm. + +img + +### layoutCfg.direction + +**Type**: String
**Options**: 'LR' | 'RL' | 'TB' | 'BT' | 'H' | 'V'
**Default**: 'LR'
**Required**: false
**Description**: The direction of layout. + +- TB —— Root is on the top, layout from the top to the bottom + +img + +- BT —— Root is on the bottom, layout from the bottom to the top + +img + +- LR —— Root is on the left, layout from the left to the right + +img + +- RL —— Root is on the right, layout from the right to the left + +img + +- H —— Root is on the middle, layout in horizontal symmetry. + +img + +- V —— Root is on the middle, layout in vertical symmetry. + +img + + +### layoutCfg.getSide + +**Type**: Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'test-child-id') return 'right'; + return 'left'; +}; +``` + +**Default**: 'right'
**Required**: false
**Description**: The callback function of node position(left or right of root node). Only affects the nodes which are connected to the root node directly. And the descendant nodes will be placed according to it + + +### layoutCfg.getId + +**Type**: Function
**Example**: + +```javascript +(d) => { + // d is a node + return d.id + '_node'; +}; +``` + +**Required**: false
**Description**: Sets the id for each node + +### layoutCfg.getWidth + +**Type**: Number | Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**Required**: false
**Description**: The width of each node + +### layoutCfg.getHeight + +**Type**: Number | Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**Required**: false
**Description**: The height of each node + +### layoutCfg.getHGap + +**Type**: Number | Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**Default**: 18
**Required**: false
**Description**: The horizontal separation of nodes + +### layoutCfg.getVGap + +**Type**: Number | Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**Default**: 18
**Required**: false
**Description**: The vertical separation of nodes + +### layoutCfg.radial + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: If layout the graph in radial style. If `radial` is `true`, we recommend to set `direction` to `'LR'` or `'RL'`:
+ +img diff --git a/packages/site/docs/api/treeGraphLayout/compactBox.zh.md b/packages/site/docs/api/treeGraphLayout/compactBox.zh.md new file mode 100644 index 0000000000..729462fb0e --- /dev/null +++ b/packages/site/docs/api/treeGraphLayout/compactBox.zh.md @@ -0,0 +1,126 @@ +--- +title: CompactBox 紧凑树 +order: 1 +--- + +紧凑盒树布局。这是树图的默认布局,其特点是布局时统合考虑每个树节点的包围盒,由经典的 Reingold–Tilford tidy 布局算法演进而来,适合于脑图等应用场景。 + +img + +### layoutCfg.direction + +**类型**:String
**可选值**:'LR' | 'RL' | 'TB' | 'BT' | 'H' | 'V'
**默认值**:'LR'
**是否必须**:false
**说明**:树布局的方向,其他选项说明 + +- TB —— 根节点在上,往下布局 + +img + +- BT —— 根节点在下,往上布局 + +img + +- LR —— 根节点在左,往右布局 + +img + +- RL —— 根节点在右,往左布局 + +img + +- H —— 根节点在中间,水平对称布局 + +img + +- V —— 根节点在中间,垂直对称布局 + +img + +### layoutCfg.getSide + +**类型**:Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'test-child-id') return 'right'; + return 'left'; +}; +``` + +**默认值**:'right'
**是否必须**:false
**说明**:节点排布在根节点的左侧/右侧。若设置了该值,则所有节点会在根节点同一侧,即 direction = 'H' 不再起效。若该参数为回调函数,则可以指定每一个节点在根节点的左/右侧。 + + +### layoutCfg.getId + +**类型**: Function
**示例**: + +```javascript +(d) => { + // d is a node + return d.id + '_node'; +}; +``` + +**是否必须**: false
**说明**: 节点 id 的回调函数 + +### layoutCfg.getWidth + +**类型**:Number | Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**是否必须**:false
**说明**:每个节点的宽度 + +### layoutCfg.getHeight + +**类型**:Number | Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**是否必须**:false
**说明**:每个节点的高度 + +### layoutCfg.getHGap + +**类型**:Number | Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**默认值**:18
**是否必须**:false
**说明**:每个节点的水平间隙 + +### layoutCfg.getVGap + +**类型**:Number | Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**默认值**:18
**是否必须**:false
**说明**:每个节点的垂直间隙 + +### layoutCfg.radial + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否按照辐射状布局。若 `radial` 为 `true`,建议 `direction` 设置为 `'LR'` 或 `'RL'`: + +img diff --git a/packages/site/docs/api/treeGraphLayout/dendrogram.en.md b/packages/site/docs/api/treeGraphLayout/dendrogram.en.md new file mode 100644 index 0000000000..de493ec428 --- /dev/null +++ b/packages/site/docs/api/treeGraphLayout/dendrogram.en.md @@ -0,0 +1,48 @@ +--- +title: Dendrogram +order: 2 +--- + +Dendrogram arranges all the leaves on the same level. It is appropriate for hierarchical clustering. It does not consider the node size, which will be regarded as 1px. + +img + +### layoutCfg.direction + +**Type**: String
**Options**: 'LR' | 'RL' | 'TB' | 'BT' | 'H' | 'V'
**Default**: 'LR'
**Required**: false
**Description**: The direction of layout. + +- TB —— Root is on the top, layout from the top to the bottom + +img + +- BT —— Root is on the bottom, layout from the bottom to the top + +img + +- LR —— Root is on the left, layout from the left to the right + +img + +- RL —— Root is on the right, layout from the right to the left + +img + +- H —— Root is on the middle, layout in horizontal symmetry. + +img + +- V —— Root is on the middle, layout in vertical symmetry. + +img + +### layoutCfg.nodeSep + +**Type**: Number
**Default**: 0
**Required**: false
**Description**: Node separation + +### layoutCfg.rankSep + +**Type**: Number
**Default**: 0
**Required**: false
**Description**: Level separation + +### layoutCfg.radial + +**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Wheter layout the graph in radial style. If `radial` is `true`, we recommend to set `direction` to `'LR'` or `'RL'`:
img diff --git a/packages/site/docs/api/treeGraphLayout/dendrogram.zh.md b/packages/site/docs/api/treeGraphLayout/dendrogram.zh.md new file mode 100644 index 0000000000..6191577d19 --- /dev/null +++ b/packages/site/docs/api/treeGraphLayout/dendrogram.zh.md @@ -0,0 +1,50 @@ +--- +title: Dendrogram 生态树 +order: 2 +--- + +生态树布局的特点是所有子节点布局在同一层级,不考虑节点大小,每个节点被当成 1px 处理。适用于表示层次聚类。 + +img + +### layoutCfg.direction + +**类型**:String
**可选值**:'LR' | 'RL' | 'TB' | 'BT' | 'H' | 'V'
**默认值**:'LR'
**是否必须**:false
**说明**:树布局的方向,默认为 `'LR'`,其他选项说明 + +- TB —— 根节点在上,往下布局 + +img + +- BT —— 根节点在下,往上布局 + +img + +- LR —— 根节点在左,往右布局 + +img + +- RL —— 根节点在右,往左布局 + +img + +- H —— 根节点在中间,水平对称布局 + +img + +- V —— 根节点在中间,垂直对称布局 + +img + +### layoutCfg.nodeSep + +**类型**:Number
**默认值**:0
**是否必须**:false
**说明**:节点间距 + +### layoutCfg.rankSep + +**类型**:Number
**默认值**:0
**是否必须**:false
**说明**:层与层之间的间距 + +### layoutCfg.radial + +**类型**:Boolean
**默认值**:false
**是否必须**:false
**说明**:是否按照辐射状布局。若 `radial` 为 `true`,建议 `direction` 设置为 `'LR'` 或 `'RL'`: + +img diff --git a/packages/site/docs/api/treeGraphLayout/guide.en.md b/packages/site/docs/api/treeGraphLayout/guide.en.md new file mode 100644 index 0000000000..c2fba7ba77 --- /dev/null +++ b/packages/site/docs/api/treeGraphLayout/guide.en.md @@ -0,0 +1,43 @@ +--- +title: Outline +order: 0 +--- + +## Overview + +Similar to [Graph Layout](/en/docs/api/graphLayout/guide), G6 provides several built-in TreeGraph layouts, which can be [configured to Graph](#Usage) by `layout`. The differences between TreeGraph and Graph: + +- When instantiating a TreeGraph, the `layout` is required, but it is not required for Graph; +- TreeGaph layouts does not support being instantiated and used independently; +- You can not custom a new type of TreeGraph Layout by `G6.registerLayout`. + +Notice that the layouts for TreeGraph cannot be used on Graph. + +There are four built-in layouts for TreeGraph: + +- [CompactBox](./compactBox) +- [Dendrogram](./dendrogram) +- [Indented](./indented) +- [Mindmap](./mindmap) + +## Usage + +```javascript +const graph = new G6.TreeGraph({ + // ... // Other configurations + layout: { // Object,required for TreeGraph + type: 'dendrogram', + ... // Other configurations for the layout + } +}); +``` + +## Common Configurations + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| type | String | dendrogram | The type of layout. Options: `'dendrogram'`, `'compactBox'`, `'mindmap'`, and `'indeted'`. | +| direction | String | LR | The direction of layout. Options: `'LR'` , `'RL'` , `'TB'` , `'BT'` , `'H'` , and `'V'`.
L: Left; R: right; T: top; B: bottom; H: horizontal; V: vertical. | +| getChildren | Function | | Return all the children nodes of the current node. | + +⚠️Attention: When`type='indeted'`, `direction` can only be `'LR'`, `'RL'`, and `'H'`. diff --git a/packages/site/docs/api/treeGraphLayout/guide.zh.md b/packages/site/docs/api/treeGraphLayout/guide.zh.md new file mode 100644 index 0000000000..1ffc957cfb --- /dev/null +++ b/packages/site/docs/api/treeGraphLayout/guide.zh.md @@ -0,0 +1,45 @@ +--- +title: 导览及使用 +order: 0 +--- + +## 树图布局总览 + +与 [Graph 的布局](/zh/docs/api/graphLayout/guide) 类似,G6 为树图 TreeGraph 提供了一些内置布局算法。可以在[实例化图时配置](#使用方法)。与 [Graph 的布局](/zh/docs/api/graphLayout/guide) 不同的是: + +- 实例化树图时必须通过配置 `layout` 配置布局,而 Graph 不配置 `layout` 时将会使用数据中的位置信息或随机布局; +- 树图布局不支持独立使用; +- 树图布局不支持自定义。 + +注意,Graph 布局与 TreeGaph 布局相互不通用。 + +G6 的内置树图布局有: + +- [CompactBox 紧凑树布局](./compactBox) +- [Dendrogram 生态树布局](./dendrogram) +- [Indented 缩进树布局](./indented) +- [Mindmap 脑图树布局](./mindmap) + +## 使用方法 + +```javascript +const graph = new G6.TreeGraph({ + // ... // 其他配置项 + layout: { // Object,对于 TreeGraph 为必须字段 + type: 'dendrogram', + ... // 布局的其他配置 + } +}); +``` + +每种布局方法的配置项不尽相同,具体参见本目录下每种布局的 API。 + +## 通用配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| type | String | dendrogram | 布局类型,支持 dendrogram、compactBox、mindmap 和 indeted。 | +| direction | String | LR | 布局方向,有  `LR` , `RL` , `TB` , `BT` , `H` , `V`  可选。
L:左;R:右;T:上;B:下;H:垂直;V:水平。 | +| getChildren | Function | | 返回当前节点的所有子节点 | + +⚠️ 注意: 当 `type='indeted'` 时,`direction` 只能取 `'LR'`、`'RL'` 和 `'H'` 这三个值。 diff --git a/packages/site/docs/api/treeGraphLayout/indented.en.md b/packages/site/docs/api/treeGraphLayout/indented.en.md new file mode 100644 index 0000000000..91184acb18 --- /dev/null +++ b/packages/site/docs/api/treeGraphLayout/indented.en.md @@ -0,0 +1,86 @@ +--- +title: Indented +order: 3 +--- + +Indented layout represents the hierarchy by indent between them. Each node will take a row/column. It is appropriate for file directory. + +img + +### layoutCfg.direction + +**Type**: String
**Options**: 'LR' | 'RL' | 'H'
**Default**: 'LR'
**Required**: false
**Description**: The direction of layout: + +- LR —— Root is on the left, layout from the left to the right(left image below)
+- RL —— Root is on the right, layout from the right to the left(center image below)
+- H —— Root is on the middle, layout in horizontal symmetry(right image below) + +img +img +img + +> (Left)LR. (Center)RL. (Right)H. + +### layoutCfg.indent + +**Type**: Number | Function
**Default**: 20
**Example**: + +```javascript +(d) => { + // d is a node + if (d.parent?.id === 'testId') return d.parent.x + 50; + return 100; +}; +``` + +**Required**: false
**Description**: When the type is Number, the colunm separation is a fixed value; When the type is Function, the distance between the node and the root node is the returned value of the function. + +### layoutCfg.getWidth + +**Type**: Number | Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**Required**: false
**Description**: The width of each node. Takes effect only when `direction` is `'H'` + +### layoutCfg.getHeight + +**Type**: Number | Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**Required**: false
**Description**: The height of each node + +### layoutCfg.getSide + +**Type**: Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 'left'; + return 'right'; +}; +``` + +**Required**: false
**Description**: The callback function of node position(left or right of root node). Only affects the nodes which are connected to the root node directly. And the descendant nodes will be placed according to it. + +### layoutCfg.dropCap + +**Type**: Boolean + +
**Required**: false + +
**Explanation**: Whether place the first child node at the next line. `true` by default diff --git a/packages/site/docs/api/treeGraphLayout/indented.zh.md b/packages/site/docs/api/treeGraphLayout/indented.zh.md new file mode 100644 index 0000000000..f4973bcfdd --- /dev/null +++ b/packages/site/docs/api/treeGraphLayout/indented.zh.md @@ -0,0 +1,88 @@ +--- +title: Indented 缩进树 +order: 3 +--- + +缩进树布局。树节点的层级通过水平方向的缩进量来表示。每个元素会占一行/一列。常用场景是文件目录结构。 + +img + +### layoutCfg.direction + +**类型**:String
**可选值**:'LR' | 'RL' | 'H'
**默认值**:'LR'
**是否必须**:false
**说明**:树布局的方向,默认为 `'LR'`,其他选项说明: + +- LR —— 根节点在左,往右布局(下图左)
+- RL —— 根节点在右,往左布局(下图中)
+- H —— 根节点在中间,水平对称布局(下图右) + +img +img +img + +> (左)LR。(中)RL。(右)H。 + +### layoutCfg.indent + +**类型**:Number | Function
**默认值**:20
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.parent?.id === 'testId') return d.parent.x + 50; + return 100; +}; +``` + +**是否必须**:false
**说明**:类型为Number时,列间间距是固定值;类型为Function时,节点与根结点的间距是函数返回值。 + +### layoutCfg.getWidth + +**类型**:Number | Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**是否必须**:false
**说明**:每个节点的宽度,`direction` 为 `'H'` 时有效 + +### layoutCfg.getHeight + +**类型**:Number | Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**是否必须**:false
**说明**:每个节点的高度 + +### layoutCfg.getSide + +**类型**:Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 'left'; + return 'right'; +}; +``` + +### layoutCfg.dropCap + +**类型**:Boolean + +
+ +**是否必须**:false + +
**说明**:每个节点的第一个自节点是否位于下一行。默认为 `true` + +**是否必须**:false
**说明**:节点放置在根节点左侧或右侧的回调函数,仅对与根节点直接相连的节点有效,设置后将会影响被设置节点的所有子孙节点。 diff --git a/packages/site/docs/api/treeGraphLayout/mindmap.en.md b/packages/site/docs/api/treeGraphLayout/mindmap.en.md new file mode 100644 index 0000000000..85a4ce3ba2 --- /dev/null +++ b/packages/site/docs/api/treeGraphLayout/mindmap.en.md @@ -0,0 +1,90 @@ +--- +title: Mindmap +order: 4 +--- + +Mindmap arranged the nodes with same depth on the same level. Different from compactBox, it does not consider the size of nodes while doing layout. + +img + +### layoutCfg.direction + +**Type**: String
**Options**: 'H' | 'V'
**Default**: 'H'
**Required**: false
**Description**: The direction of layout. + +- H —— Root is on the middle, layout in horizontal symmetry. + +img + +- V —— Root is on the middle, layout in vertical symmetry. + +img + +### layoutCfg.getWidth + +**Type**: Number | Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**Required**: false
**Description**: The width of each node + +### layoutCfg.getHeight + +**Type**: Number | Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**Required**: false
**Description**: The height of each node + +### layoutCfg.getHGap + +**Type**: Number | Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**Default**: 18
**Required**: false
**Description**: The horizontal separation of nodes + +### layoutCfg.getVGap + +**Type**: Number | Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**Default**: 18
**Required**: false
**Description**: The vertical separation of nodes + +### layoutCfg.getSide + +**Type**: Function
**Example**: + +```javascript +(d) => { + // d is a node + if (d.id === 'test-child-id') return 'right'; + return 'left'; +}; +``` + +**Default**: 'right'
**Required**: false
**Description**: The callback function of node position(left or right of root node). Only affects the nodes which are connected to the root node directly. And the descendant nodes will be placed according to it diff --git a/packages/site/docs/api/treeGraphLayout/mindmap.zh.md b/packages/site/docs/api/treeGraphLayout/mindmap.zh.md new file mode 100644 index 0000000000..918240e820 --- /dev/null +++ b/packages/site/docs/api/treeGraphLayout/mindmap.zh.md @@ -0,0 +1,90 @@ +--- +title: Mindmap 脑图树 +order: 4 +--- + +深度相同的节点将会被放置在同一层,与 compactBox 不同的是,布局不会考虑节点的大小。 + +img + +### layoutCfg.direction + +**类型**:String
**可选值**:'H' | 'V'
**默认值**:'H'
**是否必须**:false
**说明**:树布局的方向,默认为 `'H'`,其他选项说明 + +- H:horizontal(水平)—— 根节点的子节点分成两部分横向放置在根节点左右两侧 + +img + +- V:vertical (竖直)—— 将根节点的所有孩子纵向排列 + +img + +### layoutCfg.getWidth + +**类型**:Number | Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**是否必须**:false
**说明**:每个节点的宽度 + +### layoutCfg.getHeight + +**类型**:Number | Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**是否必须**:false
**说明**:每个节点的高度 + +### layoutCfg.getHGap + +**类型**:Number | Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**默认值**:18
**是否必须**:false
**说明**:每个节点的水平间隙 + +### layoutCfg.getVGap + +**类型**:Number | Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'testId') return 50; + return 100; +}; +``` + +**默认值**:18
**是否必须**:false
**说明**:每个节点的垂直间隙 + +### layoutCfg.getSide + +**类型**:Function
**示例**: + +```javascript +(d) => { + // d 是一个节点 + if (d.id === 'test-child-id') return 'right'; + return 'left'; +}; +``` + +**默认值**:'right'
**是否必须**:false
**说明**:节点排布在根节点的左侧/右侧。若设置了该值,则所有节点会在根节点同一侧,即 direction = 'H' 不再起效。若该参数为回调函数,则可以指定每一个节点在根节点的左/右侧。 diff --git a/packages/site/docs/api/treeMethods.en.md b/packages/site/docs/api/treeMethods.en.md new file mode 100644 index 0000000000..beacc83d45 --- /dev/null +++ b/packages/site/docs/api/treeMethods.en.md @@ -0,0 +1,194 @@ +--- +title: TreeGraph Functions +order: 4 +--- + +### data() + +### addChild(data, parent) + +Add sub tree to the parent node. + +⚠️ Attention: G6 will use the `data` object as the model of the newly added item, and the `data` might be modified. If you do not want it to be modified, use the deep cloned `data` instead. + +**Parameters** + +| Name | Type | Required | Description | +| ------ | ------ | -------- | -------------------- | ---------------------------------- | +| data | Object | true | The data of subtree. | +| parent | Node | String | true | The id or instance of parent node. | + +**Usage** + +```javascript +const data = { + id: 'sub1', + children: [ + { + id: 'subTree1', + children: [...] + }, + { + id: 'subTree2', + children: [...] + } + ] +}; + +treeGraph.addChild(data, 'root') +``` + +### updateChild(data, parentId) + +Incrementally update or add one child data of a parent node. If you want to update all the children of the parent, try [updateChildren]() instead. The following image illustrate the differences between `updateChild` and `updateChildren`:
img img + +**Parameters** + +| Name | Type | Required | Description | +| ------ | -------- | -------- | -------------------------- | +| data | TreeData | true | The data of subtreee. | +| parent | String | false | The id of the parent node. | + +⚠️Attention: When the `parent` is null, this operation will update the graph fully. + +**Usage** + +```javascript +const data = { + id: 'sub1', + children: [ + { + id: 'subTree1', + children: [...] + }, + { + id: 'subTree2', + children: [...] + } + ] +}; + +treeGraph.updateChild(data, 'root') +``` + +### updateChildren(data, parentId) + +Update all the children of the parent. If you want to update or add one child of the parent, try [updateChild]() instead. The following image illustrate the differences between `updateChild` and `updateChildren`:
+ +img +img + +**Parameters** + +| Name | Type | Required | Description | +| ------ | ---------- | -------- | -------------------------- | +| data | TreeData[] | true | The data of subtreee. | +| parent | String | true | The id of the parent node. | + +**Usage** + +```javascript +const data = [ + { + id: 'subTree1', + children: [...] + }, + { + id: 'subTree2', + children: [...] + } +]; + +treeGraph.updateChildren(data, 'root') +``` + +### removeChild(id) + +Remove the subtree started from a child node with the id. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ------------------------------------ | +| id | String | true | The id of the subtree to be removed. | + +**Usage** + +```javascript +treeGraph.removeChild('sub'); +``` + +## Layout + +### changeLayout(layout) + +Change the layout. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| layout | Object | false | The new layout configurations. If the `layout` is null, the layout will not be changed. | + +**Usage** + +```javascript +const layout = { + type: 'mindmap', + direction: 'H', + getSubTreeSep: () => 20, + getVGap: () => 25, + getHeight: () => 30, + getWidth: () => 30, +}; +treeGraph.changeLayout(layout); +``` + +### layout(fitView) + +Refresh the layout. Usually, it is called after changing data. The `refreshLayout` is discarded by v4.x, call `layout` instead. + +**Parameters** + +| Name | Type | Required | Description | +| ------- | ------- | -------- | -------------------------------------------- | +| fitView | Boolean | false | Whether to fit view after refreshing layout. | + +**Usage** + +```javascript +treeGraph.layout(true); +``` + +## Search + +### findDataById(id, target) + +Find data model according to the id. + +**Parameters** + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| id | String | true | The id of the item. | +| target | Object | false | Search from the target. If the target is null, this operation will search from the root node. | + +**Return** + +- The type of return value: Object; +- The return value is the data model of the found node. + +**Usage** + +```javascript +const target = { + id: 'sub1', + children: [...] +} + +// Search the node with id 'sub1.1' from target +const subData = treeGraph.findDataById('sub1.1', target) + +// Search the node with id 'sub1.1' from root node +const subData = treeGraph.findDataById('sub1.1') +``` diff --git a/packages/site/docs/api/treeMethods.zh.md b/packages/site/docs/api/treeMethods.zh.md new file mode 100644 index 0000000000..ae9cbfd537 --- /dev/null +++ b/packages/site/docs/api/treeMethods.zh.md @@ -0,0 +1,191 @@ +--- +title: TreeGraph 实例方法 +order: 4 +--- + +### data() + +### addChild(data, parent) + +在指定的父节点下添加子树。 + +⚠️ 注意: 将会直接使用 `data` 对象作为新增节点/边的数据模型,G6 内部可能会对其增加或修改一些必要的字段。若不希望原始参数被修改,建议在使用深拷贝后的 `data`。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | ------ | -------- | ---------- | +| data | Object | true | 子树的数据 | +| parent | Node | String | true | 父节点或父节点 ID | + +**用法** + +```javascript +const data = { + id: 'sub1', + children: [ + { + id: 'subTree1', + children: [...] + }, + { + id: 'subTree2', + children: [...] + } + ] +}; + +treeGraph.addChild(data, 'root') +``` + +### updateChild(data, parentId) + +更新数据,差量更新子树。data 是一个子树数据。若该子树的根节点不存在与当前树数据中,将该子树添加到 parentId 节点的子节点中。若该子树的根节点已经存在,则更新该子树数据。若希望更新或增加 parentId 节点下所有子节点,请使用 [updateChildren]()。二者区别图示如下: img img + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| -------- | -------- | -------- | ---------- | +| data | TreeData | true | 子树的数据 | +| parentId | String | false | 父节点 ID | + +⚠️ 注意: 当 `parentId` 参数为空时,则全量更新。 + +**用法** + +```javascript +const data = { + id: 'sub1', + children: [ + { + id: 'subTree1', + children: [...] + }, + { + id: 'subTree2', + children: [...] + } + ] +}; + +treeGraph.updateChild(data, 'root') +``` + +### updateChildren(data, parentId) + +更新数据,差量更新子树中的所有子节点。data 是一个子树数据数组。若希望更新或增加一个 parentId 节点的子节点,请使用 [updateChild]()。二者区别图示如下:
img img + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| -------- | ---------- | -------- | -------------- | +| data | TreeData[] | true | 子树的数据数组 | +| parentId | String | true | 父节点 ID | + +**用法** + +```javascript +const data = [ + { + id: 'subTree1', + children: [...] + }, + { + id: 'subTree2', + children: [...] + } +]; + +treeGraph.updateChildren(data, 'root') +``` + +### removeChild(id) + +删除指定的子树。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ----------------- | +| id | String | true | 要删除的子树的 ID | + +**用法** + +```javascript +treeGraph.removeChild('sub'); +``` + +## 布局 + +### changeLayout(layout) + +更改并应用指定的布局。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | ------ | -------- | ---------------------------------- | +| layout | Object | false | 指定的布局配置,如不传,则不做变更 | + +**用法** + +```javascript +const layout = { + type: 'mindmap', + direction: 'H', + getSubTreeSep: () => 20, + getVGap: () => 25, + getHeight: () => 30, + getWidth: () => 30, +}; +treeGraph.changeLayout(layout); +``` + +### layout(fitView) + +数据变更后,重新布局,刷新视图,并更新到画布。v4.x 废弃了 `refreshLayout`,请使用 `layout` 替代。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------- | ------- | -------- | ------------------------------ | +| fitView | Boolean | false | 更新布局后,是否需要自适应窗口 | + +**用法** + +```javascript +treeGraph.layout(true); +``` + +## 查找 + +### findDataById(id, target) + +根据指定的 ID 获取对应的源数据。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ------ | ------ | -------- | -------------------------------------------- | +| id | String | true | 指定的元素 ID | +| target | Object | false | 从指定的节点开始查找,为空时从根节点开始查找 | + +**返回值** + +- 返回值类型:Object; +- 返回值为查找到的节点的源数据。 + +**用法** + +```javascript +const target = { + id: 'sub1', + children: [...] +} + +// 从 target 节点开始查找 sub1.1 节点 +const subData = treeGraph.findDataById('sub1.1', target) + +// 从根节点开始查找 sub1.1 节点 +const subData = treeGraph.findDataById('sub1.1') +``` diff --git a/packages/site/docs/design/case.en.md b/packages/site/docs/design/case.en.md new file mode 100644 index 0000000000..2ce70840f2 --- /dev/null +++ b/packages/site/docs/design/case.en.md @@ -0,0 +1,8 @@ +--- +title: Case +order: 6 +--- + +**🛠The English Version is in Prograss🛠** + + diff --git a/packages/site/docs/design/case.zh.md b/packages/site/docs/design/case.zh.md new file mode 100644 index 0000000000..e2e726ccc8 --- /dev/null +++ b/packages/site/docs/design/case.zh.md @@ -0,0 +1,24 @@ +--- +title: 综合案例 +order: 6 +--- + +## 云安全 + +在分析网络安全数据时,设备、事件、位置、IP、签名等之间的连接是发现异常、威胁和漏洞的关键。理解这些联系的最好方法就是把它们形象化。今天,在许多大型组织、金融机构和安全咨询服务中都有网络或 IT 的安全要求。这些组织需要保护自己免受如 zero-day 漏洞,DDoS 或网络钓鱼攻击之类漏洞的侵害。他们从服务器、路由器或应用程序日志和网络状态中收集数据,来检测可疑活动。图可视化可以一目了然地展示这些数据并检测可疑模式。通过对连接状态的可视化探索,可以更快速定位漏洞或攻击。
详细案例请参考[《图可视化解决方案:云安全》](https://gw.alipayobjects.com/os/bmw-prod/660e1732-ff10-4f00-8594-a08d5a2d11d8.pdf)。 + +## 知识图谱 + +知识图谱(Knowledge Graph)是一种用图模型来描述知识和建模世界万物之间的关联关系的技术方法。知识图谱将信息中的知识或者数据加以关联,实现人类知识的描述及推理计算,并最终实现像人类一样对事物进行理解与解释,推动了从弱人工智能到强人工智能的发展。知识图谱由节点和边组成。节点可以是实体(如一个人、一个支付设备、一个企业等),也或是抽象的概念-本体类型(如人工智能、事物等)。边可以是本体类型之间的关系,也可以实体与实体之间的关系,如投资关系、支付关系等。图可视化可以更加清晰直观地描述这种结构化的关系,利于看清,是知识图谱领域的做图表示、图分析推理等重要基石。
详细案例请参考[《图可视化解决方案:知识图谱》](https://gw.alipayobjects.com/os/bmw-prod/b6686d7a-2860-43a4-a75e-880490d5a414.pdf)。 + +## 企业风控 + +随着互联网发展和数字化的进程,企业数据在规模上几何增长,越来越多的企业面临信用、合规、声誉、第三方等诸多风险,企业风险控制的需求甚至也扩大到了政府、专业机构等各类细分领域。如何结合各个细分领域、业务场景,利用数据可视化技术,帮助企业更清晰发现和识别风险,从而将数据变成实际可用的风控产品和服务,这是当下企业风控需要解决的一个问题。
详细案例请参考[《图可视化解决方案:企业风控》](https://gw.alipayobjects.com/os/bmw-prod/0b872268-1388-457f-9358-c41327a861e1.pdf)。 + +## 图数据库 + +图数据库领域是最近几年大数据领域热度颇高的领域,从 DB Engines 的排名来看,自 2013 年开始,图数据库的发展就一骑绝尘。与传统关系型数据库不同,图数据技术主要关注数据间关系查询能力,是表示和查询关联关系的最佳方式。借助于图数据库技术: + +- 可以快速从百亿级电商网络中匹配出刷单团伙; +- 可以快速构建出人与人的社交关系,分析特定用户的人际关系、关注度、转发量等; +- 把 IP、域名、主机等一些列实体构建成图,可以快速发现诸如木马网络的不安全因素,辅以图分析能力能够很容易对不安全因素进行追根溯源。
详细案例请参考[《图可视化解决方案:图数据库》](https://gw.alipayobjects.com/os/bmw-prod/a381a006-85e8-4c55-83b3-278a02f83535.pdf)。 diff --git a/packages/site/docs/design/component/componentOverview.en.md b/packages/site/docs/design/component/componentOverview.en.md new file mode 100644 index 0000000000..cd9e43270b --- /dev/null +++ b/packages/site/docs/design/component/componentOverview.en.md @@ -0,0 +1,8 @@ +--- +title: Component Design Overview +order: 0 +--- + +**🛠The English Version is in Prograss🛠** + + diff --git a/packages/site/docs/design/component/componentOverview.zh.md b/packages/site/docs/design/component/componentOverview.zh.md new file mode 100644 index 0000000000..1ed0a33560 --- /dev/null +++ b/packages/site/docs/design/component/componentOverview.zh.md @@ -0,0 +1,154 @@ +--- +title: 组件设计概览 +order: 0 +--- + +> 介绍一个图分析产品会有的常用组件,以及各组件的作用及用法。白皮书中组件相关内容点和边的基础样式是是所有图可视化的基石,要组成一个完成的图分析产品,还需要有各类组件来承担不同的功能。从体验设计的角度来看,常见的组件可分为如下几种类型: + +- 基础组件:图例、工具栏、右键菜单、视图控制栏、系统日志等 +- 条件输入:查询、筛选、搜索、画布设置等 +- 信息输出:详情面板、气泡、Tooltip、画板信息等 +- 高级功能:时间轴、快照画廊、分析报表等 + +‘img’ + +在某些特殊场景下,也需要结合业务实际情况基于基础组件去升级优化,乃至基于产品独有的能力去设计全新的组件。在优化一个基础组件或设计全新的组件时,需要结合实际的功能需求,从使用场景、构成元素、常见类型、交互说明等几个角度完整的思考清楚。以 AntV 最新设计的两个组件为例: + +- [时间轴 TimeBar](./timebar) +- [视图控制栏 View ToolBar](./viewToolbar) + +## 组件类型 + +### 通用组件 + +> 引用自白皮书内容-组件。交互组件,是指用户操作节点,操作边,操作画布,所需要的配套组件。比如 Hover 节点展示出提示框(Tooltip);点击图例(Legend)对节点筛选;右键节点,弹出菜单(ContextMenu);对画布进行 放大,缩小,全屏等一套操作工具栏(Toolbar),以及动态改变时间范围,影响画布展示(Timebar) + +Legend 图例是一种常见的图分析配套组件,通常将节点 和 边 分类后进行染色,交互分析。其中点击图例,有两种逻辑,一种为 高亮逻辑,即高亮选中的图例所对应的节点,一种是过滤逻辑,即将未选中的节点从画布中过滤。 + +> @antv/graphin-components 提供了 Legend 组件,如下图所示:
+ +‘img’ + +### 提示框 tooltip + +Tooltip 提示框是一种快速浏览信息的交互组件,常用于图的节点上,通过鼠标 Hover 产生一个提示框,鼠标移出节点则取消提示框,一般在快速查询信息的时候非常有帮助。 + +> @antv/g6 提供了 tooltip 组件,如下图所示: + +
+‘img’ + +### 右键菜单 ContextMenu + +ContextMenu 是右键菜单,通常是对节点进行进一步操作的组件。例如:通过右键菜单实现节点的复制,删除,反选等等。同时,我们也可以对选择的节点发起新画布分析,或者进行打标,发起关系扩散,数据请求之类的高级自定义行为。图分析产品中的 右键菜单往往是和 浏览器网页 的右键菜单 交互与展示形式保持一致,但是也有特殊的形状类型,比如右键仪表盘菜单。 + +> @antv/graphin-components 提供了 ContextMenu 组件,如下图所示:
‘img’ + +#### 工具栏 Toolbar + +Toolbar 是提供 常见分析操作 的工具栏。内置了撤销重做(操作历史),鱼眼放大镜,画布缩放,全屏,节点聚焦,画布快照下载等等功能。 + +> @antv/graphin-components 提供了 Toolbar 组件,如下图所示: + +
+‘img’ + +#### 时间轴 Timebar + +时间轴是一种针对时间序列的分析组件,根据时间日期的改变,画布的图也相应动态改变,一个功能完备的时间轴,可以配合播放、快进、后退等控制按钮组使用,为用户带来意想不到的分析效果 + +> @antv/g6 提供了 Timebar 组件,如下图所示: + +
+‘img’ + +### 数据交互组件 + +#### 查询面板 QueryPanel + +用户查询图数据的组件,一般由特定的 DSL 语言编辑器组成,例如 SQL、Gremlin、Cypher 等,在某些场景下,通过这个查询面板,可以让用户自主加载数据。 + +
+‘img’ + +#### 筛选面板 FilterPanel + +筛选面板是对图元素:节点与边的筛选,从而达到减少视觉干扰的效果。 + +
+‘img’ + +#### 搜索面板 SearchPanel + +在关系分析过程中,搜索面板可以辅助用户快速定位其需要关心节点、关联关系。减少用户读图时间。 + +
+‘img’ + +#### 属性面板 PropertiesPanel + +在关系分析中,节点、边的属性信息量较大,很难把所有信息都在画布中呈现。除了关键属性信息,其他属性信息可以通过属性面板的交互方式呈现给用户 + +
+‘img’ + +### 后分析组件 + +> 后分析组件:顾名思义是 画布分析后的分析组件。通常来讲,我们的图分析默认都是在画布区域分析,当一个图已经分析完毕,我们需要对分析结果进行增强,或者保存分享给其他人进行二次分析,这部分能力往往也是图分析产品所应该具备的系统能力。我们梳理出来 3 个后分析组件:注解组件,快照画廊,分析报表。 + +#### 注解组件 AnnotationPanel + +能够对画布的分析结果进行标注:可以使用圈选,拉索 对分析结果的图片进行区域内容选择,使用文本标注,标注内容可以按照时间轴存储。 + +> 技术上,社区有开源的 d3-annotation,可以轻松实现注解功能。下图是一个分组备注案例 + +
+‘img’ + +#### 快照画廊 Snapshot Gallery + +快照画廊:是由一系列分析结果快照保存组成的事件长廊,我们形象称之为快照画廊:与传统单一的快照功能相比,快照画廊能够将片断的分析快照保存在系统上,以供分析师能够回看和二次分析。相比 Toolbar 工具栏里的“撤销回退”功能,快照画廊更佳可视化,与分析系统集成度更佳。 + +> 技术上,@antv/g6 提供 save image 功能,配合 gallery 功能特性。 + +
+‘img’ + +#### 分析报表 AnalysisReport + +一个中心明确,内容清晰的分析报表能够节省决策者读懂报表内容的时间。图分析报表除了能使用静态图片加内容注解方式的方式呈现给决策者之外,可以使用 IFrame 的方式内嵌的三方报表系统中,是分析结果具有一定的可交互性,从而帮助决策者更好的理解报表内容。 + +
+‘img’ + +### 系统组件 + +> 系统组件是指可视化分析系统中需要的一些组件,比如分析过程产生的日志(SystemLog),原始数据表格的展示(TableMode),以及画布的实时状态信息(CanvasInfo)和画布的用户设置项(CanvasSetting) + +#### 系统日志 SystemLog + +关系分析的分析过程是一个反复与后端系统进行交互的过程,一个设计完善、内容清晰的系统日志组件能够帮助用户迅速定位问题。 + +
+‘img’ + +#### 表格模式 TableMode + +表格模式虽然不能直观展示图中关联关系,但是对于罗列点、边属性信息有较大优势,可以作为分析能力的一个补充。 + +
+‘img’ + +#### 画布设置 CanvasSetting + +画布设置,可以设置画布元素的颜色,大小,类型,从而达到最佳的展示效果。 + +
+‘img’ + +#### 画布信息 CanvasInfo + +统计画布的实时信息,比如统计当前画布的节点数量,边数量,在一些持续性布局中,比如力导,还可以提供布局时间与进度。在一些大图场景下,还需要监控浏览器的内存,网络等系统监控信息。 + +
+‘img’ diff --git a/packages/site/docs/design/component/timebar.en.md b/packages/site/docs/design/component/timebar.en.md new file mode 100644 index 0000000000..158a959c7c --- /dev/null +++ b/packages/site/docs/design/component/timebar.en.md @@ -0,0 +1,8 @@ +--- +title: Timebar +order: 1 +--- + +**🛠The English Version is in Prograss🛠** + + diff --git a/packages/site/docs/design/component/timebar.zh.md b/packages/site/docs/design/component/timebar.zh.md new file mode 100644 index 0000000000..5476be4223 --- /dev/null +++ b/packages/site/docs/design/component/timebar.zh.md @@ -0,0 +1,99 @@ +--- +title: 时间轴 TimeBar +order: 1 +--- + +## 定义 + +时间轴组件基于普通缩略轴演变而来,它是有效展示动态时序数据、分析图数据的组件。该组件可以让用户快速、直观地观察事件序列以及它们之间的联系。用户可以播放时间来发现异常和探寻模式,推动调查并揭示数据中隐藏的故事。 + +
+‘img’ + +## 何时使用 + +如果需要观察一定时间内图数据的演变情况,分析变化趋势时,建议开启时间轴组件。例:在金融风控领域,保险公司和金融机构的反欺诈人员通过图可视化分析三个月内的案件情况,时间轴组件可以帮助快速分析可疑人脉、财务转账关系,定位嫌疑人。 + +## 常见类型 + +### 趋势图时间轴 + +该时间轴包括但不限于折线图、面积图、柱状图中的一种或多种组合用来表示某种数据属性趋势的时间轴组件,[查看演示 Demo](https://g6.antv.antgroup.com/examples/tool/timebar#timebar)。 + +
+‘img’ + +
+‘img’ + +### 简版时间轴 + +相对于趋势图时间轴而言,去掉了表示数据趋势的图表,使用更为简洁的线条来表示时间范围,[查看演示 Demo](https://g6.antv.antgroup.com/examples/tool/timebar#simple-timebar)。 + +
+‘img’ + +### 时间刻度时间轴 + +指表示时间刻度的时间轴组件,[查看演示 Demo](https://g6.antv.antgroup.com/examples/tool/timebar#slice-timebar)。 + +
+‘img’ + +## 构成元素 + +
+‘img’ +
+ +时间轴组件主体分为三部分,2、3 部分需同时出现或隐藏。 + +1. 缩略轴:可配置成趋势图时间轴、简版时间轴、时间刻度时间轴; +2. 播放器:播放时间动画,可配置是否显示; +3. 时间配置:可配置播放速度、是否只看单一时间点; + +## 出现位置 + +时间轴作为辅助组件,建议放在图形区下方。 + +
+‘img’ + +## 交互说明 + +### 缩略轴 + +支持拖拽、点击、平移改变时间范围。 + +
+‘img’ +
+ +简版时间轴、时间刻度时间轴交互操作同上。鼠标滚轮滚动时,左右平移已选定区间,暂不支持触控版操作。 + +
+‘img’ +
+ +轴上数值文本内置自动躲避规则。 + +
+‘img’ + +### 播放器 + +播放器主要包括三部分,其中播放、暂停按钮动作及状态相互切换。 + +
+‘img’ +
+播放方式,分两种: +- 累计时间段数据:开始时间不变,结束时间持续增加,适合查看从一个时间点开始,持续观察累计变化趋势; +- 区间时间段数据:开始到结束时间的区间段固定不变,播放时该时间段水平移动,适合查看固定时间段内的数据变化趋势; + +### 时间配置 + +时间配置主要包括两部分,单一时间开关、播放速度设置。 + +- 单一时间开关,默认不开启。
‘img’
+- 播放速度设置,默认配速为最慢速 1,最大速为 5。支持滚轮(触控板)滑动切换配速,每次增减值为 1。
‘img’ diff --git a/packages/site/docs/design/component/viewToolbar.en.md b/packages/site/docs/design/component/viewToolbar.en.md new file mode 100644 index 0000000000..a387e6c08b --- /dev/null +++ b/packages/site/docs/design/component/viewToolbar.en.md @@ -0,0 +1,8 @@ +--- +title: View Toolbar +order: 2 +--- + +**🛠The English Version is in Prograss🛠** + + diff --git a/packages/site/docs/design/component/viewToolbar.zh.md b/packages/site/docs/design/component/viewToolbar.zh.md new file mode 100644 index 0000000000..96e546e92c --- /dev/null +++ b/packages/site/docs/design/component/viewToolbar.zh.md @@ -0,0 +1,99 @@ +--- +title: 视图控制栏 +order: 2 +--- + +## 定义 + +‘img’ +
+视图控制栏是用户与数据进行交互的载体,当用户操作视图工具栏时,可对视图可视区间缩放、平移、对数据进行检索、过滤等。 + +## 何时使用 + +以下两种情况,建议设计时开启视图控制栏。 + +- 数据绘制区域超过屏幕可视区域,为方便用户能以全局视角观察数据,建议开启视图控制栏。例:地图上的视图控制栏; +- 用户需要频繁与视图上的数据进行交互,比如放大、缩小视图空间,快速定位检索数据点等。例:公安通过关系网核查犯罪嫌疑人; + +## 构成元素 + +‘img’ +
+ +以下每组元素都为选配项 + +- 搜索; +- 画布控制按钮组:+、—、百分比、Mini Map 迷你视图(可选配); +- 自定义组:可定制视图工具栏按钮,比如全屏、定位当前位置等操作。 + +## 常见类型 + +‘img’ +
+ +## 出现位置 + +- z-index 层级:层级高于画布,但低于视图中的 Tooltip 提示信息组件; +- 水平边距:距两边边距均为 8 的倍数; +- 位置:视布局需要,八个方向均可配置。 + +
+‘img’ + +## 交互说明 + +### 搜索 + +- 键盘操作:输入后,enter 执行搜索操作,按键盘 esc 可退出搜索框,搜索恢复 normal 态。 + +
+‘img’ + +### 画布控制按纽组 + +- 点击“+”,0 以上默认增幅 25%,100% 以上默认增幅 50%,建议最大值 200%; +- 点击”—“,100%~200% 之间,默认降幅 50%,100% 以下默认降幅 25%,建议早小值 25%; +- 百分比字体:Roboto Condensed; +- 键盘操作:按 esc 可退出已激活的下拉框。 + +
+‘img’ + +### Minimap + +- 键盘操作:按 esc 可退出已显示的 Minimap。 + +
+‘img’ + +## 设计建议 + +### 按钮分组显示 + +建议视图控制栏按钮按照格式塔原理分组显示。 + +
+‘img’ + +### 遵循用户阅读习惯 + +画布初始位置,建议符合从左至右,从上至下的阅读习惯,而不是一刀切显示视图的正中间。比如: + +- 思维导图建议贴左,垂直居中; +- 关系图将起始节点居中显示; +- DAG 流程图建议贴上,水平居中; + +### 效率优先 + +建议视图增快捷操作,提升使用效率 + +- 放大 +:Ctrl/⌘ + + +- 缩小 -:Ctrl/⌘ + — +- 100% 1:1 等比例显示:Ctrl/⌘ + 0 +- 适应画布显示:Ctrl/⌘ + 1 + +### 全场景考虑 + +- 建议补充移动端样式及交互; +- 补充 Dark Mode 模式。 diff --git a/packages/site/docs/design/global/Interactive.zh.md b/packages/site/docs/design/global/Interactive.zh.md new file mode 100644 index 0000000000..0e21aa43c6 --- /dev/null +++ b/packages/site/docs/design/global/Interactive.zh.md @@ -0,0 +1,132 @@ +--- +title: 全局交互 +order: 1 +--- + +### 引言 + +在图可视化分析的实际场景中,往往会发现静态演示不足以满足我们的业务需求,这种情况在海量数据的情况下尤为显著。若将巨量的数据完整地排布到可视化空间中,会发现信息密度远远超过了人的可读范围(图 1.1)。在只需传达给观者大致感知与情绪冲击的场景,这样的方式是适用的,但要解决更具体的问题或获取更深度的洞察时,就变得举步维艰。 + + + +> 图 1.1 《人际关系星形图》( Personal Friendster ) - Jeffrey Heer 2004
Personal Friendster 是 Vizster 在线社交网络可视化项目的一部分,加利福利亚大学伯克利分校的 Jeffrey 教授采集了自己在社交网络 Friendster 中的三度人际关系数据,通过力导向布局可视化了以自己为中心的 47471 人与 432430 段关系,该图颠覆了人的认知:仅仅三度关系,就能构建出如此庞大的社交网络。这是该项目希望达成的目标,但要在如此庞大的节点与边中去探索具体的人与关系,作为静态图无疑是不可能的。 + +为了使图能传达具体的信息,而不仅只是模糊的感知,需要将可视化空间中的图元数量限制在人肉眼可识别能力的范围内。并在下一步提供恰当的交互,来使用户渐进式地发现更丰富与多维的信息(图 1.2)。 + + + +> 图 1.2 GraphStudio - Alibaba Inc.
通过条件筛选、搜索、双击展开关系等等交互方式探索数据,令用户能够依照自己的分析目标去发现各个颗粒度的数据与关系 + +交互使被动的"看客"成为主动的"用户",更好地参与了对数据的理解和分析。可视分析系统的目的往往不仅是向用户传递定制好的知识,而是还能提供一个工具或平台来帮助用户探索数据,得到结论。 + +## 交互规范 + +目前在阿里和蚂蚁体系内,G6 广泛应用于金融风控、云安全、知识图谱、企业风控、图数据库等业务,其广泛的应用场景,也决定了其不仅要保证专业性与扩展性,也要保证普适性,无论是新手、中级用户、亦或是专家,都能高效且容易地使用产品。我们提供的交互规范中,通过简单的单击、双击、移动等操作就可以很轻松地对可视化内容进行探索,也支持高级操作,如快捷键等隐性操作,可以让专家用户的工作更加高效,以及可以进行更深维度的探索。 + +不同的业务场景具有不同的交互,但又有相通的交互部分。G6 依照目前的经验沉淀,将通用的部分作为全局交互以供自由配置,具备业务特性的部分则作为扩展模板以供参照使用。 + +- 通用交互,剥离了业务属性,是一套适应大部分关系图交互探索的基础工具箱,并将范围圈定在了基础键鼠操作内,令普通电脑用户也能迅速地利用其对数据进行探索 +- 扩展交互,不一定适应所有的应用场景与业务属性,但承载了 G6 强大的扩展性与能力,其范围不受限制,既可以是普通的交互行为触发,也能被其他事件触发,亦或由实时的业务数据触发等,用户能通过这些交互对数据进行更深度更定制化的探索。 +- 操作对象可以分为画布、节点、Combo、边和其他四部分,所有的交互根据不同的操作对象进行分类。 + +### 画布 Canvas + +#### 通用交互 + +| **通用交互** | **触发** | **演示** | +| :-- | :-- | --- | +| 缩放画布 | 🖱 鼠标:滚轮向上/向下
⌨️ 键盘:「⌘(Ctrl)」+「+/-」
💻 触控板:双指展开/合并 | | +| 移动画布 | 🖱 鼠标:拖拽
💻 触控板:双指移动
🖱 键鼠:Space 键+拖拽(当与框选操作冲突时) | | +| 回到概览 | ⌨️ 键盘:「Ctrl/⌘」+「1」 | | + +#### 扩展交互 + +| **扩展交互** | **触发** | **演示** | +| --- | --- | --- | +| 鱼眼 | 🎚 专用控件 | | +| 布局切换 | 🎚 专用控件 | | +| 时序过滤 | 🎚 专用控件 | | +| 边过滤 | 🎚 专用控件 | | +| 缩略图 | 🎚 专用控件 | | + +### 节点 Node + +#### 通用交互 + +| **通用交互** | **触发** | **演示** | +| :-- | :-- | --- | +| 指向节点 | 🖱 鼠标:悬停 | | +| 选中节点 | 🖱 鼠标:单击 | | +| 探索节点 | 🖱 鼠标:双击
⌨️ 键盘:Enter | | +| 取消选中 | 🖱 鼠标:单击空白区域
⌨️ 键盘:Esc | | + +#### 扩展交互 + +| **扩展交互** | **触发** | **演示** | +| --- | --- | --- | +| 多选节点 | ⌨️🖱 键鼠:Shift + 长按移动
⌨️🖱 键鼠:Shift + 单击
🖱 鼠标:长按移动(当与拖拽画布操作不冲突时)
🎚 控件:套索 |

| +| 移动节点 | 🖱 鼠标:拖拽 | | +| 改变节点层级 | 🖱 鼠标:拖拽 |
| +| 高亮相邻节点 | 📃 事件:选中节点 | | +| 高亮最短路径 | 🎚 专用控件 | | + +### 边 Edge + +#### 通用交互 + +| **通用交互** | **触发** | **演示** | +| :-- | :-- | --- | +| 指向边 | 🖱 鼠标:悬停 | | +| 选中边 | 🖱 鼠标:单击 | | +| 取消选中 | 🖱 鼠标:单击空白区域
键盘:Esc | | + +#### 扩展交互 + +| **扩展交互** | **触发** | **演示** | +| :-- | :-- | --- | +| 多选边 | ⌨️🖱 键鼠:Shift + 单击
⌨️🖱 键鼠:Shift + 长按移动
🖱 鼠标:长按移动(当与拖拽画布操作不冲突时) | | + +### 组 Combo + +#### 通用交互 + +| **通用交互** | **触发** | **演示** | +| :-- | :-- | --- | +| 指向 Combo | 🖱 鼠标:悬停 | | +| 选中 Combo | 🖱 鼠标:单击 | | +| 展开/收起 Combo | 🖱 鼠标:双击
键盘:Enter
🎚 专用控件 | | +| 取消选中 Combo | 🖱 鼠标:单击空白区域
键盘:Esc | | + +#### 扩展交互 + +| **扩展交互** | **触发** | **演示** | +| --- | --- | --- | +| 多选 Combo | ⌨️🖱 键鼠:Shift + 单击 | | +| 移动 Combo | 🖱 鼠标:拖拽 | | +| 改变 Combo 层级 | 🖱 鼠标:拖拽 | | + +## 快捷操作 + +由于图可视化信息密度很大,设计者要在短时间内完成某个关系图的配置,或者阅读者需要在短时间内探索出数据关系的症结。这时操作效率就显得尤为重要,G6 现已内置如下常用快捷操作,如果不满足,你还可以定制自己的快捷键盘操作。 + +| **通用交互** | **触发** | **演示** | +| :-- | :-- | --- | +| 撤销 | ⌨️ 键盘:「Ctrl/⌘」+「z」 | | +| 重做 | ⌨️ 键盘:「Ctrl/⌘」+「Shift」+「z」 | | +| 右键菜单 | 🖱 鼠标:右键单击 |
| + +## 交互模式 + +交互在数据可视化系统中解决的核心问题是**有限的可视化空间与数据过载之间的矛盾**,交互帮助拓展了可视化中信息表达的空间,并给用户提供在空间中探索数据的路径,这条路径如何修筑才能确保探索过程的通畅?通过对经典方法的发掘与在实际业务中的提炼,总结了两类交互模式以供参考:由表及里、以点及面。 + +### 由表及里 + +Ben Shneiderman 于 1996 年提出可视化信息检索的箴言 **Overview First, Zoom and Filter, Then Details-on-Demand**,它符合人类寻求信息的基本逻辑:先概览,然后局部,最后聚焦兴趣点进行探索,这是一个由表及里的过程。 + + + +### 以点及面 + +得益于搜索等技术的成熟,可视分析系统已经能够快速定位到用户所关注的对象,特别是已经有明确的分析目标时,这类场景就不必要由整体顺着数据结构向下慢慢地探索,而更多的是基于所关注的对象去延伸与发现它周边的关系与详细信息。 + + diff --git a/packages/site/docs/design/global/interactive.en.md b/packages/site/docs/design/global/interactive.en.md new file mode 100644 index 0000000000..4615d4211a --- /dev/null +++ b/packages/site/docs/design/global/interactive.en.md @@ -0,0 +1,8 @@ +--- +title: Global Interactive +order: 1 +--- + +**🛠The English Version is in Prograss🛠** + + diff --git a/packages/site/docs/design/global/style.en.md b/packages/site/docs/design/global/style.en.md new file mode 100644 index 0000000000..77da0dd9ba --- /dev/null +++ b/packages/site/docs/design/global/style.en.md @@ -0,0 +1,8 @@ +--- +title: Global Style +order: 0 +--- + +**🛠The English Version is in Prograss🛠** + + diff --git a/packages/site/docs/design/global/style.zh.md b/packages/site/docs/design/global/style.zh.md new file mode 100644 index 0000000000..2c684fad01 --- /dev/null +++ b/packages/site/docs/design/global/style.zh.md @@ -0,0 +1,180 @@ +--- +title: 全局样式 +order: 0 +--- + +## 图构成元素 + +图的构成元素包括节点(Node)、边(Edge)和组(Combo),这些基础元素是图的原子组成部分,设计者可根据特定业务场景变更节点、边、组的配色和组合形式,搭建更复杂的图可视分析应用。 + +### 节点(Node) + +#### 定义 + +节点是构成图的基本单位,一般表示某个实体。如社交网络数据中,一个人就是一个实体,用一个节点来表示。 + +#### 常见形态 + +根据业务场景需要,信息可视化呈现时: + +- 节点可加标签、可不加标签; +- 节点可存在不同形状; +- 节点可加简短描述; +- 节点可展开下级信息。 + + + + + +#### 交互样式 + +点交互基础样式有以下 6 种:基础状态:Default-默认、Active-激活、Selected-选中、Disable-失效被动状态:Highlight-强化、Inactive-弱化(在交互场景中与 Default-默认 做出区分) + + + +> 以力导向图布局和辐射布局为例 + + + +### 边(Edge) + +#### 定义 + +边(Edge)表示的是两个节点之间的关系。如社交网络数据中,我和你的关系,是朋友。 + +#### 常见形态 + +因图的类型众多,边的形态也相应有: + +- 有向的(含箭头)、无向的(无箭头); +- 加权的(含值)、无加权的(不含值); +- 加标签、不加标签; +- 不同粗细代表节点流量。 + + + + + +#### 交互样式 + +边的交互基础样式跟节点同样有以下 6 种:Default、Active、Selected、Disable、Highlight、Inactive + + + +> 在节点的交互样式引用图例中同样可看到边的运用效果 + +### 组(Combo) + +#### 定义 + +组合,又称为节点分组,用于管理一组相似的节点,如一组具有相同类型的节点,或位置上比较靠近的一组节点,可以将它们划分到同一个 Combo 中,可以有效降低视觉上的干扰。 + +#### 常见形态 + +G6 默认提供两种类型,使用带有不重叠约束的力导向图布局方法,可根据业务场景和布局需要选取合适的形状。 + +- Circle 圆形 +- Rect 矩形 + + + +> 在 Combo 的具体运用中,会出现 Combo 未展开/已展开、一级 Combo 和二级 Combo 及更多级相结合的形式,上图仅为未展开、展开(共一级)、展开(共二级)形式 + +#### 交互样式 + +节点组合形式千变万化,按常见形态延伸相应的交互样式如下: + + + +## 关系图色板 + +G6 在 AntV 的基础色彩体系的基础上,结合关系图表达的特点,精细化调整了颜色在数据维度上的衡量和线性感知。内置了一系列优美、和谐且满足无障碍设计原则的色板。包括:分类色板、邻近色板、发散色板、语义色板。默认情况下以蓝色为基础样式的案例色,也是 G6 的默认主色。 + +让颜色在图中能够达成在数据变化和人体感知上尽可能线性匹配,不同数值对应的颜色区分度要足够高,且在拥有分类色状态下依然感知均匀,一个连续数据集的所有数据点都具有同等的视觉重要性。 + +### 默认主题色 + +选择蓝色为基础样式的案例色,也是基础样式的默认色 /G6 的主色;灰色作为辅助色。 + + + +### 邻近色色板 + +#### 定义 + +邻近色顺序色板,一般使用两个或以上个临近色调,通过明度和饱和度的逐步渐变,常用来区分有序数据优先级的高低、连续数据的大小或梯度变化。 + +#### 取色指南 + +根据数据语义特定管理约束,选择合理色调搭配,使连续变化的色调和明度,可产生更多色彩分级,表达更多的连续数值。 + +#### 取色方法 + +1. 亮色色板选取单色顺序色板中的 1 号色作为起始色,相应临近色调 4 号色为中间色,以此类推,继中间色相应近色调 7 号色为结束色,借助色彩工具,在 CIELab 色彩空间下生成渐变色; +2. 暗色色板同理,起始色为 2 号色,中间色为相应邻近色调 5 号色,结束色为中间色邻近色调的 8 号色; +3. 保留未分段的色带,便于用户自由分段取数。 + + + +### 发散色色板 + +#### 定义 + +也称对比色渐变色板,一般是两种互补色(也可以是对比色)去展现数据从一个负向值到 0 点再到正向值的连续变化区间,显示相对立的两个值的大小关系。数据范围的两端同等强调中间值和极值,以表示断点(如零变化或平均值)周围与数据中特定有意义的中间值之间的差异。 + +#### 取色指南 + +关键断点应该采用中性颜色及与背景色对比度低,如浅灰色,端点应该采用和背景色对比度高的饱和颜色。一般来说是对称的,临界断点可以是平均值、中间值或零变化值。 + +#### 取色方法 + +1. 选取分类色板中的对比色或互补色,其中 7 号色起始色和结束色,4 号色为过渡色,灰阶色的 1 号色作为中间色,在 CIELab 色彩空间下生成渐变色; +2. 暗色色板同理,其中 8 号色起始色和结束色,5 号色为过渡色,灰阶色的 2 号色作为中间色; +3. 保留未分段的色带,便于用户自由分段取数。 + + + +### 语义色板 + +#### 定义 + +色彩在可视化中的使用,不仅是数据信息传递的可视化通道,同时也是更深一层的文化故事的载体,用于表达意义或情感。 + +#### 取色指南 + +重视用色习惯,遵循相关标准,色彩也不是都能寓意的,相当一部分图表色彩选择和感情因素无关,而是按照某种习惯来设定色彩,即所谓约定俗成,有的甚至形成来规范。 + + +> 语义色板(部分) + +更多色板介绍,详见 AntV 色板。 + +## 设计 Tips + +### 节点(Node) + +以圆形节点为例,根据点不同的信息展示形式,基础形状的大小有所不同,在图展示中需将图相关信息做最优展示。 + + +> 圆形节点——文本置内型,节点直径大小建议为 60px,文本大小为 12px + + +> 圆形节点——文本非置内型,节点直径大小建议为 16px,文本大小为 12px + +### 边(Edge) + +边的粗细:边在点默认大小场景下默认为 1px,通常根据视图大小变化等比例放大缩小。为保持信息有效可视,边最小为 1px,最大值为 12px。 + +### 组(Combo) + +Combo 的大小跟随内容,Node 与 Combo 之间的间隙最小为 Small = 8px。 + +### 色板使用 + +> 数据集:又称为资料集、数据集合或资料集合,是一种由数据所组成的集合。连续数值:统计学概念,又称连续变量。指在一定区间内可以任意取值、数值是连续不断的、相邻两个数值可作无限分割(即可取无限个数值)的数据。断点:文中主要指数据集的中心值或参考值,例如零变化或平均值。端点:文中主要指数据集极端值,例如最大最小值。语义:文中主要指色彩心理学中色彩在客观上对人们对一种刺激和象征,它在主观上又是一种反应和行为。包含从知觉、感情而到记忆、思想、意志、象征等与色彩的因果关系。 + + + +当需要用颜色作为视觉通道时,数据性质可作为参考因素,选用色板的步骤大致如下图: + + diff --git a/packages/site/docs/design/overview.en.md b/packages/site/docs/design/overview.en.md new file mode 100644 index 0000000000..a9541129d2 --- /dev/null +++ b/packages/site/docs/design/overview.en.md @@ -0,0 +1,199 @@ +--- +title: Design +order: 0 +redirect_from: + - /en/docs/design +--- + +In the article "Science of Complexity", Warren Weaver divides the history of modern science into three stages: Simple Problems, Disorderly Complex Problems, and Orderly Complex Problems, according to the complexity of the problems. After the middle of the 20th century, the academic community began to face more orderly and complex problems of instability, nonlinearity, and diversity. The industry urgently needed an analysis and exploration tool based on a network model and a way of thinking. Later, with the graph-based topological structure of the [Königsberg Seven Bridges Problem](https://en.wikipedia.org/wiki/Seven_Bridges_of_K%C3%B6nigsberg) being solved as a starting point, graph analysis is playing a role in more fields. In the Internet industry, with the development of big data and AI technology, more and more graph visual analysis has been used in many scenarios, such as community structure analysis, clustering, relationship prediction, graph learning, graph neural network, and network evolution, etc. + +
+ +img +
+> 《Science of Complexity》 - Warren Weaver 1948 +
+ +The demanding for design increases rapidly in various graph visual analysis products. What can designers do in this field? How to design a complex graph visual analysis product? What are the key points where we should pay attention on? The AntV design team summarized all these thinking as "G6 Graph Visual Design System", hoping to bring more thoughts and help more designers who are not familiar with this field. + +## 1. Introduction + +### 1.1 Common Application Scenarios + +In Alibaba and Ant Group, graph analysis is widely used in different scenarios such as cloud security, knowledge graph, enterprise risk control, graph database, etc. In abstract terms, there are two basic types: + +- Recording, clarifying, and revealing the fact: such as security monitoring and flow monitoring; +- The expansion and abstraction of the fact (prediction of the future): such as various technologies based on artificial intelligence, the prediction and real-time prevention for graphs.
img
+ +### 1.2 Types of Graph + +Nowadays, there is no unified graph classificationin both the industry and the academia. Based on AntV's business, we have summarized several common types of graphs: common network, flow chart, DAG diagram, architecture diagram, ER diagram, tree graph. Each graph type has specific usage scenarios and key points for designing. We provide a detailed introduction from the basic introduction, characteristics, applicable business scenarios, and design guidelines for graphs.
img
[Intro and Guidelines for Different Graph Type >](https://g6.antv.antgroup.com/en/design/template)
+ +## 2. Objects to be Designed + +With the design requirements of a graph, designers not only design the exterior of the graph, but also consider the following five layers for experience design. Starting from understanding business and product goals, we should know the user requirements, and then define the fuctional boundaries of the graph product cooperating with the product managers. And then, we build task processes, information frameworks and interface layout of the product based on scenarios. In the final stage, we polish the product interface, and most importantly the visual and micro-interaction design of the graph.
Five layers:
| Layers | Description | | ---- | ---- | | Surface | Visual perception layer: the visual design of the basic product interface, and the graph items such as nodes and edges | | Skeleton | Interface layout: such as canvas, components' layout and interaction logic | | Structure | Architecture of process and information: determine how to integrate multiple graph analysis modes in the product, the combination of various functional modules, and the construction of information framework | | Scope | Function/content requirement: define the boundary of graph analysis product | | Strategy | User requirmens, business goals, product goals | + +
+ +### 2.1 General Design Process + +From the perspective of graph analysis, the product usually goes through the following stages in order to transform the data source into valuable information to end users.
img
+ +> Quoted from "Graph Analysis and Visualization"
+ +### 2.2 General Graph Analysis Modes + +> Corresponds to the scope layer in the "five layers" + +Have explored the characteristics of the analysis scene, we divide the graph analysis mode of a product or an application into three types: with clear purpose, no clear purpose, and in special scenario. The interfaces for different analysis modes will not be the same, as well as the user's using process. For a full-featured graph analysis product, the three analysis modes may be available at the same time, resulting in an exponential increase in interface complexity. + +#### 2.2.1 With Clear Purpose + +This type of analysis mode has clear analysis or query conditions. The presentation of the condition may be a regular expression, a query statement, or a clear starting node and ending node, even directly viewing the defail info of an certain graph item. There are several common query modes: rule query, statement query, association analysis, filter/search on canvas, view details, etc. With clear purpose, it is usually necessary for end users to input clear conditional information by some input panels to expore and analyze the graph.
img
+ +#### 2.2.2 Without Clear Purpose + +So as to exploration without clear purpose, in order to explore the characteristics of the data and discover valuable infomation, some basic analysis interaction such as N-degree expansion, drill-down analysis, sub-graph exploration, and undo rollback based on the existing data will be helpful. + +
+img +
+ +#### 2.2.3 In Special Scenarios + +##### A. Analysis Scenarios with built-in AI algorithm + +In the scenarios with built-in AI algorithm, algorithms or rule reasoning capabilities are necessary to quickly mine target nodes and relationships from massive data, such as: guarantee loop in financial scenes, shortest path, etc. + +
+img +
+ +##### B. Scenarios with Time-series or Geographic Information + +When there is itme-series or geographic infomation in the source data, the graph visualization is usually cmbined with timebar or a map. + +
+ +
+ +### 2.3 Common Components + +> Corresponds to the frame layer in the "five layers" + +The styles of nodes and edges are the basic design for graph visualization. To form a complete graph analysis product, there are various components undertaking different functions. From the perspective of experience design, common components can be categorized as: + +- Basic components: legend, toolbar, right-click context menu, view control bar, system log, etc.; +- Condition input: query panel, filte panelr, search bar, canvas settings, etc.; +- Information output: details panel, tooltip, canvas info panel, etc.; +- Advanced functions: timeline, snapshot gallery, analysis report, etc. + +
+img +
+[The Buit-in Components of AntV >](https://g6.antv.antgroup.com/en/design/component/componentOverview) +
+ +In some special scenarios, it is also necessary to upgrade and optimize basic components based on the actual business requirements, or even design new components based on the unique capabilities of the product. When optimizing a basic component or designing a brand-new component, it is necessary to combine the actual functional requirements and get a full picture of several perspectives such as usage scenarios, constituent elements, common types, and interactive instructions. Take the two newly designed components of AntV as examples: + +- [TimeBar](https://g6.antv.antgroup.com/en/design/component/timebar) +- [View Control ToolBar](https://g6.antv.antgroup.com/en/design/component/viewToolbar) + +### 2.4 Intereaction Design + +A complete interaction design usually consists of triggers, rules, and feedback. Usually, in a graph analysis product, the common triggers of the interaction behaviors are mouse, keyboard, and touchpad. The rules includes common ones such as expanding a node when double-clicking and highlighting the node when clicking. The feedback is the result presented by the system to end users according to different 'rules', it is indicated by the changes of the items' visual styles in most cases. The Guideline for Intereaction Design > + +#### 2.4.1 General Interaction & Extended Interaction + +According to whether the interaction event is a global common behavior, the interaction in G6 is categorized as: "universal" and "extended": + +- Universal interaction: not related to any bussiness attributes, it is a set of basic toolboxes that adapt to most of the interactive exploration of graphs, and the scope is delineated within the basic keyboard and mouse operations, so that most computer users are able to quickly explore data with it; +- Extended interaction: not necessarily suitable for all scenarios and business attributes. With the powerful scalability and capabilities of G6, the interactions are not limited to the universal ones. It can be triggered by ordinary triggers, other events, or real-time business data. Users of G6 are able to custom the exploration path for their own scenarios freely. + +#### 2.4.2 Object to be Interacted + +"Interaction" enables users to change from passive "watchers" to active "exploratory analysts" and better participate in the process of data understanding and analysis. The purpose of analysis products is not only to deliver knowledge to users, but to provide a tool to help users explore and analyze the large data, and finally get the desired conclusions. A complete graph analysis product consists of multiple basic interactions which might be combined together according purposes and usages. The objects to be interacted inlude canvas, node, edge, combo, and others.
img
+ +### 2.5 Visual Design + +> Corresponds to the performance layer in the "five layers"
Aside from the product interface, the visual design of the graph is essentially a process of establishing mapping channels between visual attributes and data characteristics to form a specific semantic association. A good visual design can greatly improve the information transmission efficiency of the graph. The main items of a graph are nodes and edges. On a node or an edge, there might be text labels and other auxiliary shapes. Consider to the visual design of the graph, the elements should to be disassembled and designed separately, and different interaction events and data attributes should be considered globally. Data properties should be mapped to corresponding visual attributes. In the same time, and final effect of integated visual attributes is also important. Common visual attributes in design are: shape, color, size, direction, material, brightness, position, etc. The design for most basic attributes: shape and color, will be introduced in detail.
[The Visual Design Guidelines >](https://g6.antv.antgroup.com/en/design/global/style)
+ +#### 2.5.1 Shape + +The nodes of the graph can be customized according to the actual requirements of the business scenario. In order to present specific information, the nodes can be customized into a special style, or combined with statistical charts (such as donut chart, line chart, etc.). + +
+ +
+ +Regardless of nodes or edges, the visual feedbacks with different mouse events should be considered in the same time:


+ +#### 2.5.2 Color + +Based on AntV's color system, and combined with the characteristics of the graph visualization, we fine-tuned the color measurement and linear perception in the data dimension, and built-in a series of beautiful, harmonious and barrier-free design principles for G6, including: classification palette, adjacent palette, divergent palette, and semantic palette. By default, blue is the case color of the basic style.# + +
+img +
+ + +
+ +Meanwhile, we also provide dark and light theme styles to meet different application scenarios:

+ +#### 2.5.3 Others + +The visual design of a graph is not as simple as stacking multiple visual attributes. Generally, the dimensions for design inludes mouse events, data characteristics, and business semantics. In the most complicated case, all three dimensions above need to be considered comprehensively. For example, the node with "Type B" in the following table should meet the condition A, and the visual effect with various mouse events should be taken into consider meanwhile. It is true that not all businesses will encounter such complex situations. In specific business scenarios, the three elements "triggers + rules + feedback" of user scenarios and interactive events can be combined to comprehensively find the most appropriate visual expression. + +| Information Dimension | Explanation | Style Example | +| --- | --- | --- | +| Mouse Event | Common mouse events: Default, Active, Selected, Disable, etc. | | +| Data Characteristics | The inherent characteristics of graph data, such as the data type of nodes or edges | | +| Business Semantics | Nodes that meet certain rules need special styles to emphasize highlighting | | + +## 3. Design Guidelines + +### 3.1 Start with Questions + +It is reasonable for a graph product project to start from the problem to be solved and lead to in-depth exploration gradually. Designing a complete graph analysis product is actually a process of continuously answering the problems encountered in the process of exploration. In other words, the first step in a good graph analysis scenario is to "ask the right questions." + +### 3.2 Switch Perspectice + +Under different perspectives and layouts, unique patterns of the graph will be highlighted. + +
+img +
+img +
+ +### 3.3 Details Exploration + +The amount of information that people can receive at a time is limited. Considering this limit, the graph visualization should express the detailed information with restraint, and express moderate content in the appropriate scene. It is recommended to follow the principle of "gradual presentation": overview first, zoom and filter, then details on demand. Common methods for details exploration are: + +- Pan and zoom: Similar to the zoom effect of the map software, present focus area at different scales, while the context might be out of the screen; +- Global + details: A common tool is minimap. When the user focus on the details, the global context is in the minimap; +- Focus + context: A typical focus+context tool is fisheye, which magnifies the interested area while the context area is deformed to provide more room. It makes sure the context and the relationships between focus and context are not lost. + +### 3.4 Time-series Graph Vis + +In time-series graph data, event s or objects occur or disappear at certain time points. A helpful tool TimeBar can help the visualization effectively display time-series data. The TimeBar component has been provided in the G6.
img
+ +### 3.5 Reversible Operation + +The interactions are very important in graph analysis products. Users usually perform a set of analysis actions on the canvas to obtain results. In order to ensure the important information is not missed and prevent wrong operations, the product should provide a reversible operation mechanism to allow the end users to go back or repeat the previous operation. + +### 3.6 End with Action + +The ultimate goal of graph analysis products is to allow the end users to get the 'answers' they want, and obtain valuable information. Based on that, user could perform the next actions. To achieve that, download/share analysis results, publish analysis rules for online service or precipitation as a template for exploratory analysis, or directly input the analysis results as a part into the complete production chain are neccesary as the end action. In short, a well-experienced graph analysis product must be able to establish a complete closed loop for experience from "questioning" to "action". + +
+img +
+ +## 4. Reference + +- [The Aesthetics of Graph Visualization - Chris Bennett, Jody Ryall, Leo Spalteholz and Amy Gooch1](https://www.researchgate.net/publication/220795329_The_Aesthetics_of_Graph_Visualization) +- [Graph Analysis and Visualization - Richard Brath/David Jonker](https://www.wiley.com/en-us/Graph+Analysis+and+Visualization%3A+Discovering+Business+Opportunity+in+Linked+Data-p-9781118845844) +- [Visual Complexity_Mapping Patterns of Information - Manuel Lima](http://goodreads.com/en/book/show/10327296) +- [Data Visualization - Wei Chen / Zeqian Shen](https://book.douban.com/subject/25760272/) diff --git a/packages/site/docs/design/overview.zh.md b/packages/site/docs/design/overview.zh.md new file mode 100644 index 0000000000..ff58751897 --- /dev/null +++ b/packages/site/docs/design/overview.zh.md @@ -0,0 +1,214 @@ +--- +title: 总则 +order: 0 +redirect_from: + - /zh/docs/design +--- + +## 引言 + +在「科学与复杂性」一文中,科学家 Warren Weaver 按学界面临问题的复杂性,将现代科学史划分为「简单问题、无序复杂问题、有序复杂问题」3 个阶段。20 世纪中叶之后,学界开始面临更多不稳定性、非线性和多元性的有序复杂问题,业界急需一种基于网络模型的分析探索工具,和思考方式。后来,以 柯尼斯堡七桥问题 基于图的拓扑结构被解决为起始点,图可视分析在越来越多的领域发挥作用,在互联网行业,随着大数据与 AI 技术发展,越来越多的业务场景用上了图可视分析,如社区结构分析、聚类、消息传播、节点分类、链接预测、图表示学习、图神经网络、网络演化等。 + +img + +> 《Science of Complexity》 - Warren Weaver 1948 + +这些形形色色的图可视分析产品,对设计的需求越来越旺盛。设计在这一领域又能做什么?该如何设计一个复杂的图可视分析产品?图可视化的视觉和交互设计又有哪些值得注意和思考的点?AntV 设计小组把我们在这一领域的思考和沉淀总结成「G6 图可视化设计体系」,希望能给更多设计师在这一陌生的设计领域带来更多思考和帮助。 + +## 简介 + +### 常见应用场景 + +在阿里、蚂蚁集团,图可视分析广泛应用于云安全、知识图谱、企业风控、图数据库等不同的业务场景。抽象来看基本上有两种类型: + +- 对现状的记录、阐明、揭示:如对机房安全,流量监控等现状的监控; +- 对现状的扩展、抽象(对未来的预测):如基于人工智能的各类技术,对于图的预测和实时防控。 + +img + +### 图的种类 + +关系图种类划分,无论业界还是学界目前都没有一个统一的定论。基于 AntV 自身的业务,我们总结归纳出几种常见图的类型:关系图、流程图、DAG 图、血缘图、ER 图、树状图。每种图都有其自身的使用场景以及设计时需要考量的点。我们从图的基本介绍、特点、适用业务场景、设计指引几个方面提供了详细的介绍。 + +img + +[查看图的种类详细介绍和设计指引 >](/zh/docs/design/global/style) + +## 设计对象 + +设计师在面对一个关系图设计需求时,不仅需要设计一张图「看上去」如何,更需要考虑体验设计五要素的每一层。从了解业务和产品目标出发,洞察用户需求,同产品经理一起定义图分析产品的功能边界,再基于用户场景去构建产品的任务流程,信息框架和界面布局,最终再去打磨产品界面以及最重要的图本身的视觉和微交互设计。 + +| 名称 | 描述 | +| --- | --- | +| 表现层 | 视觉感知层:产品基础界面和图本身节点和边的视觉设计 | +| 框架层 | 界面布局:如画布、各类组件的布局关系和使用逻辑 | +| 结构层 | 流程、信息架构:确定产品中多个图分析模式如何整合为一体,各种功能的模块组合,信息框架的构建 | +| 范围层 | 功能/内容需求:定义产品图分析功能和呈现内容的边界 | +| 战略层 | 用户需求,业务目标,产品目标 | + +### 通用流程 + +从图分析的角度来看一个图分析产品通常会历经如下几个阶段,以将数据源转化为对用户有价值的信息。 + +img + +> 引用自《图分析与可视化》 + +### 常见图分析模式 + +> 对应「五要素」中的范围层 + +按图产品探索分析场景的特性,我们将图分析产品的分析模式为**有明确目的**、**无明确目的**和**特殊场景**三种。不同的分析模式界面会有所不同,用户的使用流程也会有所差异。诚然,一个功能完备的图分析产品,3 种分析模式可能会同时具备,从而导致界面复杂度成倍的提升。需要设计师能够抽丝剥茧,知其所以然,理清楚每一种模式各自特点之后,再结合实际的用户场景,全盘考虑产品设计方案。 + +#### 有明确目的 + +这类分析模式是有明确的分析或查询条件,这个条件的呈现形式可能是一个规则表达式,一段 Gremlin 或 GQL 的查询语句,或明确的起点和终点,甚至是直接查看某个节点或某条边的具体信息。常见的模式有:规则查询、Gremlin 查询、关联分析、筛选/搜索画布、查看详情等。这类模式下,通常需要通过搜索或在各种类型的输入面板中,输入查询语句、规则等明确的条件信息,来进行探查和分析。 + +img + +#### 无明确目的 + +无明确目的地探索是指基于已有数据内容,进行关系的 N 度扩展、下钻分析、子图探索、撤销回退等操作,来挖掘数据中的特性,发现价值或机会点的分析过程。 + +img + +#### 特殊场景 + +**内置了 AI 算法能力的分析场景** + +这类分析场景通常需要借助内置的算法或规则推理能力来实现,从海量数据中快捷的挖掘出符合特定规则的目标节点和关系,常见的有:担保圈、实控人、最短路径等。 + +img + +**结合时间或地理信息的场景** + +在源数据中含有时间和地理维度的内容时,会出现结合时间或地理信息的分析场景。 + +img + +### 常见组件 + +> 对应「五要素」中的框架层 + +点和边的基础样式是所有图可视化的基石,要组成一个完整的图分析产品,还需要有各类组件来承担不同的功能。从体验设计的角度来看,常见的组件可分为如下几种类型: + +- 基础组件:图例、工具栏、右键菜单、视图控制栏、系统日志等; +- 条件输入:查询、筛选、搜索、画布设置等; +- 信息输出:详情面板、气泡、Tooltip、画板信息等; +- 高级功能:时间轴、快照画廊、分析报表等。 + +[查看 AntV 内置的各类组件 >](/zh/docs/design/component/componentOverview) + + + +在某些特殊场景下,也需要结合业务实际情况基于基础组件去升级优化,乃至基于产品独有的能力去设计全新的组件。在优化一个基础组件或设计全新的组件时,需要结合实际的功能需求,从使用场景、构成元素、常见类型、交互说明等几个角度完整的思考清楚。以 AntV 最新设计的两个组件为例: + +- [时间轴 TimeBar](/zh/docs/design/component/timebar) +- [视图控制栏 View ToolBar](/zh/docs/design/component/viewToolbar) + +### 交互设计 + +一个完整的交互设计行为通常由触发器、规则和反馈组成。在图分析产品常见交互行为中,触发器通常是常见的鼠标、键盘、触控板;规则通常有节点双击时展开,单击时高亮等常见通用规则;而反馈则是各个交互的操作对象根据不同的「规则」所呈现出来的行为或样式表达,通常以各类视觉属性变化的形式出现。 + +[查看图交互设计指引 >](/zh/docs/design/global/interactive) + +#### 通用交互 & 扩展交互 + +按照交互事件是否全局或跨产品通用,将 G6 以及 Graphin 中的交互分为「通用」和「扩展」两类: + +- 通用交互,剥离业务属性,是一套适应大部分关系图交互探索的基础工具箱,并将范围圈定在了基础键鼠操作内,令普通电脑用户也能迅速地利用其对数据进行探索; +- 扩展交互,不一定适应所有的应用场景与业务属性,但承载了 G6 强大的扩展性与能力,其范围不受限制,既可以是普通的交互行为触发,也能被其他事件触发,亦或由实时的业务数据触发等,用户能通过这些交互对数据进行更深度更定制化的探索。 + +#### 操作对象 + +「交互」能使用户从被动的「看客」成为主动的「探索分析者」,更好地参与对数据的理解和分析的过程。可视分析产品的目的也不仅是向用户传递定制好的知识,而是提供一个工具来帮助用户在海量数据中进行探索分析,并最终得到想要的结论。 一个完整地图分析产品必定是由诸多的基础交互事件按照不同的目的和使用场景组合在一起的,为了将各类复杂的交互事件抽丝剥茧,交互事件的操作对象通常有:画布、节点、Combo、边和其他。 + +img + +### 视觉设计 + +> 对应「五要素」中的表现层 + +抛开图分析产品本身产品界面,**关系图的视觉设计本质上是把视觉属性和数据特性建立映射关系,形成特定的语义关联的过程**。好的视觉设计,能极大的提高关系图的信息传达效率,降低用户的认知成本。图最核心的元素就是点、边、以及点边上的文字标签,在考虑图的视觉设计时,需要将这些元素的组成元素拆解来看,单独设计,并全局考虑不同交互事件,数据属性和业务场景下的视觉展示需要和视觉属性的映射关系,以及不同元素的视觉属性整合到一起时的视觉表达。在可视化设计中常见的视觉属性有:形状、颜色、大小、方向、材质、明度,位置等。以最基础的形状和颜色的设计为引入进行详细介绍。 + +[查看视觉设计指引 >](/zh/docs/design/global/style) + +#### 形状 + +关系图的节点可根据业务实际场景需要,为表达特定的某类信息,可将节点定制为特殊样式,或将节点与常见的二维图表(如:环形图,玫瑰图等)结合,以展示更多信息,甚至在需要突出强调表达某种业务独有的属性和特点时,可尝试用 3D 图形来表达。 + +img + +无论节点还是边,在设计时,都需要考虑在不同鼠标事件的情况下的视觉表现: + +img + +img + +#### 颜色 + +G6 在 AntV 的基础色彩体系的基础上,结合关系图表达的特点,精细化调整了颜色在数据维度上的衡量和线性感知。内置了一系列优美、和谐且满足无障碍设计原则的色板。包括:分类色板、邻近色板、发散色板、语义色板。默认情况下以蓝色为基础样式的案例色,也是 G6 的默认主色。 + +img + +img + +同时,我们还提供深、浅色两套主题样式,以满足不同的应用场景: + +img + +#### 其他 + +完整的关系图可视化视觉设计,不是单纯的将不同的视觉属性机械地叠加在一起。通常情况下,一个图分析产品中的视觉设计需要考虑的信息维度有:鼠标事件、数据特性、业务语义。最复杂的情况下,3 个维度的信息都需要综合考虑,比如:下图案例中的「类型 B」在「满足条件 A」的情况下,也需要考虑各种鼠标事件情况下的视觉展示情况。诚然不是所有业务都会遇到如此复杂的情况,在具体的业务场景中可结合用户场景以及交互事件的三要素「触发器+规则+反馈」,来综合判断和决策最适当的视觉表达形式。 + +| 信息维度 | 释义 | 样式示例 | +| --- | --- | --- | +| 鼠标事件 | 常见的鼠标事件: Default、Active、Selected、Disable 等 | | +| 数据特性 | 图数据本身固有的特性,如节点或边的数据类型 | | +| 业务语义 | 符合某些规则的节点需要特殊样式强调高亮 | | + +## 设计指引 + +### 始于发问 + +每一个图产品项目都应该从要解决的问题出发,逐步导向深入的探索,设计一个完整的图分析产品其实就是不断解答探索图过程中遇到的问题的过程。换句话说,一个好的图分析场景的第一步就是「问对问题」。 + +### 切换视角 + +在不同的视角和布局下,关系图会表现出不一样的模式和行为,呈现出的内容重点也不尽相同。 + +img + +img + +### 管理细节 + +人一次性能接受到的信息量有限,考虑人对信息认知极限,克制地表达关系图中的细节信息,在合适的场景下表达适度的内容。建议遵循「渐进呈现」的原则,先概览,再放大过滤,最后看细节。常见的管理细节的方法有: + +- 缩放:参考地图软件的缩放的效果,在不同的比例下呈现不同核心内容; +- 全局+细节:常见的形式有缩略图,在画布中查看细节内容时,同时能在缩略图中获取全局的信息; +- 焦点+背景:常见的形式有鱼眼、焦点聚焦等突出中心内容弱化周边节点的形式。 + +### 考虑时间 + +数据很少是静态的,某件事在某个时间点爆发或者持续间发生,关联的因子有多个,时间轴工具可以有效展示动态时序数据、分析图数据关联因子。G6 图可视化中已经提供了完整的「时间轴」组件。 + +img + +### 操作可逆 + +图分析产品往往是非常重交互的场景,通常用户会在画布执行连续分析的动作或通过一连串的分析行为才拿到有效的结果信息,为了确保用户不错过有效的信息以及防止错误的操作,如误删了某些节点或边或扩展了太多节点,产品中需要提供操作可逆的机制,能够允许用户回退或重复上一步操作。 + +img + +### 终于行动 + +图分析产品的最终目的是让用户得到想要的「答案」,获取到有价值的信息,并以这些信息为基础去执行下一步动作,常见的有:下载/分享分析结果、将分析规则发布为在线服务或沉淀为探索分析模板、或直接将分析结果作为完整生产链路中的一环输入给下游环节。一言以蔽之,一个体验良好的图分析产品,必定能从「发问」到「行动」形成完整的产品体验闭环。 + +## 参考 + +- [The Aesthetics of Graph Visualization - Chris Bennett, Jody Ryall, Leo Spalteholz and Amy Gooch1](https://www.researchgate.net/publication/220795329_The_Aesthetics_of_Graph_Visualization) +- [图分析与可视化\_在关联数据中发现商业机会 - Richard Brath/David Jonker](https://book.douban.com/subject/26756024/) +- [Visual Complexity_Mapping Patterns of Information - Manuel Lima](https://book.douban.com/subject/25665238/) +- [AntV 图可视分析解决方案]() +- [数据可视化 - 陈为 / 沈则潜](https://book.douban.com/subject/25760272/) diff --git a/packages/site/docs/design/principles.en.md b/packages/site/docs/design/principles.en.md new file mode 100644 index 0000000000..bb1eabfe26 --- /dev/null +++ b/packages/site/docs/design/principles.en.md @@ -0,0 +1,10 @@ +--- +title: Design Principles +order: 1 +redirect_from: + - /zh/docs/design +--- + +**🛠The English Version is in Prograss🛠** + + diff --git a/packages/site/docs/design/principles.zh.md b/packages/site/docs/design/principles.zh.md new file mode 100644 index 0000000000..d4ecf74ae3 --- /dev/null +++ b/packages/site/docs/design/principles.zh.md @@ -0,0 +1,46 @@ +--- +title: 设计原则 +order: 1 +redirect_from: + - /zh/docs/design +--- + +AntV 的设计原则是基于 Ant Design 设计体系衍生的,它在遵循 Ant Design 设计价值观的同时,对数据可视化领域的进一步解读,如色板、字体的指引。 + +G6 作为 AntV 图可视化技术栈,在设计上依然遵循 AntV 的四条核心原则:准确、清晰、有效、美,这四条原则按重要等级先后排序,相辅相成且呈递进关系。 + + + +### 准确 + +可视表达时不歪曲,不误导,不遗漏,精准如实反应数据的特征信息。 + +例如:关系图的箭头指向必须清晰、明确,否则在大数据展示时容易辨识不清。在图形上,我们对三角形进行一定的裁剪,让其像指南针一样有明确的指向性,比较下面两图,左侧的箭头能清晰辨别指向性,而右侧的箭头在识别时会有些困惑。 + + + +### 清晰 + +清晰包括两个层面,结构清晰 与 内容清晰。 + +结构清晰:数据可视化呈现的是一幅作品,它是制作者分析思路的呈现,其布局决定阅读者的浏览顺序。清晰的平面布局能很好的帮助阅读者获取信息。下图展现的是同一组数据下不同布局的关系图,例如环状布局可用于突出数据结构中的环;辐射状布局是辐射状树布局的扩展,可突出关注点与其他节点的最短路径关系;格子布局可以让图看起来十分规整,利于探索。 + + + +内容清晰: + +不让用户带着疑惑看图是我们始终不变的追求。例如在 G6 中经常会出现成千上万个节点,为了让用户能对所有节点感受的更直观,我们设计了鱼眼功能,同时查看图的全貌和局部,可以清晰地描绘出焦点所在节点与其邻居之间的直接关系。 + + + +### 有效 + +信息传达有重点,克制不冗余,避免信息过载,用最适量的数据-油墨比(Data-ink Ratio)表达对用户最有用的信息。 + + + +### 美 + +美是一种克制,合理利用视觉元素进行映射,运用格式塔原理对数据进行分组,既能帮助用户更快的获取信息,也能在一定程度上建立一种秩序美、规律美。 + + diff --git a/packages/site/docs/design/template.en.md b/packages/site/docs/design/template.en.md new file mode 100644 index 0000000000..74b5658514 --- /dev/null +++ b/packages/site/docs/design/template.en.md @@ -0,0 +1,8 @@ +--- +title: Template +order: 5 +--- + +**🛠The English Version is in Prograss🛠** + + diff --git a/packages/site/docs/design/template.zh.md b/packages/site/docs/design/template.zh.md new file mode 100644 index 0000000000..7b1a593da6 --- /dev/null +++ b/packages/site/docs/design/template.zh.md @@ -0,0 +1,183 @@ +--- +title: 模版 +order: 5 +--- + +## 流程图 + +### 名词解释 + +流程图是表示算法、工作流或流程的一种框图表示,它以不同类型的框代表不同种类的步骤,每两个步骤之间则以箭头连接。这种表示方法便于说明解决已知问题的方法。 + +### 特点 + +流程图背后可以概括了各节点类型、其内容及其他补充用的信息。在设计或者记录一些简单的步骤或程序都会用得上流程图。与其他图表一样,这种图表可以帮助可视化发生了什么事情,从而更易去理解中间的工序。虽然有很多𧗠生出来的版本,各目有各目的标示方式,它们大都都有以下 2 种的符号: + +- 步骤:通常称作“活动”,常以长方形来表示; +- 决定:常以钻石形来表示。其他常用形状: + +| 形状 | 名称 | 描述 | +| --- | --- | --- | +| ‘img’/ | 起止符号 | 用来表示次要或程序的开始与完结。常以一个圆角长方形表示。通常里面会标上“开始”或“结束”或其他相关字眼,如“提交查询”或“接受产品”。 | +| ‘img’/ | 程序 | 以长方形来代表一系列程序去改变量值、形式、数据的位置。 | +| ‘img’/ | 决策判断 | 以一个菱形去显示一个条件进程,用来按情况去决定下一步走向。通常以“是/否”或“真/假”值去决定。 | +| ‘img’/ | 输入/输出 | 以平行四边形来据输入或输出的过程,即填入数据或显示工作结果的步骤。 | +| ‘img’/ | 已定义流程 | 用一个有 2 条左右垂直线长方型,来表示一个已在其他地方定义了的过程。 | +| ‘img’/ | 同页参考 | 用一个含有字母的小圆圈来连接目标流程画于同一页上。 | +| ‘img’/ | 换页参考 | 用一个倒画的屋型来表示目标流程画于另一页上。 | + +### 业务场景 + +流程图是流经一个系统的信息流、观点流或部件流的图形代表。在企业中,流程图主要用来说明某一过程。这种过程既可以是生产线上的工艺流程,也可以是完成一项任务必需的管理过程。典型场景: + +- 组织结构图:组织结构图是把企业组织分成若干部分,并且标明各部分之间可能存在的各种关系。例如上下级领导关系(组织机构图),物流关系,资金流关系和资料传递关系等; +- BPMN:用于以业务流程模型详细说明各种业务流程; +- UML:UML 立足于对事物的实体、性质、关系、结构、状态和动态变化过程的全程描述和反映; +- EPC 事件过程线图:适合诸如 B2B、供应链流程管理、仓储物流管理等商业化业务流程。 + +
+‘img’ + +### 绘制流程图的 Tips + +- 流程图一般是用标准的符号绘制的,并非严格要求使用这些方框,圆圈,菱形或其它标准的符号来制作一个流程图,但是标准符号确实能更清晰地展示事件的类别。以下是大多数情况常用的一组标准符号 +- 常用的流程图方向是从左到右或者从上到下: +- 上下流程图:上下流程图是最常见的一种流程图,它仅表示上一步与下一步的顺序关系; +- 矩阵流程图:矩阵流程图不仅表示下下关系,还可以看出某一过程的其他关系; +- 在标准流程图符号内简要说明符号代表的内容。必要时,你可以用注释符号更清晰地描述数据或计算步骤; +- 确保流程图有一个逻辑的起点和终点; +- 用简单的测试数据来测试流程图的有效性是非常有用的。 + +## DAG + +### 名词解释 + +DAG 是 Directed Acyclic Graph 的缩写,即有向无环图,它是指图中一个点经过两种路线到达另一个点没有闭环。它原本是计算机领域一种常用数据结构,因为独特的拓扑结构所带来的优异特性,经常被用于处理动态规划、导航中寻求最短路径、数据压缩等多种算法场景 + +### 特点 + +DAG 相比于目前的公链技术,其实是图和链的区别,对于链而言,无法只处理一个局部,因为链的入度和出度只有一个,不能把链上的节点拆成好几个节点去处理,但是对于图却可以,因为图可以有多个出度,那么可以同时处理多个出度连接的节点。所以,它的特点是: + +- DAG 图可以有编辑态(拖拉拽画成一个图),也可是只读态(供阅读者查阅读); +- 交易速度块,DAG 实现的局部处理和并行结算可以使得交易速度大幅度提升; +- 拓展性强,因为各个节点无需等待同步其他的节点的数据就可计算使得记账节点很容易答复延展,因此 DAG 很适用于物联网类项目; +- 作恶难度更大,相比于链式结构,在 DAG 中恶意修改的难度会大很多,因为 DAG 拥有着很多的出度和入度,假如要修改某一个节点,那么对应的出入度都要进行修改。 + +### 业务场景 + +DAG 图常用来描述业务流程,典型场景: + +- 人工智能产品,可以通过 DAG 图可以将一个复杂的人工智能实验流程给图形化出来,大大降低理解成本; +- 系统架构:表达一个系统架构各个层各个实例之间的关系,有明确的分层; +- 交易系统:表达资金、交易等流转情况。 + +### 设计示例图 + +‘img’ + +### 绘制 DAG 图的 Tips + +- 一定有明确的方向,从上到下,从左到右等,不存在节点指向方向逆反; +- 一个节点可能会拆分出多个节点,前后存在包含、关联等关系; +- 除业务特性外,图没有唯一的开始结束节点。 + +## 血缘图 + +### 名词解释 + +Data Lineage 数据血统,也叫做 Data Provenance 数据起源或 Data Pedigree 数据谱系。任何的数据,从产生、ETL 处理、加工、融合、流转,到最终消亡,数据之间自然会形成一种关系。好比人类社会中的人际关系,类似的一种关系来表达数据之间的这种关系,称之为数据的血缘关系。 + +### 特点 + +- 归属性:特定的数据归属特定的组织(或个人),数据具有归属性; +- 多源性:同一个数据可以有多个来源, 一个数据可以是多个数据经过加工而生成的,而且这种加工过程可以是多个; +- 可追溯性: 数据的血缘关系,体现了数据的生命周期,体现了数据从产生到消亡的整个过程,具备可追溯性; +- 层次性: 数据的血缘关系是有层次的。对数据的分类、归纳、总结等对数据进行的描述信息又形成了新的数据,不同程度的描述信息形成了数据的层次。 + +### 业务场景 + +- 追踪数据溯源:当数据发生异常,帮助追踪到异常发生的原因,平时也能帮助我们追踪数据的来源,追踪数据处理过程; +- 评估数据价值:要对数据价值进行评估,就需要有依据,数据血缘关系,可以从#数据受众、数据更新量级、数据更新# 频次等方面给数据价值的评估提供依据; +- 数据质量评估:数据的血缘关系图上,可以方便的看到数据清洗的标准清单,这个清单反映了对数据质量的要求。 + +### 设计示例图 + +‘img’ + +### 绘制血缘图的 Tips + +- 数据节点有三种类型:主节点,数据流出节点,数据流入节点; +- 主节点只有一个,一般位于整个图形的中间,是可视化图形的核心节点。图形展示的血缘关系就是此节点的血缘关系,其他与此节点无关的血缘关系都不在图形上展示,以保证图形的简单、清晰; +- 数据流入节点可以有多个,是主节点的父节点,表示数据来源; +- 数据流出节点也可以有多个,是主节点的子节点,表示数据的去向;包括一种特殊的节点,即终端节点,终端节点是一种特殊的数据流出节点,表示数据不再往下进行流转,这种数据一般用来做可视化展示; +- 流转线表现的是数据的流转路径,从左到右流转。数据流转线路从数据流入节点出来往主节点汇聚,又从主节点流出往数据流出节点扩散; +- 数据流转线路表现了三个维度的信息,分别是方向、数据更新量级、数据更新频次; +- 方向的表现方式,没有做特别的设计,默认从上到下流转; +- 数据更新的量级通过线条的粗细来表现。线条越粗表示数据量级越大,线条越细则表示数据量级越小; +- 数据更新的频次用线条中线段的长度来表现。线段越短表示更新频次越高,线段越长表示更新频次越底,一根实线则表示只流转一次。 + +## 关系图 + +### 名词解释 + +关系图,又称关联图,是用来分析事物之间“原因与结果”、“目的与手段”等复杂关系的一种图表,它能够帮助人们从事物之间的逻辑关系中,寻找出解决问题的办法。关系图由圆圈(或方框)和箭头组成,其中圆圈中是文字说明部分箭头由原因指向结果,由手段指向目的。 + +### 特点 + +图是一个高度抽象的模型,数据中的各种关系都能用图表示。图中的点和边,可以非常灵活,不局限于现实意义的实体。最直接能想到的当然是某个人可以是点,某个产品可以是点,它们之间的联系是边;除了点与点之间的联系外,还分为空间聚集的聚类,反应出不同团队、不同聚类之间的关系。 + +### 业务场景 + +图分析的四种广泛使用的类型包括路径分析,连通性分析,社区分析和集中性分析: + +- 路径分析:这种类型的分析可用于确定图中两个节点之间的最短距离。例如路线优化,特别适用于物流,供应和分销链以及智慧城市的交通优化; +- 连通性分析:这种类型的图形分析可用于确定诸如公用电网之类的网络中的弱点。它还可以比较跨网络的连接; +- 社区分析:基于距离和密度的分析用于查找社交网络中互动的人群; +- 集中度分析:这种分析类型能够识别相关性,例如在社交网络中找到最有影响力的人,或找到访问量最高的网页。 + +### 设计示例图 + +‘img’ + +### 绘制关系图的 Tips + +- 把对象变成点,点的大小、颜色可以是它的两个参数, +- 两个点之间的关系可以用连线来表示。连线分为无向(只是连接的导向,一些简单的关系很容易体现)和有向(复杂网络,连接+方向)。 +- 线本身的方向代表了连接的关系同时线的粗线也可以表示线的连接强度 + +## E-R 图 + +### 名词解释 + +实体关系图也称 ER 模型(是指以实体、关系、属性三个基本概念概括数据的基本结构,从而描述静态数据结构的概念模型),全称为实体联系模型或实体关系模型,是概念数据模型的高层描述所使用的数据模型或模式图。 + +### 特点 + +ER 图分为实体、属性、关系三个核心部分。 + +- 实体:即数据模型中的数据对象,例如人、学生、音乐都可以作为一个数据对象; +- 属性:即数据对象所具有的属性,例如学生具有姓名、学号、年级等属性; +- 关系:用来表现数据对象与数据对象之间的联系,例如学生的实体和成绩表的实体之间有一定的联系,每个学生都有自己的成绩表,这就是一种关系。 + +ER 图中关联关系有三种: + +- 1 对 1(1:1) :1 对 1 关系是指对于实体集 A 与实体集 B,A 中的每一个实体至多与 B 中一个实体有关系;反之,在实体集 B 中的每个实体至多与实体集 A 中一个实体有关系; +- 1 对多(1:N) :1 对多关系是指实体集 A 与实体集 B 中至少有 N(N>0)个实体有关系;并且实体集 B 中每一个实体至多与实体集 A 中一个实体有关系; +- 多对多(M:N) :多对多关系是指实体集 A 中的每一个实体与实体集 B 中至少有 M(M>0)个实体有关系,并且实体集 B 中的每一个实体与实体集 A 中的至少 N(N>0)个实体有关系。 + +### 使用场景 + +一般在逻辑和物理数据库设计中使用,包括信息工程和空间建模。也可以用在两个或更多实体相互如何关联; + +- 常用于信息系统设计中:在概念结构设计阶段用来描述信息需求或要存储在数据库中的信息类型,作为用户与分析员之间有效的交流工具; +- 描述感兴趣区域的任何本体:对使用的术语和它们的联系的概述和分类,用实体、联系和属性这三个概念来理解现实问题。 + +### 设计示例图 + +‘img’ + +### 绘制 E-R 图的 Tips + +- 首先确定这个模块有哪几个核心的对象以及具体有哪些特征;其次思考这些对象之间的关系,如何相互转变;最后把他们用 ER 图的方法表述出来; +- 方形表示实体,一般是名词;菱形表示联系,一般是动词;椭圆表示属性,一般是名词;线表示联系; +- 尽量精简实体以及优化属性。 diff --git a/packages/site/docs/manual/FAQ/angular-support.en.md b/packages/site/docs/manual/FAQ/angular-support.en.md new file mode 100644 index 0000000000..e6f2e2ed09 --- /dev/null +++ b/packages/site/docs/manual/FAQ/angular-support.en.md @@ -0,0 +1,20 @@ +--- +title: Error in Angular with G6 +order: 2 +--- + +**This doc is only for Angular 9 and above and the solution is only tested on Angular 9 and above.** + +There's no special settings or differences to use G6 in Angular if you just start a new Angular project from latest Angular CLI. Do it like you normal do when adding new dependency + +However, if your application is upgraded from older angular version (for example, my project is from angular 6), you may run into trouble. + +If you see `cannot read property 'webpackChunkAlgorithm'` error like following picture shows +![image](https://user-images.githubusercontent.com/12276316/110507994-8e108e00-80ce-11eb-9f40-653f2181e44b.png) + +Please refer to https://github.com/antvis/G6/issues/2691 for solution + +basically to make G6 work for angular: + +1. Create a .browserslistrc file under your application directory, same level as package.json. `please notice that browserslist won't working.` +2. opt-out the IE support. diff --git a/packages/site/docs/manual/FAQ/angular-support.zh.md b/packages/site/docs/manual/FAQ/angular-support.zh.md new file mode 100644 index 0000000000..a9d7dab191 --- /dev/null +++ b/packages/site/docs/manual/FAQ/angular-support.zh.md @@ -0,0 +1,20 @@ +--- +title: Angular + G6 报错 +order: 2 +--- + +**本文档仅对于 Angular 9 以上版本适用** + +如果你的项目是通过最新的 Angular CLI 创建,那么 G6 可以即开即用,不需要特殊设置。 + +如果你的项目是从老的 Angular 版本升级而来(比如我的是从 Angular 6 逐步升级),那么你大概会遇到问题。 + +如果你遇到类似下图的 `cannot read property 'webpackChunkAlgorithm'` 错误 +![image](https://user-images.githubusercontent.com/12276316/110507994-8e108e00-80ce-11eb-9f40-653f2181e44b.png) + +请参考 #2691(https://github.com/antvis/G6/issues/2691) 中的解决方案。 + +在 Angular 中使用 G6,需要满足以下两点: + +1. 项目根目录下(与 package.json 同目录)需要有 `.browserslistrc` 文件,请注意 `browserslist` 文件无效。 +2. 不开启IE支持。 diff --git a/packages/site/docs/manual/FAQ/build-error-rollup.en.md b/packages/site/docs/manual/FAQ/build-error-rollup.en.md new file mode 100644 index 0000000000..5bfead8c06 --- /dev/null +++ b/packages/site/docs/manual/FAQ/build-error-rollup.en.md @@ -0,0 +1,31 @@ +--- +title: G6 3.3.x 版本 Rollup 集成错误 +order: 4 +--- + +具体参考:[#1260](https://github.com/antvis/G6/issues/1260#issuecomment-596306823) + +## Problem + +You might meet the error while building your project with lastest version G6 & rollup: + +``` +error Error: 'groupBy' is not exported by node_modules/_lodash@4.17.15@lodash/lodash.js + at error (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:5400:30) + at Module.error (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:9820:16) + at handleMissingExport (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:9721:28) + at Module.traceVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:10159:24) + at ModuleScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:8766:39) + at FunctionScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:3065:38) + at ChildScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:3065:38) + at FunctionScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:3065:38) + at ChildScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:3065:38) + at BlockScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:3065:38) +``` + +remark:3.2.x version can build success。 + +## Solution + +1. add `babel-plugin-lodash` plugin,this plugin will auto optimize lodash references +2. set `@rollup/plugin-node-resolve` plugin browser property to true,fixed G's problem。 diff --git a/packages/site/docs/manual/FAQ/build-error-rollup.zh.md b/packages/site/docs/manual/FAQ/build-error-rollup.zh.md new file mode 100644 index 0000000000..9df04539bc --- /dev/null +++ b/packages/site/docs/manual/FAQ/build-error-rollup.zh.md @@ -0,0 +1,31 @@ +--- +title: G6 3.3.x 版本 Rollup 集成错误 +order: 4 +--- + +具体参考:[#1260](https://github.com/antvis/G6/issues/1260#issuecomment-596306823) + +## 问题 + +当用户使用 rollup 打包 G6 3.3.x 版本时,会报以下错误: + +``` +error Error: 'groupBy' is not exported by node_modules/_lodash@4.17.15@lodash/lodash.js + at error (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:5400:30) + at Module.error (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:9820:16) + at handleMissingExport (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:9721:28) + at Module.traceVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:10159:24) + at ModuleScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:8766:39) + at FunctionScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:3065:38) + at ChildScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:3065:38) + at FunctionScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:3065:38) + at ChildScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:3065:38) + at BlockScope.findVariable (/Users/gaoli/GitHub/GGEditor/node_modules/_rollup@1.31.1@rollup/dist/shared/node-entry.js:3065:38) +``` + +说明:3.2.x 版本不会出现这个问题。 + +## 解决方案 + +1. 添加 babel-plugin-lodash 插件,此插件会自动优化 lodash 的引用方式。 +2. 设置 @rollup/plugin-node-resolve 插件 browser 属性为 true,修复 G 当前的问题。 diff --git a/packages/site/docs/manual/FAQ/build-error.en.md b/packages/site/docs/manual/FAQ/build-error.en.md new file mode 100644 index 0000000000..ae787c5f82 --- /dev/null +++ b/packages/site/docs/manual/FAQ/build-error.en.md @@ -0,0 +1,16 @@ +--- +title: Building Error +order: 11 +--- + +## Problem + +You might meet the error while building your project with lastest version G6: + +img + +img + +## Solution + +Refer to #issues 960. diff --git a/packages/site/docs/manual/FAQ/build-error.zh.md b/packages/site/docs/manual/FAQ/build-error.zh.md new file mode 100644 index 0000000000..e94093009f --- /dev/null +++ b/packages/site/docs/manual/FAQ/build-error.zh.md @@ -0,0 +1,16 @@ +--- +title: Build 报错 +order: 11 +--- + +## 问题 + +使用最新版 G6,build 时候报如下错误: + +img + +img + +## 解决方案 + +参考 #issues 960。 diff --git a/packages/site/docs/manual/FAQ/faq.en.md b/packages/site/docs/manual/FAQ/faq.en.md new file mode 100644 index 0000000000..6b0fdb9b9a --- /dev/null +++ b/packages/site/docs/manual/FAQ/faq.en.md @@ -0,0 +1,77 @@ +--- +title: Frequently asked questions +order: 0 +--- + +- [Set the background of the label on node or edge](/en/docs/manual/middle/elements/advanced-style/set-label-bg) +- [Update Item's Style](/en/docs/manual/middle/elements/methods/updateElement) +- [Update Label](/en/docs/manual/middle/elements/advanced-style/updateText) +- [Gradient Color for Objects in G6](/en/docs/manual/middle/elements/advanced-style/gradient) +- [Fill with Texture in G6](/en/docs/manual/middle/elements/advanced-style/texture) +- [Render the Edge on the Top](/en/docs/manual/middle/elements/methods/elementIndex) +- [Multiple Edges between Two Nodes](/en/docs/manual/middle/elements/methods/multi-line) +- [G6 in React](/en/docs/manual/advanced/g6InReact) +- [How to auto zoom Tooltip、ContextMenu when zoom canvas](/en/docs/manual/middle/plugins/autoZoomTooltip) + + +### fitView Failed +> I configured `fitView: true` on the graph instance, but it is not taking effect + +The reasons and solutions for it: + +1. The value of `minZoom` is too big. If you have a graph with large range, e.g. positions of nodes range from 0 to 10000, the zoom ratio to propertly fit to the view requires a very small value of `minZoom`. So the zoom failed. The default value of `minZoom` is 0.02. To address it, you could assign a small value to `minZoom`, e.g. 0.0000001; + +2. If you are using `type: force` layout, or other force family layouts e,g, force2、forceAtlas2 etc. and assigned `animate: true` for them. The animation looks like force simulation is the result of rendering the mid-result after each iteration during the layout calculation. If we do `fitView` every single time the graph renders, the view might flashes a lot. So the `fitView` will be called after the layout is totaly done. And, when the force layout going to end, the displacements of the nodes might be micro. It looks like the animation is finished but the graph did not fit to view. It is also expected, since the `fitView` will only be called after the force layout is totaly fininshed. + +P.S. force does not support silence layout right now. But we add a new force family layout named `force2`, whichi supports config it by `animate`. If it is assigned to `false` and the graph instance is configured with `fitView: true`, the graph will not be rendered until the layout is done and fit to the view in the same time. + +3. The `width` or `height` for the graph instance is not correct. Oversize comparing to the container, initial state at React, or some other reasons might lead to the problem. If you want to keep the graph fitting the the container, listen to the changes of `width` and `height` of the container, and call `graph.changeSize` and `graph.fitView`. e.g. call `graph.changeSize` after the user resize the browser: + +```javascript +if (typeof window !== 'undefined') { + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; +} +``` + +### Rendering Residuals + +G6 4.x depends on the rendering engine @antv/g@4.x, which supports the local refresh. Local refresh definitely brings the performance improvements, but the residual problem in the same time. e.e. the label of the node leaves the residuals while dragging the node. Recently, @antv/g is in the process of upgrading to v5.x, it might not consider to fix the issue at v4.x. So when we encounter the problem, we could try the following steps to alleviate the problem: + +1. Check the attributes of the shapes in the node/edge/combo who leaves the residuals to avoid the invalid values, e.g. `null`, `NaN`, etc.; + +2. Use the round number for the number type attributes if it is possible, e.g. `r`, `width`, `height`, `fontSize`, etc.; + +3. Assign `labelCfg.style.fontFamily` or `fontFamily` in the text shape who leaves residuals with the font of the brower; + +4. Add white stroke to the text, e.g.: + +```javascript +// node/edge/combo config +labelCfg: { + style: { + stroke: '#fff', + lineWidth: 4 + } +} +// for the text shape in custom node/edge/combo +group.addShape('text', { + attrs: { + // ... other attributes, + stroke: '#fff', + lineWidth: 4, + } + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape' +}) +``` + +5. If the steps above did not solve your problem, disable local refresh by `graph.get('canvas').set('localRefresh', false)`, and this might slow down the rendering. + + +### Performance Improvement + +The performance problem in G6 has two aspect: rendering and calculation(e.g. layout). We are trying our best to improve the built-in code in G6 to make the proformance better. But sometimes, we suggest users to implement graph app with better way to alleviate the probmen. Refre to [Performance Tips for G6 Apps](/en/docs/manual/faq/performance-opt) to see the tips for your app! \ No newline at end of file diff --git a/packages/site/docs/manual/FAQ/faq.zh.md b/packages/site/docs/manual/FAQ/faq.zh.md new file mode 100644 index 0000000000..76a3c8c866 --- /dev/null +++ b/packages/site/docs/manual/FAQ/faq.zh.md @@ -0,0 +1,76 @@ +--- +title: 常见使用问题汇总 +order: 0 +--- + +- [如何设置节点或边的背景](/zh/docs/manual/middle/elements/advanced-style/set-label-bg) +- [如何更新节点或边的样式](/zh/docs/manual/middle/elements/methods/updateElement) +- [如何更新文本样式](/zh/docs/manual/middle/elements/advanced-style/updateText) +- [如何给元素设置渐变色](/zh/docs/manual/middle/elements/advanced-style/gradient) +- [如何给元素设置纹理](/zh/docs/manual/middle/elements/advanced-style/texture) +- [如何设置 Edge 前置](/zh/docs/manual/middle/elements/methods/elementIndex) +- [两点之间存在多条边](/zh/docs/manual/middle/elements/methods/multi-line) +- [React 中使用 G6](/zh/docs/manual/advanced/g6InReact) +- [缩放画布时如何让 Tooltip、ContextMenu 自动缩放](/zh/docs/manual/middle/plugins/autoZoomTooltip) + +### fitView 失败 +> 为什么明明在图实例上配置了 `fitView: true`,但却不生效? + +fitView 不生效的原因可能是: + +1. `minZoom` 值不够小。如果你的图范围很大,要缩放到小于 `minZoom` 的值才能完成适配,这种情况下缩放就会失败。图默认的 `minZoom` 是 0.02。解决方案是在实例化图的时候将 `minZoom` 设置一个很小的值; + +2. 如果使用的是 `type: force` 布局,布局过程是实时渲染计算结果的,所以会出现模拟力相互作用的动画效果。这种情况下如果每一次渲染都进行 fitView,那么图可能就忽大忽小的,因此若配置了 fitView 那么 G6 会在布局结束的时候,进行一次图的适配。即动画结束时进行大小适配。而 force 在迭代的尾声接近收敛,节点移动的幅度很小,有时候看起来好像动画已经结束了却还没有适配,需要等它完全稳定下来才意味着布局结束,从而进行大小适配; + +P.S. force 不支持无动画的布局,近期新增的 `force2` 支持配置 `animate` 来控制是否一边计算布局一边渲染,设置为 `false`,且图实例配置了 `fitView: true`,那么将布局完成后直接绘制出适配容器大小的图。 + +3. 给到 graph 的 `width` 或 `height` 在图实例化时不准确,导致创建的画布大小不对。这很有可能出现在 React 初始化时,图的容器还没有渲染。如果需要图始终适配容器大小,可以监听容器的 `width`、`height` 变化,进行 `graph.changeSize` 和必要的 `graph.fitView`。例如在用户拖拽浏览器改变大小时,使用 `graph.changeSize`: + +```javascript +if (typeof window !== 'undefined') { + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; +} +``` + +### 渲染残影问题 + +G6 4.x 依赖的渲染引擎 @antv/g@4.x 版本支持了局部渲染,带了性能提升的同时,也带来了图形更新时可能存在渲染残影的问题。比如拖拽节点时,节点的文本会留下轨迹。由于目前 @antv/g 正在进行大版本的升级(到 5.x),可能不考虑在 4.x 彻底修复这个问题。当我们遇到这个问题的时候,考虑通过下面几种方法解决: + +1. 检查节点中的图形,包括文本图形,样式配置中是否存在非法值,例如 `null`、`NaN` 等; + +2. 尽量使用整数作为数值型的样式值,例如 `r`、`width`、`height`、`fontSize` 等; + +3. 使用浏览器字体作为 `labelCfg.style.fontFamily` 或留下残影的文本图形的 `fontFamily` 属性; + +4. 给文本增加白色描边,如: + +```javascript +// 节点/边/ combo 的文本配置 +labelCfg: { + style: { + stroke: '#fff', + lineWidth: 4 + } +} +// 自定义节点/边/ combo 中的文字图形 +group.addShape('text', { + attrs: { + // ... other attributes, + stroke: '#fff', + lineWidth: 4, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'text-shape' +}) +``` + +5. 以上方法都不奏效,关闭局部渲染 `graph.get('canvas').set('localRefresh', false)`。这个方法可能导致性能有所降低。 + + +### 卡顿 / 性能差 + +G6 的性能主要存在两个瓶颈:渲染、计算(例如布局计算)。G6 内部的代码在持续迭代性能优化,尽可能保持内置代码性能最优。有时候,可能是大家在使用 G6 实现自己的应用时,由于不熟悉或 G6 文档不够全面等问题,使得功能设计、写法问题、或 API 使用不太合理导致的表现出卡顿现象。请看专文[《从卡掉渣到满帧率需要几步?》](/docs/manual/faq/performance-opt),介绍了一些 tips 来帮助你优化实现方式导致的性能问题。 \ No newline at end of file diff --git a/packages/site/docs/manual/FAQ/performance-opt.en.md b/packages/site/docs/manual/FAQ/performance-opt.en.md new file mode 100644 index 0000000000..a0f19eb255 --- /dev/null +++ b/packages/site/docs/manual/FAQ/performance-opt.en.md @@ -0,0 +1,217 @@ +--- +title: Performance Tips for G6 Apps +order: 13 +--- + +## Introduction + +Performance problem is significant on graph visualization apps since graph usually has complex and large data. There are some tips to alleviate the issue for G6 apps. G6 has two performance bottleneck: rendering and computation. If you are not interested about the theory, jump to the [Tips](#tips) chapter. + +### Performance Bottleneck - Rendering + +On the aspect of renderng, the proformance is mainly affected by the total number of shapes on the canvas. e.g. there is a rect, a text, and an image shape on a node, and a path, a text shape on a edge. Then, a graph with 100 nodes and 50 edges will have 100 * 3 + 50 * 2 = 400 shapes in total. However, the number of shapes on a custom node usually reaches 10-20, which means the total number on the canvas will be large. So we suggest to reduce the shapes on custom items to improve the rendering performance. + +### Performance Bottleneck - Computation + +Computation on a graph mainly includes layout calculation, polyline path finding calculation, etc. + + +## Tips + +We are trying to keep on improving the built-in codes in G6 to reach better performance. And on the aspect of apps based on G6, the implement ways are significant to the upper level performance. Inpropriate implementations might lead to unexpected costs. + +### A Proper Size for Graph + +The `width` and `height` should be assigned according to the container DOM in the browser. According to the resolutions of most diplays, the `width` is usually smaller than 2500 and the `height` is usually smaller than 2000. There was a issue about the performance, we find `width` and `height` in the reproducing demo are over 100,000, it leads to a very large ``, which is totaly unnecessary, since most part of the canvas will overflow the viewport. Actually, it is common to have nodes with large `x` and `y`, but we don't have to set the `width` and `height` to a large number, only use G6's ability to visualize and interact with the data, e.g. `graph.fitView` to fit to the viewport, `zoom-canvas` behavior to allow user zoom canvas, `drag-canvas` behavior to allow user drag canvas. + +### Canvas Instead of SVG + +Comparing to Canvas, some users might be more familiar with the DOM/SVG. And the shapes rendered by SVG could be inspected by the browser console. When you defined a shape by `group.addShape('dom', {...})` in custom node, the graph instance must be configured with `renderer: 'svg'`. **BUT, the performance of SVG is much worse than Canvas.** If you have medium size or large size data to visualize, we strongly suggest you to use Canvas instead of SVG. And Canvas is very flexible to defined all kinds of nodes, including those look like DOM card. For example, there are two card-like node which is defined and redered by canvas: + + + +- http://g6.antv.antgroup.com/en/examples/item/customNode/#card +- http://g6.antv.antgroup.com/en/examples/item/customNode/#cardNode + +For the inspectable ability of SVG, although the canvas does not support it, we could print the shapes and their attributes by the following ways to debug: + +```javascript +// For the whole graph +const graphGroup = graph.getGroup(); // graph's root graphics group +const graphGroups = graphGroup.get('children'); // there will be groups with id -node, -edge, -delegate in usual + +// For a node (similar to edge/combo) +const node = graph.findById('node1'); // find a node item on the graph +const nodeShapeGroup = node.getContainer(); // get the node's graphics group +const nodeShapes = nodeShapeGroup.get('children'); // get all the shapes in the node's graphics group +const keyShape = node.getKeyShape(); // get the key shape of the node, which is a child shape in nodeShapes +const labelShape = nodeShapeGroup.find(ele => ele.get('name') === 'label-shape'); // get the shape with name 'label-shape', which is also a child shape in nodeShapes. name is assigned when calling addShape +console.log(nodeShapes[0].attr(), keyShape.attr(), labelShape.attr()); // get and print the shape's style attributes +``` + +Besides, we suggest to limit the number of shapes in custom node/edge/combo, refer to the chapter [Cut down the Shapes on Custom Items](#cut-down-the-shapes-on-custom-items). + +### Cut down the Shapes on Custom Items + +The rendering performance depends on the number of shapes on the canvas to a great degree. Sometimes, although there are only 100 nodes, the complex shapes on custom node lead to large number of shapes on canvas. For example, there are 27 shapes in the following node, some of them are hidden by scrolled container: + + + +We suggest: +- Cut down the unnecessary shapes. e.g. if you want to add stroke, configure `lineWidth` and `stroke` for a shape instead of adding an extra background shape. +- Hide the invisible shapes by `visible: false` instead of `opacity: 0`. And control the visibility by `shape.show()` or `shape.hide()` in `update` or `draw` according to your requirement. e.g. + +```javascript +const circleShape = group.addShape('circle', { + attrs: {}, // if hide the shape by opacity: 0, the shape is rendered. so we suggest to use visible as below + name: 'custom-circle', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + visible: false, // hide the shape and it will not be rendered +}); +circleShape.show(); // show +circleShape.hide(); // hide +``` + +- Adjust the visibility of shapes according to the detail/zoom level of the graph. On small graphs, it is feasible to show every detail infomation of a node data on the displaying node, since the users are interested in the detail in those cases. But on large graphs with an small zoom ratio, user will be more interested about the overview structure of the data, and the detail when they zoom-in the graph. So we suggest to adjust the visibility of shapes according to detail/zoom level to reduce the clutter of infomation, and improve the performance in the same time. Try to zoom-in and zoom-out the graph in this Demo [Decision Tree](http://g6.antv.antgroup.com/en/examples/case/treeDemos/#decisionTree), you will see the shapes being hidden and shown graciously (9 shapes in detail view, 2 shapes in overview). + + + + + +### Implement the Update Function for Custom Items + +For convenience, fresh men usually only implement `draw` or `drawShape` in custom node/edge/combo. We also encourage that in small graphs, which will reduce the cost for developing and learning. But it aslo brings extra performance cost. There might be several situations when customize a node: + +1. Did not give the third parameter for `G6.registerNode` as extended type name, and did not implement `update` (or defining it with `update: undefined`): + +```javascript +G6.registerNode('custom-node', { + draw: (cfg, group) => {}, + update: undefined, // or do not implement update +}); // no third parameter +``` + +The custom node will not extend any existing node type, and nor have its own `update` function. The defined `draw` will be called at first rendering and every updating, e.g. `graph.updateItem`, `node.refresh`, etc. It leads to graphics group clearing, shape destroying, and shapes re-initiating. That is the costs. + +2. Gave the third parameter for `G6.registerNode`, but did not implement `update`: + +```javascript +G6.registerNode('custom-node', { + draw: (cfg, group) => {}, +}, 'circle'); // extend built-in circle type node +``` + +`custom-node` will extend the built-in `circle` type node, including its functions like `update`, `setState`, and so on. Sometimes, you may find the custom-node is not updated as expected, e.g. some strange shapes or styles occurs. It is due to the `draw` of `custom-node` and the `draw` of extended `circle` type are so different that `circle`'s `update` (which matches its own shapes defined in its `draw`) does not match `custom-node`'s shapes. To address the problem, a simplest way is to rewrite `update` to `undefined`. But it also brings the extra cost like the first situation. + +3. Gave the third parameter for `G6.registerNode`, and rewrote `update: undefined`: + +```javascript +G6.registerNode('custom-node', { + draw: (cfg, group) => {}, + update: undefined, // rewrite update +}, 'circle'); // extend built-in circle type node +``` + +As it is described in the second situation, since there is no `update` for custom-node, the defined `draw` will be called at first rendering and any updating, e.g. `graph.updateItem`, `node.refresh`, etc. It leads to group clearing, shape destroying, and shapes re-initiating. That is the costs. + +Therefore, we should utilize the life hooks of node with reasonable codes for better performance: +```javascript +G6.registerNode('custom-node', { + draw: (cfg, group) => { + group.addShape('circle', { + attrs: {...}, // styles, + name: 'xxx' // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }) + // ... + }, + update: (cfg, group, item) => { // different responses for different cfg changes + const someShape = group.find(ele => ele.get('name') === 'xxx'); // get the shapes should be updated by name + someShape.attr({ lineWidth: 2 }); // update the style + someShape.show(); // controls the visibility + }, +}, 'circle'); // extends built-in circle type node +``` + +It requires developers to have clear management for shapes. Similar to the hooks `componentDidMount`, `componentDidUpdate` of React, components should have different responses for different props changes. + +### Use Polyline Properly + +Different from other edge types, polyline calculates its path by A* path finding algorithm when its `controlPoints` is not defined. A* is an algorithm with high complexity. The performance issue will be extremely significant when dragging nodes, since the algorithm will be re-calculated frequently during dragging. There are some tips to alleviate the problem: + +- Custom simple polyline instead using the built-in polyline. There is a demo [Custom Polyline](http://g6.antv.antgroup.com/en/examples/item/customEdge/#customPolyline). In most cases, the bending positions are at the 1/3 and 2/3 of the line between source node and target node (the begining position from source node is `startPoint` and the ending position from the target node is `endPoint` in the following example): + +```javascript +[ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y], + ['L', endPoint.x, endPoint.y], +] +``` + +- If you are using `dagre` layout, enable its `controlPoints` to calculate the bending positions for polyline by `dagre`, which means the `controlPoints` will be given by the layout and A* will not be called anymore: +```javascript +const graph = new Graph({ + // ... other configurations + layout: { + type: 'dagre', + controlPoints: true, // Calculate the bending positions as controlPoints for edges. But will not change your edge type. Assign edge type as polyline in defaultEdge or edge data to make it take effect. + // ... other configurations + }, + defaultEdge: { + type: 'polyline' + } +}) +``` + +### Enable Optimize Configurations for Behaviors + +Local refresh happens on updating node/edge/combo's configurations, states, and so on, which means renderer only clears the dirty bounding box and redraws the updated shapes. Renderer will clear the whole canvas and redraw at global updates like panning canvas and zooming canvas. That is to say, global updates cost much more than local udpates. For example, when user drags or zooms the canvas, the clearing and redrawing are frequently repeated. So the user may find it is not so smooth in large graph with such behaviors. G6 supports `enableOptimize` option for built-in behaviors `zoom-canvas` and `drag-canvas`, which is `false` by default. Assign it to `true`, all the shapes besides keyShapes will be hidden during panning and zooming. ([keyShape](http://g6.antv.antgroup.com/en/manual/middle/elements/shape/shape-keyshape) is the shape returned by `draw` function of `G6.registerNode`, `G6.registerEdge`, and `G6.registerCombo`). After panning and zooming, the hidden shapes will be shown again. It will enhance the performance of these global updates a lot. + +Configure `enableOptimize` to `true`: + +```javascript +const graoh = new Graph({ + // ...other configurations + modes: { + default: [{ + type: 'drag-canvas', + enableOptimize: true, + // ... other configurations + }, { + type: 'zoom-canvas', + enableOptimize: true, + // ... other configurations + }] + } +}) +``` + +### Select a Proper Layout + +G6 provides lots of layout methods. Layouts of force family are chosen by most developers. G6 has several force family layouts, and their performances are different. We suggest developers to try `force2` which is provided recently. + +- force2: new force layout in G6 with better performance, and more configurations for gravity, center forces, clustering forces. And the animation is also configurable (by `animate`); +- force: d3's classic force layout, does not support silence calculation currently; +- forceAtlas2: a force layout with different force model, whose result is more compact. Implements the paper [ForceAtlas2, a Continuous Graph Layout Algorithm forHandy Network Visualization Designed for the GephiSoftware](https://www.researchgate.net/publication/262977655_ForceAtlas2_a_Continuous_Graph_Layout_Algorithm_for_Handy_Network_Visualization_Designed_for_the_Gephi_Software); +- fruchterman: another force model, whose result looks like regular hexagon in a way. The performance of it is not so good. Implements the paper [Fruchterman–Reingold Hexagon Empowered NodeDeployment in Wireless Sensor Network Application](https://www.researchgate.net/publication/361955786_Fruchterman-Reingold_Hexagon_Empowered_Node_Deployment_in_Wireless_Sensor_Network_Application?_sg=HVkgQIDKgLcvASw6B488WEdzJxN2m_X1T2MZ4zoa12KVnE-w8f6v_CawQ98tU2Bh7DN5qlRbmWNUDEA)。 + +Besides `force`, other force family layouts have option `animate` to enable the animation during layout calculation. Actually, the 'animation' means rendering the mid-result after each iteration of force calculation. Nodes look like particles pushes/pulled by real forces. And other layouts will be rendered one time after layout finished (or force layout with `animate: false`). Configure `animate: true` on graph instance enables the interpolation animation for those static layouts after calculation finished. + +On samll graphs, force layouts always output good result whatever `animate` is `true` or `false`. On large graphs, if `animate` is disabled, the layout might cost some time for calculation and the user will not see the graph until the layout is done, which leads to bad user experience. Enabling `ainmate` shows the graph at the begining and user could wait with animation, which will be more acceptable by users. Sometimes, the nodes might swing nearing the end of calculation. Developers could stop the layout in the listeners of canvas clicking or node clicking. + +### Data Increment APIs + +- Update several items, we suggest `graph.updateItem` respectively; +- Add several items, we suggest `graph.addItem`. And v4.6.6 supports `graph.addItems` for batch adding; +- Remove several items, we suggest `graph.removeItem`; +- Most part of the data need to be changed, we suggest `graph.changeData`, which will diff the current data and new data, and merge the new one into old one according to the id; + +### Minimap with Proper Configuration + +Minimap is a plugin of G6, which has three types: `'default'`, `'keyShape'`, and `'delegate'`. With `'default'` type, all the graphics shapes and groups will be completely cloned to the canvas ad Minimap. And the minimap canvas will be updated when the items on the main graph being updated. That is to say, twice cost occurs for a graph with a `'default'` type minimap. With `'keyShape'` type, minimap only shows the key shapes of the main graph. With `'delegate'` type, minimap shows delegate shapes (configured by `delegateStyle`) to represent the items of the main graph. These simplification with `'keyShape'` and `'delegate'` types enhance the performance a lot. Therefore we suggest to use these two types instead of `'default'` type on large graphs. + +Besides, the size of minimap is much smaller than main graph in usual. When there is lots of items, the edges will be extremely thin and not easy to recognized by users on the small view anyway. Therefore, v4.7.16 supports `hideEdge` option (`false` by default) for minimap. Assign it to `true` to hide the edges on the minimap to further enhance the performance. + +### Use Animation Properly + +Animation costs a lot in usual. We suggest developers to use animation reasonably on local responses instead of globaly, e.g. breath animation when hover a node, flow animation on selected edges. And developers should well manage the animations and stop them in time. \ No newline at end of file diff --git a/packages/site/docs/manual/FAQ/performance-opt.zh.md b/packages/site/docs/manual/FAQ/performance-opt.zh.md new file mode 100644 index 0000000000..2e6b7c0923 --- /dev/null +++ b/packages/site/docs/manual/FAQ/performance-opt.zh.md @@ -0,0 +1,206 @@ +--- +title: 从卡掉渣到满帧需要几步 +order: 13 +--- + +## 简介 + +在面对复杂数据的图可视分析,你的 G6 应用是否出现了卡顿、掉帧、不流畅现象?跟着本文的 tips 排查和优化,提升你的图可视化应用的性能。G6 的性能瓶颈主要在两个方面:渲染、计算。本小节介绍性能瓶颈的一些原理,对理论不感兴趣只想直接优化代码的小伙伴可以直接跳到[解决方案](#解决方案)章节 + +### 性能瓶颈 — 渲染 + +在渲染方面,性能主要取决于当前画布上形状元素的个数,e.g. 一个节点上有矩形、文本、图片三个图形,一条边上有路径、文本两个图形,那么一份 100 个节点、50 条边的图数据,将渲染出 100 * 3 + 50 * 2 = 400 个图形。然而,开发者常常自定义非常复杂的节点,一个节点上可能有 10~20 个图形,那么画布上的图形数量将陡增。因此,尽可能地减少不必要的图形绘制,是提升渲染性能的主要手段。 + +### 性能瓶颈 — 计算 + +计算方面,主要包括节点布局计算、折线自动寻径算法等。 + + +## 解决方案 + +G6 内部代码,我们在持续迭代其性能。而基于 G6 的图应用,则需要应用的开发者关注实现方式,不合理的实现方式很可能导致性能的额外开销。 + +### 定义合理的画布大小 + +一般来说,我们应当根据浏览器中容器的大小设置图画布的大小,即在图实例上配置的 `width` 与 `height`。目前主流显示器的分辨率来看,浏览器中放置图的容器长一般都不会超过 2500,高一般不会超过 2000。之前曾经遇到过开发者将图的 `width` 和 `height` 设置到几万,这造成了 `` 标签的宽高非常大。这完全没有必要,因为大部分超出了浏览器视口。实际上,我们绘制的节点即使坐标达到了上万,我们仍然可以通过 `drag-canvas`、`zoom-canvas` 等交互滚动查看,没有必要设置巨大的图宽高。 + +### 尽可能选择 Canvas 渲染 + +相比于 Canvas,可能部分开发者更熟悉 DOM/SVG 的定义,毕竟 SVG 渲染出来之后可以审查元素,更符合我们的日常调试习惯。比如当你在自定义节点中使用 `group.addShape('dom', {...})` 这种 'dom' 图形时,就必须要使用 SVG 渲染,即在图实例上配置 `renderer: 'svg'`。**但 SVG 的性能比 Canvas 差得多。** 在数据较大、节点比较复杂的情况下,我们强烈推荐你使用 Canvas 进行渲染。Canvas 定义图形的方式也非常灵活,完全可以覆盖 SVG 的能力,或任何看起来像 DOM 定义的卡片样式的节点。比如下面这两个例子,都是使用 Canvas 渲染和定义。 + + +- http://g6.antv.antgroup.com/examples/item/customNode/#card +- http://g6.antv.antgroup.com/examples/item/customNode/#cardNode + +回到 SVG 容易审查这个优势,虽然 Canvas 上没有办法审查每一个图形,但我们可以通过下面方式打印图形的属性,进行调试: + +```javascript +// 整个图 +const graphGroup = graph.getGroup(); // 整个图的根图形分组 +const graphGroups = graphGroup.get('children'); // 一般会有 -node -edge -delegate 几个分组 + +// 单个节点(单个边/ combo 也类似) +const node = graph.findById('node1'); // 找到某个节点对象 +const nodeShapeGroup = node.getContainer(); // 获取该节点的图形分组 +const nodeShapes = nodeShapeGroup.get('children'); // 获取改节点中的所有图形 +const keyShape = node.getKeyShape(); // 获取该节点的关键图形,keyShape 在 nodeShapes 中 +const labelShape = nodeShapeGroup.find(ele => ele.get('name') === 'label-shape'); // 获取 name 为 'label-shape' 的图形(name 在 addShape 时指定)。labelShape 在 nodeShapes 中 +console.log(nodeShapes[0].attr(), keyShape.attr(), labelShape.attr()); // 获取并打印图形的属性 +``` + +除了使用 Canvas 渲染,在定义如此复杂的节点时,同时建议尽可能控制图形的数量,见下文 [减少自定义元素的图形数量](#减少自定义元素的图形数量) 一节。 + +### 减少自定义元素的图形数量 + +图的渲染性能很大程度取决于画布上图形的数量。有时虽然数据层面只有 100 个节点,但由于自定义节点非常复杂,每个节点达到数十个图形,再加上复杂的自定义边,可能图上图形也能够达到上万。比如下面这个节点上有二十七个图形(因为节点带滚动,部分文字、锚点被隐藏): + + +- 减少不必要的图形。例如,给矩形增加边框,不需要新增图形,只需要给矩形设置描边粗细 `lineWidth` 和描边色 `stroke` 即可。 +- 默认看不见的图形,设置 `visible: false`(而不是 `opacity: 0`)进行隐藏。在自定义节点的 `update` 方法或 `draw` 方法中,根据情况再通过 `shape.show()` 将其显示出来或 `shape.hide()` 再次隐藏,e.g. +```javascript +const circleShape = group.addShape('circle', { + attrs: {}, // 在 attrs 中设置 opacity: 0 也能达到看不见的目的,但实际上还是渲染了,更推荐使用 visible 控制 + name: 'custom-circle', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + visible: false, // 默认隐藏。注意 visible 字段的位置。visible 为 false 时,图形不会被渲染 +}); +circleShape.show(); // 显示 +circleShape.hide(); // 隐藏 +``` +- 根据缩放等级,调整显示的图形。在小规模的图上,每个节点都有详细信息性能问题不大,且用户也许需要在每个节点上看到如此详细的信息。但在较大规模的图上,概览时用户更关心的是图的关系结构,此时我们应当考虑,根据情况调整自定义节点上图形的数量,隐藏不必要的信息。这样做一方面可以减小渲染的压力,另一方面可以让用户更高效地获得更清晰的信息。在官网案例[决策树](http://g6.antv.antgroup.com/examples/case/treeDemos/#decisionTree)中,进行画布的缩放,可以看到详情(左)和缩略节点(右)的优雅切换。每个节点上图形显示的图形数量从 9 个(详细)降低到 2 个(缩略)。 + + + + + +### 为自定义元素实现 update 方法 + +初学者为了方便自定义节点/边/ combo,往往只定义 `draw` 或 `drawShape` 方法,我们也鼓励在小规模图上这样做,可以减少一些开发成本和学习成本。但这将带来额外的性能开销。以自定义节点为例,可能有以下几种情况: + +1. `G6.registerNode` 第三个参数没有传入被继承的节点类型名,且没有定义 `update` 方法(或 `update: undefined`),如下: + +```javascript +G6.registerNode('custom-node', { + draw: (cfg, group) => {}, + update: undefined, // 或没有实现 update +}); // 没有第三个参数 +``` + +那么,这个自定义节点将不继承任何内置节点,也没有自己的 `update` 方法。包括初次绘制,所有的更新,例如通过外部或外部调用的 `graph.updateItem`、`node.refresh` 等方法,都将擦除该节点上的所有图形,并重新走一遍 `draw` 方法。这也意味着这个节点上的所有图形将被销毁和重新实例化。这就带来了大量消耗。 + +2. `G6.registerNode` 第三个参数指定了被继承的节点类型名,没有复写 `update`,如下: + +```javascript +G6.registerNode('custom-node', { + draw: (cfg, group) => {}, +}, 'circle'); // 继承内置 circle 类型节点 +``` + +此时,`custom-node` 将继承内置 `circle` 类型节点的 `update`、`setState` 等方法。有时,可能发现节点更新时,似乎有不符合预期的图形出现,这是由于 `custom-node` 的 `draw` 方法和 `circle` 类型节点的 `draw` 方法差异太大,以至于 `circle` 类型节点按照自己在 `draw` 方法中定义的图形进行更新,与 `custom-node` 逻辑不匹配。解决这一问题最简单的方法就是将 `update` 复写为 `undefined`。此时,就带来了和第一种情况类似的、不实现 `update` 方法的性能开销。 + +3. `G6.registerNode` 第三个参数指定了被继承的节点类型名,复写 `update: undefined`,如下: + +```javascript +G6.registerNode('custom-node', { + draw: (cfg, group) => {}, + update: undefined, // 被复写 +}, 'circle'); // 继承内置 circle 类型节点 +``` + +上面第二种情况所述的,更新时出现不符合预期的图形或样式问题在复写 `update: undefined` 后应当不复存在。但带来了不实现 `update` 方法的性能开销。即所有的更新,例如通过外部或外部调用的 `graph.updateItem`、`node.refresh` 等方法,都将擦除该节点上的所有图形,并重新走一遍 `draw` 方法。这也意味着这个节点上的所有图形将被销毁和重新实例化。 + +因此,为了更好的渲染性能,最合理的实现是充分利用节点的生命周期,在不同生命周期给出不同的增量逻辑。如下: +```javascript +G6.registerNode('custom-node', { + draw: (cfg, group) => { + group.addShape('circle', { + attrs: {...}, // styles, + name: 'xxx' // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }) + // ... + }, + update: (cfg, group, item) => { // 根据 cfg,产生增量的响应 + const someShape = group.find(ele => ele.get('name') === 'xxx'); // 拿到需要更新的图形 + someShape.attr({ lineWidth: 2 }); // 修改图形样式 + someShape.show(); // 控制显示和隐藏 + }, +}, 'circle'); // 继承内置 circle 类型节点 +``` +当然,这要求开发者能够对节点上图形的更新有足够清晰管理逻辑。就像 React 的 `componentDidMount`、`componentDidUpdate` 等生命周期函数一样,不同的 props 变更做出不同的响应。相信只要理解了这一原理,你也能轻松做到。 + +### 合理使用折线边 polyline + +与其他类型的边不同,折线类型边(polyline)在未指定 `controlPoints`(拐折点)时,将使用 A* 自动寻径算法,根据起点和终点的位置,自动计算折线弯折的位置。这一计算的复杂度较高,特别是在拖拽节点的过程中,相关的边需要实时根据最新的端点位置,频繁地计算 A* 算法。因此,当图上的 polyline 边比较多时,可能出现卡顿现象。根据你的实际情况,可以选择如下方法进行避免: + +- 参考官网案例[自定义折线](http://g6.antv.antgroup.com/zh/examples/item/customEdge/#customPolyline)。大部分情况下,折线两个弯折位置分别在两端点(下面例子的`startPoint`、`endPoint`)连线的 1/3 和 2/3 处,其实我们可以轻易计算出简单的折线路径。 +```javascript +[ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y], + ['L', endPoint.x, endPoint.y], +] +``` + +- 若你使用的布局算法是 `dagre`,那么可以打开它的 `controlPoints` 配置。`dagre` 算法将为 polyline 边计算控制点,有了控制点,polyline 将不再使用 A* 自动寻径算法,配置方法如下: +```javascript +const graph = new Graph({ + // ... 其他配置 + layout: { + type: 'dagre', + controlPoints: true, + // ... 其他配置 + } +}) +``` + +### 打开交互的优化配置项 + +设置节点/边的状态样式、拖拽节点等,基本都是局部更新,即渲染器只会擦除更新前的“脏矩形”,绘制上更新后的图形。但平移画布、缩放画布,在 Canvas 层面上,实际上是整个画布的擦除和重绘,并且在平移/缩放的过程中,这一重绘是极其频繁地被执行的。因此在较大规模的图上,用户可能会明显感觉到平移、缩放画布时非常卡顿。内置的缩放画布 `zoom-canvas` 和拖拽平移画布 `drag-canvas` 交互支持配置项 `enableOptimize`,设置为 `true` 时,在拖拽/缩放过程中,非关键图形(即 `G6.registerNode`、`G6.registerEdge`、`G6.registerCombo` 的 `draw` 返回的图形)将会被隐藏。拖拽/缩放结束后,哪些临时被隐藏的图形将恢复显示。这样能够大大提升拖拽/缩放过程中的帧率。 + +默认情况下 `enableOptimize` 是 `false`,可以通过下面方式配置: +```javascript +const graoh = new Graph({ + // ...其他配置项 + modes: { + default: [{ + type: 'drag-canvas', + enableOptimize: true, + // ... 其他配置 + }, { + type: 'zoom-canvas', + enableOptimize: true, + // ... 其他配置 + }] + } +}) +``` + +### 选择合适的布局算法 + +G6 提供了多种布局算法,其中力导向布局还是受到大多数开发者的青睐。G6 的以下几种布局均是力导家族成员,但性能却有差异,我们更推荐使用近期新增的 `force2` 算法: + +- force2:新增的力导算法,性能优秀,在经典力导向模型基础上,增加了更多关于向心力、聚类力的配置,可配置带动画或不带动画的布局 (`animate`); +- force:引用自 d3 的力导向算法,暂不支持不带动画的布局; +- forceAtlas2:区别于经典的力导向模型,效果更紧凑,性能一般,实现自论文 [ForceAtlas2, a Continuous Graph Layout Algorithm forHandy Network Visualization Designed for the GephiSoftware](https://www.researchgate.net/publication/262977655_ForceAtlas2_a_Continuous_Graph_Layout_Algorithm_for_Handy_Network_Visualization_Designed_for_the_Gephi_Software); +- fruchterman:另一种力导向模型,倾向于六边形的分布,性能较差,实现自论文 [Fruchterman–Reingold Hexagon Empowered NodeDeployment in Wireless Sensor Network Application](https://www.researchgate.net/publication/361955786_Fruchterman-Reingold_Hexagon_Empowered_Node_Deployment_in_Wireless_Sensor_Network_Application?_sg=HVkgQIDKgLcvASw6B488WEdzJxN2m_X1T2MZ4zoa12KVnE-w8f6v_CawQ98tU2Bh7DN5qlRbmWNUDEA)。 + +只有力导向家族的布局可以通过布局的 `animate` 配置控制是否在计算过程中不断渲染,从而展现出类似“粒子碰撞”、“力相互作用”的动画效果。其他布局只能在完全计算完成之后进行绘制,在图实例上配置 `animate: true` 可以为这一类静态的布局,或力导向家族配置 `animate: false` 的情况下,布局完成之后进行节点位置的插值动画,移动到对应的位置上。 + +在数据量较小时,力导向家族无论是否开启布局的 `animate`,效果均不错。在较大数据集上,若关闭 `animate`,则可能需要较长时间等待布局完成后,画布才会更新。若打开 `animate`,在动画中等待布局的完成,一般来说更容易为终端用户所接受。当然,也有可能出现布局的尾声,节点有“震荡”情况。建议在监听节点或画布的点击事件,在用户点击时,停止布局。 + +### 数据增量 V.S. changeData + +- 若干个元素的更新,更推荐使用 `graph.updateItem` 分别更新; +- 新增若干个元素,更推荐使用 `graph.addItem`。v4.6.6 起支持了 `graph.addItems` 批量新增元素; +- 移除若干个元素,更推荐使用 `graph.removeItem`; +- 大部分的数据变更,使用 `graph.changeData`。该方法将做当前图数据和 changeData 传入的新数据的 diff,若发现 id 相同的元素,将进行新旧数据的融合。 + +### 优化 Minimap 配置 + +Minimap 是 G6 的小地图插件,用作图的导览。它有三种类型:`'default'`、`'keyShape'`、`'delegate'`。`'default'` 模式下,主图上的所有内容将被完全拷贝一份到 Minimap 的画布上,在主图元素发生更新的时候,Minimap 的响应内容也需要重新拷贝,相当于两份图的开销,因此这种类型的 Minimap 性能最差。`'keyShape'` 模式下,Minimap 仅显示节点和边的主要图形、`'delegate'` 模式下,Minimap 仅显示代理图形(可通过 `delegateStyle` 配置),这两种模式的 Minimap 画布内容较为简化,因此有更好的性能。在较大规模的图上,我们更推荐后面两种模式的 Minimap。 + +此外,考虑到 Minimap 一般比较小,元素比较多时,边比较细,在 Minimap 上也看不清。 v4.7.16 起,Minimap 支持了 `hideEdge` 配置(默认为 `false`),可设置为 `true` 以隐藏 Minimap 上的边,从而更大程度地提升 Minimap 的性能。 + +### 合理地使用动画 + +动画的性能开销一般比较大,更建议动画使用在局部的状态响应时。例如 hover 节点时的呼吸效果、相关上下游的边流动效果等。并及时地停止动画。 \ No newline at end of file diff --git a/packages/site/docs/manual/FAQ/supportIE.en.md b/packages/site/docs/manual/FAQ/supportIE.en.md new file mode 100644 index 0000000000..9c98ce7b15 --- /dev/null +++ b/packages/site/docs/manual/FAQ/supportIE.en.md @@ -0,0 +1,66 @@ +--- +title: Using G6 with IE +order: 12 +--- + +In this section, we will introduce the usage of G6 in IE. + +## Solution + +Import `babel-polyfill` into your project: + +- Import `babel-polyfill` in your main entrance file; +- Add some code into `bable-loader`: + +``` +{ + test: /\.js$/, + loader: 'babel-loader', + include: [resolve('src'), resolve('node_modules/@antv/g6')] +} +``` + +> `include` indicates the directories of the .js files should to be babel-loader; exclude represents the directories of .js files should not to be babel-loader。 + +The content of `include` should be assigned according to your project. + +Refer to The Link for more detail. + +In addition, there are some solutions for the projects with @vue/cli, umi, and create-react-app. **First, ensure your project can be ran on IE without G6**. + +You may find the error: img + +### vue/cli + +The G6 Vue Demo is based on @vue/cli(V: 4.0.5). There will be some small differences to the 3.x version. Now, we are going to solve the compatibility issues of @vue/cli.
First, we find the document on Vue Official Website, which points out the problem of browser compatibility: img + +New a vue.config.js file in the same directory of package.json, and add `transpileDependencies`: img + +The value of `transpileDependencies` is `[]` by default, which means no Babel with all the node_modules files. Now, we add the files should be Bable into `transpileDependencies` as below. Note that the dependencies to be added **should not contain node_modules, and use the package name @antv/g6**. The reason is that the @vue/cli will add the prefix node_modules automatically. The @antv/g6 must be same consistent to that in package.json. Use npm while installing the dependencies. If you are using yarn or cnpm, you should make sure that there are no modified package name in node_modules. + +```javascript +module.exports = { + transpileDependencies: ['@antv/g6'], +}; +``` + +Open the project with IE11 to see the result: img + +The original error is solved, but new problem shows up. Open the project with Chrome, you can find the same error. The compatibility issue has been solved by adding `transpileDependencies`. We find a solution in the issues of Vue: add `sourceType: "unambiguous"` to babel.config.js, which can be refered to the official website of Vue for the definition. + +```javascript +module.exports = { + sourceType: 'unambiguous', + presets: ['@vue/cli-plugin-babel/preset'], +}; +``` + +Compile it again: img + +Now, the prolem is solved. + +### create-react-app + +If you are using create-react-app(V: 3.0.0) to initiate your project, create-react-app has built in the solution for compatibility. You only need to configure the compatibility of the project by several methods. Please refer to HERE.
img + +If you want to figure out the inner solving process, run `npm run eject` or `yarn run eject` to check the inner configurations of create-react-app. This operation is irreversible. img diff --git a/packages/site/docs/manual/FAQ/supportIE.zh.md b/packages/site/docs/manual/FAQ/supportIE.zh.md new file mode 100644 index 0000000000..7639fb5164 --- /dev/null +++ b/packages/site/docs/manual/FAQ/supportIE.zh.md @@ -0,0 +1,80 @@ +--- +title: 如何让 IE 支持 G6 +order: 12 +--- + +本文将介绍如何在 IE 中使用 G6。 + +## 解决方案 + +对于这类问题,我们在项目中只需要引入 `babel-polyfill` 即可,具体使用方法如下: + +- 在主入门文件中引入 `babel-polyfill` ; +- 在 `bable-loader` 中加入如下代码: + +``` +{ + test: /\.js$/, + loader: 'babel-loader', + include: [resolve('src'), resolve('node_modules/@antv/g6')] +} +``` + +> `include` 表示哪些目录中的 .js 文件需要进行 babel-loader;exclude 表示哪些目录中的 .js 文件不要进行 babel-loader。 + +`include` 中的内容请根据具体项目情况设置。 + +更详细的请参考:https://blog.csdn.net/y491887095/article/details/81541502。 + +另外,针对 @vue/cli、umi、create-react-app 搭建的项目给出一些解决方案,**务必确保在没有引入 G6 时你的项目可以正常运行在 IE 上**。 + +类似如下错误。 img + +### vue/cli + +本 G6 Vue 案例 是基于 @vue/cli(V: 4.0.5),3.x 版本可能在写法上有些出入。@vue/cli 怎么解决依赖兼容性问题呢?
遇到问题首先查看 Vue 官网 上有没有类似的教程,从官网上我们定位到浏览器兼容性,如下 img + +从文章中我们貌似已经找到了问题的答案,我们需要新建 vue.config.js 文件(和 package.json 同一目录),在里面添加 `transpileDependencies` 选项: + +img + +该选项默认值是 `[]`,说明会忽略掉所有掉 node_modules 文件,不会对 node_modules 里面对文件做 Babel。我们需要做的就是把我们希望 Babel 的文件加入即可,代码如下。需要注意的是我们加入的依赖**不需要包含 node_modules ,用包名 @antv/g6**即可,因为 @vue/cli 会自动添加前缀 node_modules 。还需要注意  @antv/g6 必须和 package.json 里面的一致。安装依赖的时候首选 npm ,如果你用 yarn、cnpm 等安装,需要确保 node_modules 里面的包名没有被更改。 + +```javascript +module.exports = { + transpileDependencies: ['@antv/g6'], +}; +``` + +好了,我们打开 IE11 看看结果吧。 img + +最开始的那个报错解决了,但出现了新问题。先用 Chrome 浏览器看看,发现问题是一样的。添加完 `transpileDependencies` 兼容性问题是没有了,但出了个新错误。再次查看官网,并没有相关文章,那就直接移步 issue 吧。经过一番查找,我们找到如下解决方案,在 babel.config.js 里面添加上 `sourceType: "unambiguous"` ,具体含义可以官网查阅。 + +```javascript +module.exports = { + sourceType: 'unambiguous', + presets: ['@vue/cli-plugin-babel/preset'], +}; +``` + +再次编译: + +img + +到此,完美解决问题。 + +### create-react-app + +如果你使用 create-react-app(V: 3.0.0) 初始化项目,那么恭喜你,create-react-app 已经内置了依赖兼容性的处理方案,你只需要配置项目自身的兼容性问题即可。配置有多种方式,可参考 这里
img + +也许你想看看内部是怎么处理的,可以执行 `npm run eject` 或 `yarn run eject` 以暴露 create-react-app 的内置配置。这个操作是不可逆的。内置配置如下: img + +### umi + +umi 不仅内置了依赖兼容性方案,而且配置简单,如果有任何问题,你可以在答疑群里面 @云谦 老师。 + +```javascript +export default { + browserslist: ['> 1%', 'last 2 versions'], +}; +``` diff --git a/packages/site/docs/manual/FAQ/upgradeGuide.en.md b/packages/site/docs/manual/FAQ/upgradeGuide.en.md new file mode 100644 index 0000000000..76ca1b9f06 --- /dev/null +++ b/packages/site/docs/manual/FAQ/upgradeGuide.en.md @@ -0,0 +1,361 @@ +--- +title: G6 3.3 Upgrade Guide +order: 1 +--- + +## The Built Outcomes of esm and commonjs + +The built outcomes of esm and commonjs do not support layouts with Web-Worker. + +## The Built Outcomes + +G6 3.3.0 supports three build outcomes: + +- lib: commonjs; +- es: esm; +- dist: umd. + +`import G6 from '@antv/g6'` default use lib outcomes。 + +The built outcomes of esm and commonjs do not support layouts with Web-Worker. + +if you want to support layouts with Web-Worker, please use the file of [CDN](https://gw.alipayobjects.com/os/lib/antv/g6/4.3.11/dist/g6.min.js). + +## Util + +- You don't need export @antv/util again after importing @antv/util in G6. The methods in @antv/util can be used directly; +- util/layout is removed: + - The methods `scaleMatrix`, `floydWarshall`, and `getAdjMatrix` in util/layout have been moved to util.math; + - The method `getEDistance` in util/layout was the same as the method `distance` in util/math. Please use `distance` in util/math; + - Use the methods `mix`, `augment`, and `isString` in @antv/util instead of these in util/layout; +- `groupData` is changed into `group`; +- The methods `flatToTree` and `addNodesToParentNode` are removed from util/group; +- The dependency to @antv/util in base is removed. + +## The Usage of Plugins + +You don't need to import other packages when you are using the built-in plugins of G6. Only use them by `G6.PluginName` after import G6. e.g. + +```javascript +// <= G6 3.2 +// Import by CDN. you need import G6 and the plugins you need. + + + +// Or import by NPM. you need import G6 and the plugins you need. +import G6, { Minimap, Grid } from '@antv/G6' + +const minimap = new Minimap({ + //... configurations +}) +const grid = new Grid({ + //... configurations +}) + + +// G6 4.x +// Import by CDN. Yuo only need to import G6. + + +// address of G6 3.x +// + +// Or import by NPM. Yuo only need to import G6. +import G6 from '@antv/G6' + +const minimap = new G6.Minimap({ + //... configurations +}) + +const grid = new G6.Grid({ + //... configurations +}) + +const graph = new G6.Graph({ + //... other configurations + plugins: [ minimap, grid ] +}); +``` + +## Tree-Graph + +In G6 3.3, The methods about layout in TreeGraph are unified as Graph: + +1. `changeLayout` is changed into `updateLayout`; +1. `refreshLayout` is changed into `layout`. + +## Animation + +In G6 3.2.x and its previous version, the usage of the shape animation is: + +```javascript +G6.registerEdge( + 'loop-growth', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = shape.getTotalLength(); + shape.animate( + { + onFrame(ratio) { + const startLen = ratio * length; + // Calculating the lineDash for the line + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + repeat: true, + }, + 2000, + ); + }, + }, + 'loop', +); +``` + +In G6 3.3: + +- We suggest not to use the calling way in 3.2 with the overrode `onFrame` function, which will be discarded soon; +- Call `animate` by two ways instead: + - Way 1: `animate(toAttrs, animateCfg)`, where `toAttrs` is the target attributes of this animation, and `animateCfg` is the configuration of the animation. e.g. + +```javascript +G6.registerEdge( + 'widen-line', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = shape.getTotalLength(); + shape.animate( + { + lineWidth: 10, + }, + { + repeat: false, + duration: 500, + }, + ); + }, + }, + 'single-edge', +); +``` + +- Way 2: `animate(onFrame, animateCfg)`, where `onFrame` is the callback function of each frame, and `animateCfg` is the configurations of the animation. e.g. + +```javascript +G6.registerEdge( + 'loop-growth', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = shape.getTotalLength(); + shape.animate( + (ratio) => { + const startLen = ratio * length; + // Calculating the lineDash for the line + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + { + repeat: true, + duration: 2000, + }, + ); + }, + }, + 'loop', +); +``` + +## The Type of the Node/Edge + +In G6 3.2.x and its previous versions, you can assign the type for edges and nodes in data, or assign them globally when instantiating the Graph, e.g. + +```javascript +// Assign the type of edges/nodes in data +const data = { + nodes: [ + { + id: 'node0', + type: 'circle', // the type of this node is circle + }, + { + id: 'node1', + type: 'rect', // the type of this node is rect + }, + ], + edges: [ + { + id: 'edge0', + source: 'node0', + target: 'node1', + type: 'polyline', // the type of this edge is polyline + }, + ], +}; +// or assign them globally when instantiating the Graph +const graph = new Graph({ + // ... other configurations for graph + defaultNode: { + type: 'circle', + // ... other configurations for default node + }, +}); +``` + +In G6 3.3, please use **`type` instead of `shape`** (For now, the `shape` is comatible. But it will be discarded in the future). + +## Cumstom Node/Edge + +## fontSize + +The fontSize of label in G6 3.3 and G6 3.2.x will result in different visual sizes. But if you are using the default fontSize(12), there will be no difference. This is due to the unreasonable matrix tranformation for label in previous version of G6's rendering engine, which is resolved in G6 3.3. Now, the visual size is more reasonable and accurate. + +#### Adding a Shape + +In G6 3.2 and previous versions, you can add a shape to a custom node or edge as following without assign `name` and `draggable`: + +```javascript +G6.registerEdge('customNode', { + draw(cfg, group) { + const keyShape = group.addShape('rect', { + attrs: { + // ... The attributes of the graphic shape + }, + }); + const circle = group.addShape('circle', { + attrs: { + // ... The attributes of the graphic shape + }, + }); + return keyShape; + }, +}); +``` + +In G6 3.3, we highly recommend you to assign `draggable`, and must assign `name` when adding a shape. **And the value of `name` should be unique in a custom node/edge/combo type.** If not, the shapes will not response some interactive events. Adding these two property as below: + +```javascript +G6.registerEdge('customNode', { + draw(cfg, group) { + const keyShape = group.addShape('rect', { + attrs: { + // ... The attributes of the graphic shape + }, + draggable: true, // Allow this shape to be dragged + name: 'key-shape', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }); + const circle = group.addShape('circle', { + attrs: { + // ... The attributes of the graphic shape + }, + draggable: true, // Allow this shape to be dragged + name: 'circle-shape', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }); + return keyShape; + }, +}); +``` + +#### marker + +In G6 3.2 and previous versions, the radius of the marker was assigned by `r` or `radius`. + +In G6 3.3, the radius of marker can be assigned by `r` only. + +e.g. + +```javascript +// <= G6 3.2.x +G6.registerEdge('customNode', { + draw(cfg, group) { + const marker = group.addShape('marker', { + attrs: { + // ... Other attributes + radius: cfg.size[0], + }, + draggable: true, + name: 'marker-shape', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }); + return keyShape; + }, +}); + +// <= G6 3.3 +G6.registerEdge('customNode', { + draw(cfg, group) { + const marker = group.addShape('marker', { + attrs: { + // ... Other attributes + r: cfg.size[0], + }, + draggable: true, + name: 'marker-shape',// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }); + return keyShape; + }, +}); +``` + +#### fan + +In G6 3.2.x and previous version, we support fan shape (usage is shown below). G6 3.3 does not support this shape any more. + +```javascript +// This is not supported in G6 3.3 +group.addShape('fan', { + attrs: { + x: 50, + y: 50, + re: 40, + rs: 30, + startAngle: (1 / 2) * Math.PI, + endAngle: Math.PI, + clockwise: false, + fill: '#b7eb8f', + }, + name: 'fan-shape' // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type +}); +``` + +## pixelRatio + +In G6 3.2 and previous versions, you need to assign the `pixelRatio` when instantiating the Graph. + +In G6 3.3, `pixelRatio` will be calculated automatically. + +## The Timing Event of click-select and brush-select + +In G6 3.2 and previous versions, The timing `nodeselectchange` will be emitted when end-users make some change by `brush-select` and `click-select`. There were two properties in the callback function of `nodeselectchange` for `brush-select`: + +- `targets`: The selected nodes and edges currently as `{nodes: [...], edges: [...]}` +- `select`: Whether the option is select or deselect currently. `true` | `false` + +There were two another properties in the callback function of `nodeselectchange` for `click-select`: + +- `target`: The manipulated node currently. It might be selected or deselected +- `select`: Whether the option is select or deselect currently. `true` | `false` + +G6 3.3 unifies the `nodeselectchange` events in these two behaviors. The `targets` is replaced by `selectedItems`, which means the selected nodes and edges currently, to avoid the confusion of the meanings of `targets` and `target`. + +
Now, there two properties in the callback function of `nodeselectchange` for `brush-select`: + +- `selectedItems`: The selected nodes and edges currently as `{nodes: [...], edges: [...]}` +- `select`: Whether the option is select or deselect currently. `true` | `false` + +There three properties in the callback function of `nodeselectchange` for `click-select`: + +- `target`: The manipulated node currently. It might be selected or deselected +- `selectedItems`: The selected nodes and edges currently as `{nodes: [...], edges: [...]}` +- `select`: Whether the option is select or deselect currently. `true` | `false` + +## SVG Renderer + +In G6 3.2 and previous versions, you can assign the renderer with options options `'canvas'` and `'svg'` for graph when instantiating the Graph. + +In G6 3.3, to make a better version with canvas, we do not support SVG renderer temporary. But we will support it in the future. diff --git a/packages/site/docs/manual/FAQ/upgradeGuide.zh.md b/packages/site/docs/manual/FAQ/upgradeGuide.zh.md new file mode 100644 index 0000000000..4f6cfee164 --- /dev/null +++ b/packages/site/docs/manual/FAQ/upgradeGuide.zh.md @@ -0,0 +1,363 @@ +--- +title: G6 3.3 升级指南 +order: 1 +--- + +## 构建产物 + +G6 3.3.0 支持三种构建产物: + +- lib:commonjs; +- es:esm; +- dist:umd。 + +`import G6 from '@antv/g6'` 默认引用的是 lib 文件夹下的产物。 + +**其中 esm 及 commonjs 构建产物不支持 webworker 布局。** + +如果要支持 webworker 布局,请引用 [CDN](https://gw.alipayobjects.com/os/lib/antv/g6/4.3.11/dist/g6.min.js) 上的文件。 + +## Util + +- 不再在 G6 中 import @antv/util  后,又 export 一次,直接使用 @antv/util  相关方法; +- 移除了 util/layout 文件; + - 将 util/layout 中的 `scaleMatrix`、`floydWarshall`、`getAdjMatrix` 方法移到了 util/math 文件中; + - util/layout 中的 `getEDistance` 方法与 util/math 中的 `distance` 重复,统一使用 math 中的 `distance` 方法; + - 删除了 util/layout 中的 `mix`、`augment`、`isString` 三个方法,统一使用 @antv/util  中相关方法; +- `groupData` 改名为 `group`; +- util/group 中删除了 `flatToTree` 和 `addNodesToParentNode` 两个方法; +- util/base 文件中删除了对 @antv/util 的依赖。 + +## 插件 Plugins + +使用 G6 内置插件时不再需要引入其他包,引入 G6 后直接通过 `G6.PluginName` 的方式获得。例如: + +```javascript +// <= G6 3.2 +// CDN 引入 G6 以及需要使用的插件 + + + +// 或 NPM 引入,需要引入 G6 及需要使用的插件 +import G6, { Minimap, Grid } from '@antv/G6' + +const minimap = new Minimap({ + //... configurations +}) +const grid = new Grid({ + //... configurations +}) + + +// G6 4.x +// CDN 引入,只需要引入 G6 + + +// G6 3.x 引入地址 +// + +// 或 NPM 引入,只需要引入 G6 +import G6 from '@antv/G6' + +const minimap = new G6.Minimap({ + //... configurations +}) + +const grid = new G6.Grid({ + //... configurations +}) + +const graph = new G6.Graph({ + //... other configurations + plugins: [ minimap, grid ] +}); +``` + +## Tree-Graph 兼容问题 + +G6 3.3 中,TreeGraph 的布局相关方法与 Graph 统一: + +1. `changeLayout` 修改为 `updateLayout`; +1. `refreshLayout` 修改为 `layout`。 + +## 动画 + +G6 3.2.x 及以下版本中动画的使用方式: + +```javascript +G6.registerEdge( + 'loop-growth', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = shape.getTotalLength(); + shape.animate( + { + onFrame(ratio) { + const startLen = ratio * length; + // 计算线的lineDash + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + repeat: true, + }, + 2000, + ); + }, + }, + 'loop', +); +``` + +G6 3.3 版本中动画: + +- 我们不再建议使用 3.2 中的调用方式,即包含复写 `onFrame`。这一种调用方式将会被移除; +- 提供两种使用 `animate` 的方式: + - 方式一:`animate(toAttrs, animateCfg)`。其中 `toAttrs` 为动画的目标参数,`animateCfg` 为动画的配置。例如: + +```javascript +G6.registerEdge( + 'widen-line', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = shape.getTotalLength(); + shape.animate( + { + lineWidth: 10, + }, + { + repeat: false, + duration: 500, + }, + ); + }, + }, + 'single-edge', +); +``` + +- 方式二:`animate(onFrame, animateCfg)`。其中 `onFrame` 为每一帧的回调函数,`animateCfg` 为动画的配置。例如: + +```javascript +G6.registerEdge( + 'loop-growth', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = shape.getTotalLength(); + shape.animate( + (ratio) => { + const startLen = ratio * length; + // 计算线的lineDash + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + { + repeat: true, + duration: 2000, + }, + ); + }, + }, + 'loop', +); +``` + +## 元素类型指定 + +G6 3.2.x 及以下版本中指定节点或边的图形类型时,可以通过在数据中单个配置、实例化图时全局配置、更新时动态配置等。例如: + +```javascript +// 在数据中单个配置 +const data = { + nodes: [ + { + id: 'node0', + type: 'circle', + }, + { + id: 'node1', + type: 'rect', + }, + ], + edges: [ + { + id: 'edge0', + source: 'node0', + target: 'node1', + type: 'polyline', + }, + ], +}; +// 或在实例化图时全局配置 +``` + +G6 3.3 将会使用 **`type` 字段替代 `shape` 字段**(同时兼容 `shape`,但 `shape` 在以后的版本中将会被舍弃)。 + +## fontSize + +G6 3.3 与 G6 3.2.x 的节点/边的 label 在相同的 fontSize 时,视觉上会有差别,但若二者都使用了默认字号(12),则二者视觉上相同。这是由于在 G6 3.2.x 及之前的版本中,底层渲染引擎在文字上做了额外的矩阵变换,这是不合理的。新版本修复了这一问题,目前的字号对应视觉样式是更合理、准确的。 + +## 自定义节点/边 + +#### 继承内置节点/边 + +G6 3.3 中,自定义节点/边时若不希望继承任何节点/边,则不需要为 `registerNode` 或 `registerEdge` 函数的第三个参数传递任何值。且**必须**重写 `draw` 方法。 + +```javascript +G6.registerEdge('customNode', { + draw(cfg, group) { + // ... + return keyShape; + }, +}); +``` + +希望继承时,节点的基类为 `'single-node'`,边的基类为 `'single-edge'`。除了继承基类外,还可以继承其他内置节点或边。而在 G6 3.2 及之前的版本中节点和边的基类统一为 `'single-shape'`。 + +#### 增加图形 + +G6 3.2 及之前的版本中,自定义节点/边时增加图形可以通过下面代码,不需要指定 `name` 及 `draggable` 属性: + +```javascript +G6.registerEdge('customNode', { + draw(cfg, group) { + const keyShape = group.addShape('rect', { + attrs: { + // ... 图形属性 + }, + }); + const circle = group.addShape('circle', { + attrs: { + // ... 图形属性 + }, + }); + return keyShape; + }, +}); +``` + +G6 3.3 中,新增图形时建议指定 `draggable`,必须指定 `name`(**`name` 值在同一元素类型中需要不唯一**)。若不添加,可能导致节点/边上的非 keyShape 图形不能响应部分事件。添加方式如下: + +```javascript +G6.registerEdge('customNode', { + draw(cfg, group) { + const keyShape = group.addShape('rect', { + attrs: { + // ... 图形属性 + }, + draggable: true, // Allow this shape to be dragged + name: 'key-shape', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }); + const circle = group.addShape('circle', { + attrs: { + // ... 图形属性 + }, + draggable: true, // Allow this shape to be dragged + name: 'circle-shape', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }); + return keyShape; + }, +}); +``` + +#### marker 图形 + +G6 3.2 及之前版本中,marker 图形的半径是通过 `r` 或 `radius` 指定。 + +G6 3.3 中,marker 的图形半径只能通过 `r` 指定。 + +例如: + +```javascript +// <= G6 3.2.x +G6.registerEdge('customNode', { + draw(cfg, group) { + const marker = group.addShape('marker', { + attrs: { + // ... 其他图形属性 + radius: cfg.size[0], + }, + draggable: true, + name: 'marker-shape', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }); + return keyShape; + }, +}); + +// <= G6 3.3 +G6.registerEdge('customNode', { + draw(cfg, group) { + const marker = group.addShape('marker', { + attrs: { + // ... 其他图形属性 + r: cfg.size[0], + }, + draggable: true, + name: 'marker-shape', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }); + return keyShape; + }, +}); +``` + +#### fan 图形 + +G6 3.2.x 及之前的版本支持 fan 图形(使用方法如下),G6 3.3 不再支持。 + +```javascript +// 如下图形 G6 3.3 不再支持 +group.addShape('fan', { + attrs: { + x: 50, + y: 50, + re: 40, + rs: 30, + startAngle: (1 / 2) * Math.PI, + endAngle: Math.PI, + clockwise: false, + fill: '#b7eb8f', + }, + name: 'fan-shape' // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 +}); +``` + +## pixelRatio + +在 G6 3.2 及之前的版本中,需要用户根据当前分辨率在实例化图时指定 `pixelRatio`。 + +G6 3.3 中,`pixelRatio` 将被自动计算,不需要再由用户传递。 + +## click-select 与 brush-select 时机事件 + +G6 3.2 中 `brush-select` 与 `click-select` 每次选取发生变化时,将会触发 `nodeselectchange`。`brush-select` 的 `nodeselectchange` 事件的回调参数含有两个字段: + +- `targets`:当前被选中的所有节点与边。`{nodes: [...], edges: [...]}` +- `select`:当前操作是选中还是取消。`true` | `false` + +`click-select`  的 `nodeselectchange` 事件的回调参数含有两个字段: + +- `target`:当前被操作的一个节点,可能是被选中,或取消选中 +- `select`:当前操作是选中还是取消。`true` | `false` + +G6 3.3 统一了这两个 behavior 的 `nodeselectchange` 事件。使用  `selectedItems` 替代 `targets` 字段,表示当前被选中的所有元素。防止 `targets` 的含义与 `target` (当前被操作的节点,可能是被选中或被取消选中)混淆。
`brush-select` 有两个字段: + +- `selectedItems`:当前被选中的所有节点与边。`{nodes: [...], edges: [...]}` +- `select`:当前操作是选中还是取消。true | false + +`click-select` 有两个字段: + +- `target`:当前被操作的一个节点,可能是被选中,或取消选中 +- `selectedItems`:当前被选中的所有节点与边。`{nodes: [...], edges: [...]}` +- `select`:当前操作是选中还是取消。`true` | `false` + +## SVG 渲染 + +在 G6 3.2 及之前的版本中,可以在实例化图时为图指定渲染器,`renderer` 可选为 `'canvas'` 或 `'svg'`。 + +在 G6 3.3 中,我们致力于更好的 Canvas 版本,暂时不支持 SVG 方式渲染。`renderer` 参数不再起效。SVG 渲染在以后的版本中提供支持。 diff --git a/packages/site/docs/manual/FAQ/vite-support.en.md b/packages/site/docs/manual/FAQ/vite-support.en.md new file mode 100644 index 0000000000..2bc6a3b532 --- /dev/null +++ b/packages/site/docs/manual/FAQ/vite-support.en.md @@ -0,0 +1,30 @@ +--- +title: Error in Vite with G6 +order: 3 +--- + +> This solution comes from GitHub User @wooodchen + +If you coming up with the error like following picture shows +![image](https://user-images.githubusercontent.com/20942938/121661253-98c82e00-cad6-11eb-9d5c-e5827db62ed9.png) + +Please refer to [GitHub Issue #2961](https://github.com/antvis/G6/issues/2961). + + +You should configure two plugins: `rollup-plugin-node-rosolve` and `rollup-plugin-commonjs`. The warning will exist but the project will be available. + +Configure `plugin` in `vite.config.js`: + +```javascript +import resolve from 'rollup-plugin-node-resolve' +import commonjs from 'rollup-plugin-commonjs' +// ... +export default defineConfig({ + plugin: [ + ..., + resolve(), + commonjs(), + ... + ] +}) +``` \ No newline at end of file diff --git a/packages/site/docs/manual/FAQ/vite-support.zh.md b/packages/site/docs/manual/FAQ/vite-support.zh.md new file mode 100644 index 0000000000..2ccf8b7bae --- /dev/null +++ b/packages/site/docs/manual/FAQ/vite-support.zh.md @@ -0,0 +1,28 @@ +--- +title: Vite + G6 报错 +order: 3 +--- + +> 感谢来自 GitHub 用户 @wooodchen 的解决方案 + +如果你在使用 Vite + G6 构建时遇到类似下图的错误 +![image](https://user-images.githubusercontent.com/20942938/121661253-98c82e00-cad6-11eb-9d5c-e5827db62ed9.png) + +请参考 [GitHub Issue #2961](https://github.com/antvis/G6/issues/2961) 中的解决方案。 + +需要插件 `rollup-plugin-node-rosolve` 和 `rollup-plugin-commonjs`。虽然不能消除提示,但可以使用。 +在 `vite.config.js` 中如下配置 + +```javascript +import resolve from 'rollup-plugin-node-resolve' +import commonjs from 'rollup-plugin-commonjs' +// ... +export default defineConfig({ + plugin: [ + ..., + resolve(), + commonjs(), + ... + ] +}) +``` \ No newline at end of file diff --git a/packages/site/docs/manual/advanced/comboTheory.en.md b/packages/site/docs/manual/advanced/comboTheory.en.md new file mode 100644 index 0000000000..2920b85e24 --- /dev/null +++ b/packages/site/docs/manual/advanced/comboTheory.en.md @@ -0,0 +1,72 @@ +--- +title: combo Theory +order: 3 +--- + +> The English version is in progress + +## The Rendering Logic of combo + +When there are no combos but nodes and edges, the visual index (zIndex) of edges are lower than nodes by default. For a graph with combos, rules about visual index should be specified to achieve reasonable result. For convenience, z(X) indicates the visual index(zIndex) in the following. + +- Rule 1: For one unnested combo, z(Node) > z(Edge) > z(combo), as shown below + +img + +> z(a) = z(b) > z(e0) > z(combo A) + +- Rule 1+: Suppose that combo A has sub combos and nodes, and there are edges between the nodes, z(sub combo) > z(Node) > z(Edge) > z(combo A it self), as shown below: + +img + +> z(b1) = z(b2) > z(e2) > z(combo B) > z(a1) = z(a2) > z(e1) > z(combo A) + +- Rule 2: We now abtain all the visual indexes of combos and nodes by rule 1. If there is an edge E with end nodes a and b from different combos, and we already know z(a) and z(b), the z(E) will be equal to the visual index of the edges in the combo which contains the end node with larger z(x). That is: + - When z(a) > z(b), z(E) is equal to the visual index of the edges in the combo which contains a; + - When z(a) <= z(b), z(E) is equal to the visual index of the edges in the combo which contains b. + +As shown in the figure below, The edges with red label matches Rule 2: + +img + +> z(e4) = z(e2) z(e5) = z(e2) z(e6) = z(e1)=z(e3) + +- Rule 2+: The combo B of upper figure is collapsed as following figure. The related nodes and edges are hidden, and some vitual edges are added to represent the relationships between items inside and outside combo B. + +img +img + +## combo Layout Theory + +G6 provides a force-directed based layout for combo named 'comboForce'. There are three situations to be considered: + +1. Layout all the items; +2. Expand a combo interactively; +3. Collapse a combo interactively. + +The principle of traditional force-directed layout: There are repulsive forces between all the node pairs as `Fr = k/r2`; There are attractive forces between the node pairs which have connections(edges) as `Fa = ks * r`. Where `r` is the distance between two nodes, `k` and `ks` are coefficient. To meet the requirement of combo layout, we add some additional strategies to make the nodes inside a combo more compact and avoid the combo overlappings. + +#### Define a coefficient m = f(c) for the attractive force Fa on the edge + +- 「Inter Edge」means an edge with two end nodes from different combos. All the edges in the below figure are inter edges. The attractive forces on them should be reduce the avoid this two combos overlapped. So the coefficient is `m = f(c) < 1`. Higher difference of the combos' depths, `m` should be smaller. E.g. the differences of `e46`, `e23`, `e12`, and `e15` are 1, and `e34`、`e13` are 2. So `f(c)` is a function about difference of the depths bewteen two end nodes' combos, e.g. `m = 1/c`; +- 「Intra Edge」means an edge with two end nodes form the same combo,the coefficient is `m = f(c) = 1`. + +#### Gravity for combo + +- For convenience, we say `P(X)` is the hierarchy depth of combo X. As shown in the figure below, `P(A) > P(B) > P(C) > P(D)`; +- Each combo has a gravity force G(X) for its succeeding nodes from their mean center. The mean center will be updated in each iteration; +- Smaller `P(X)`, larger `G(X)`. e.g. `G(X) = 1/P(X)`; +- Some nodes might be affected by multiple gravity forces. Such as the node #6 in the figure below, it is affected by the gravity forces `G(C)` from combo C with red stroke, `G(B)` from combo B with green stroke, and `G(A)` from combo A with yellow stroke, where `G(C) > G(B) > G(A)`. + +#### Overlapping detection + +- Detect the overlappings between nodes in each iteration, and: + - If two node overlapped, magnify a coefficient `R` to the repulsive force between them to take them apart. +- Detect the overlappings between combos in each iteration (or each `q` iteraction in reduce the computation): + - First of all, compute the bounding box of the children (including nodes and sub combos); + - Then traverse the combo tree from top to bottom to find the overlapped combo pairs with same depth in a parent combo; + - Increase the gravity of the parent combo if two sub combos are overlapped. + +img + +> The combos with same hierarchy depths are in the same color. The hierarchy depths on this graph is: A > B > C > D diff --git a/packages/site/docs/manual/advanced/comboTheory.zh.md b/packages/site/docs/manual/advanced/comboTheory.zh.md new file mode 100644 index 0000000000..bd5725de0d --- /dev/null +++ b/packages/site/docs/manual/advanced/comboTheory.zh.md @@ -0,0 +1,70 @@ +--- +title: Combo 实现原理 +order: 3 +--- + +## Combo 渲染视觉层级逻辑 + +当图中只有 Node 和 Edge 而不存在 Combo 时,所有 Edge 的「视觉层级 zIndex」默认低于所有的 Node。但增加嵌套的 Combo 后,元素间的视觉层级需要较复杂的规则定义,方能符合合理的逻辑。为了方便说明,我们用 z(X) 表示 X 元素的视觉层级值。 + +- 规则一:单层 Combo 中各元素层级关系是 z(Node) > z(Edge) > z(Combo),如下所示: + +img + +> z(a) = z(b) > z(e0) > z(Combo A) + +- 规则一补充:假设 Combo A 内部的子元素包括子 Combo 及节点,且节点间存在边,则:z(子 Combo) > z(Node) > z(Edge) > z(Combo A 本身),示例如下: + +img + +> z(b1) = z(b2) > z(e2) > z(Combo B) > z(a1) = z(a2) > z(e1) > z(Combo A) + +- 规则二:通过规则一,可以得到所有 Combo 及 Node 的视觉层级值。若存在某条边 E 的两个端点 a 与 b 来自不同的 Combo,已知 z(a) 与 z(b),则 z(E) 为 max(z(a), z(b)) 所在 Combo 内边的层级,即: + - 当 z(a) > z(b) 时,z(E) 等于 a 所在 Combo 内边的层级; + - 当 z(a) <= z(b),z(E) 等于 b 所在 Combo 内边的层级。 + +以下图为例,图中红色标注的边属于上述情况: + +img + +> z(e4) = z(e2) z(e5) = z(e2) z(e6) = z(e1)=z(e3) + +- 规则二补充:在上图的基础上,Combo B 收起后,如下左图;Combo A 收起后,如下右图。可以发现,在收缩一个 Combo 后,隐藏了与该 Combo 相关的节点及边,而增加了虚拟边来表示有外部元素连接到该 Combo 内的元素。 + +img +img + +## Combo 布局原理 + +Combo 使用带有不重叠约束的力导型布局方法,Combo 布局分为以下三种情况: + +1. 布局最细粒度所有元素; +2. 交互展开一个 Combo; +3. 交互收起一个 Combo。 + +力导向布局的原则:所有点对之间存在斥力 `Fr = k/r2`,边连接的点对之间存在引力 `Fa = ks * r`,其中 `r` 为两个节点之间的距离,`k` 与 `ks` 为系数。 + +#### 为边上的引力 Fa 添加系数 m = f(c) + +- 「跨组边」—— 边两端节点来自不同的 Combos,减弱其引力大小,即 `m = f(c) < 1`。图中所有标出的边都为跨组边。跨越层数越多,减弱程度越高。如 `e46`、`e23`、`e12`、`e15` 跨越了一层,`e34`、`e13` 跨越了两层。因此 `f(c)` 是关于跨越层数(c)的函数,可以是 `m = 1/c` 等; +- 「同组边」—— 边两端节点来自相同 Combo,则引力定义方式不变,即 `m = f(c) = 1`。 + +#### 增加 Combo 的中心力 + +- 为方便描述,我们为 Combo X 定义层级的高低值 `P(X)`。如下图所示,A、B、C、D 四个组的层级高低:`P(A) > P(B) > P(C) > P(D)`; +- 每个 Combo 中有由分组内节点当前的平均位置中心发出的重力,该平均中心根据每次迭代的节点位置进行更新; +- `P(X)` 越小,其发出的重力 `G(X)` 越大。例如 `G(X) = 1/P(X)`; +- 有些节点可能受到多个重力。例如下图 6 号节点,受到了它上层红色 Combo C 的重力 `G(C)`,绿色 Combo B 的重力 `G(B)`,黄色 Combo A 的重力 `G(A)`。`G(C) > G(B) > G(A)`。 + +#### 迭代中的重叠检测 + +- 每次迭代检测节点之间是否存在重叠: + - 若两个节点之间存在重叠,则为二者间的斥力乘以一个放大系数 `R`,使之斥开。 +- 每次迭代(或每 `q` 次迭代)检测 Combo 之间是否存在重叠: + - 首先计算最小能够包围该组内元素的圆形或矩形(根据 Combo Type 决定); + - 计算至上而下遍历,检测每个 Combo 内层级相同的子 Combos 是否存在重叠; + - 若存在重叠则加大该 Combo 的重力。 + +img + +> 相同颜色的 border 代表了相同的层级,该图层级由高到低分别是:A > B > C > D diff --git a/packages/site/docs/manual/advanced/coordinate-system.en.md b/packages/site/docs/manual/advanced/coordinate-system.en.md new file mode 100644 index 0000000000..22f25484fc --- /dev/null +++ b/packages/site/docs/manual/advanced/coordinate-system.en.md @@ -0,0 +1,229 @@ +--- +title: Coordinate Systems in G6 +order: 0 +--- + +In G6, the container of the `` is assigned by `container` when instantiating a Graph. But the coordinate system of DOM and the drawing coordinate system are not the same, which leads to some confusion in the cases: + +- Placing a DOM element (tooltip, menu, and so on) with `position: absolute` upon the canvas on the position where + - the mouse clicked; + - the clicked node. + +The problem is more obvious after the graph has been panned and zoomed. Now, we are going to learn the coordinate systems in G6 to help you solve the issue. + +## Three Coordinate Systems + +First of all, we know that there are three kinds of coordinate system in G6, we call them: clientX/clientY, canvasX/canvasY, and pointX/pointY. + +### clientX/clientY + +It is related to the broswer, whose origin is at the left top of the broswer's content and it is not scrolled with the page. As shown below, the two pictures show the cases with different positions of y-scroll, but the origin of clientX/clientY is fixed. But the clientX/clientY coordinates of the left-top of the Container Dom in the two cases are different. Left one has (100, 1000), but right one has (100, 200). + +img +img + +### canvasX/canvasY + +It is the self coordinate system of the Container DOM. We suppose that the `width` and `height` of the graph were assigned to be 550 and 500: + +```javascript +const Graph = new G6.Graph({ + container: 'container', + width: 550, + height: 500 +}) +``` + +The size of the Container DOM is 550\*500. The origin of the canvasX/canvasY system is at the left-top of the Container DOM, and the right-bottom point of the Container DOM is (550, 500). + +img + +### pointX/pointY + +The previous two coordinate systems are the DOM related systems, and the coordinate values are integer. Different from them, pointX/pointY is the real drawing coordinate systems for shapes on the canvas. For nodes, each node has (x, y) in pointX/pointY system as position information. Notice that when end users pan or zoom a graph, they are actually manipulating the pointX/pointY coordinate system. + +## The Relationships between the Three System + +To be more specific, we show an example in this section. + +The three systems in the figures of this section are colored with grey, blue, and red, corresponds to clientX/clientY, canvasX/canvasY, and pointX/pointY respectively. And the labels and numbers for each system have the corresponding colors. + +img + +### Graph without Transformation + +The figure below shows the case without transfromation, that is, the tranfromational matrix is a unit matrix. We can see from the figure that the canvasX/canvasY and pointX/pointY are overlapped, including the scale of the axes and the origin. The canvasX/canvasY and pointX/pointY coordinates of the root node's position, tagged by black point, of the graph are the same. And the origin of the clientX/clientY is at the left-top of the broswer's content, and the black point's clientX/clientY coordinate is equal to the canvasX/canvasY or pointX/pointY plus the left and top distance to the browser's border. + +img + +> Figure 1: three coordinate systems when the graph has no transformation. + +### Zoomed Graph + +In fact, zooming the graph is zooming the pointX/pointY system (centered at the its origin by default). Now we zoom the graph with scale `2`, which means the transformational matrix of the graph is: + +``` +matrix = + 2 0 0 + 0 2 0 + 0 0 1 +``` + +It is easy to be imagine that part of the graph will be out of the Container DOM. + +The graph is zoomed with scale `2` means the pointX/pointY system is zoomed with scale `2`, the scale of the axes are zoomed two times to origin state. You can think of the coordinate axes as two retractable ropes. The ropes have a mark with '1' or '2' or '3' ... every one centimeter in the relaxed state. These marks are the coordinate values of the pointX/pointY system. To zoom the graph, we pull the ends of the ropes separately and double them in the positive direction, then the distance between adjacent marks becomes two centimeters. + +In the same time, canvasX/canvasY and clientX/clientY coordinate systems stay unchanged. In the other word, (90, 0) in canvasX/canvasY is (45, 0) in the pointX/pointY, (0, 250) in canvasX/canvasY is (0, 125) in pointX/pointY. But the corresponding values in clientX/clientY is the values in canvasX/canvasY plus the left and top distances from the Container DOM to the borders of the browser. + +img + +> Figure 2: Three coordinate systems when the graph is zoomed. + +In figure 2, the canvasX/canvasY and clientX/clientY coordinate value of the black point in figure 1 stays unchanged with (90, 250) and (290, 350) respectively. Due to the changes of the scales of the pointX/pointY system, the value is changed to (45, 125). And the root node (marked with black point B) in figure 2 has same pointX/pointY value as figure 1, and its values in canvasX/canvasY and clientX/clientY are changed to (180, 500) and (380, 600) respectively. + +### Zoomed and Translated Graph + +Based on the zoomed graph of previous section, we now translate with vector (50, 50), which means the transfromational matrix becomes: + +``` +matrix = + 2 0 0 + 0 2 0 + 50 50 1 +``` + +It is easy to be imagine that the left top part of the COntainer DOM will be empty. + +Similar the previous version, translating the graph is actrually translating the whole pointX/pointY system, that is moving the axes ropes along positive direction of x by 50 and positive direction of y by 50 respectively. + +In the sametime, canvasX/canvasY and clientX/clientY system stay unchanged. E.g., (90, 0) of canvasX/canvasY corresponds to ((90-50)/2=20, 0) in pointX/pointY system; (0, 250) of canvasX/canvasY corresponds to (0, (250-50)/2=100) in pointX/pointY system. But the corresponding values in clientX/clientY is still the values in canvasX/canvasY plus the left and top distances from the Container DOM to the borders of the browser. + +img + +> Figure 3: Three coordinate systems of zoomed and translated graph. + +In figure 3, the canvasX/cnavasY and clientX/clientY coordinate value of the black point in figure 1 stays unchanged with (90, 250) and (290, 350) respectively. Due to the changes of the origin of the pointX.pointY system, the value is changed to (20, 100). And the root node (marked with black point B) in figure 3 has same pointX/pointY value as figure 1 and figure 2, and its canvasX/canvasY and clientX/clientY are chagned to (230, 550) and (430, 650) respectively. + +## Translate the Coordinates with API + +Sometimes we need to translate the coordinates for different usages. In G6, the paremeter `event` of different events contains three coordinate values for the three coordinate system, the correspondence of the variable names in `event` and the systems is: + +- event.x, event.y => pointX/pointY; +- event.canvasX, event.canvasY => canvasX/canvasY; +- event.clientX, event.clientY => clientX/clientY. + +Notice that the `x` and `y` are the velus of pointX/pointY system. + +### getCanvasByPoint + +Translate coordinate values of pointX/pointY system to canvasX/canvasY system. + +### getPointByCanvas + +Translate coordinate values of canvasX/canvasY system to pointX/pointY system. + +### getClientByPoint + +Translate coordinate values of pointX/pointY system to clientX/clientY system. + +### getPointByClient + +Translate coordinate values of clientX/clientY system to pointX/pointY system. + +Easy to find out that these four APIs are all about pointX/pointY, and users are able to translate between clientX/clientY and canvasX/canvasY by combining the four APIs: + +- clientX/clientY to canvasX/canvasY: + +```javascript +const point = graph.getPointByClient(clientX, clientY) +const canvasXY = graph.getCanvasByPoint(point.x, point.y); +``` + +- canvasX/canvasY to clientX/clientY: + +```javascript +const point = graph.getPointByCanvas(canvasX, canvasY) +const clientXY = graph.getClientByPoint(point.x, point.y); +``` + +## Using The Three Coordinate Systems + +At the beginning of this document, we metioned the following case: + +Place a DOM with `position: absolute` , e.g. tooltip, menu, and so on, at the position where + +- the point currently clicked by the mouse; +- the currently clicked node's position. + +Wrong offset will occur if user takes coordinate values of wrong coordinate systems. The problem will become worse if the graph is zoomed or translated. + +Now, we are going to define a DOM for such case: + +```javascript + const floatDOM = createDom(` +
+ floating dom +
+ `); +``` + +No matter which case, we recommend two ways to mount the DOM: + +- Way 1: Mount the DOM on body: + +```javascript +document.body.appendChild(floatDOM); +``` + +- Way 3: Mount the DOM on Container DOM , which means the DOM has the same parent of the canvas: + +```javascript +const container = document.getElementById('container') // Suppose that the id of the COntainer DOM is 'container' +container.appendChild(floatDOM); +``` + +### Mount DOM on body + +We know that a DOM with `position: absolute` is positioned related to the parent DOM. Mounting the DOM on body means the parent DOM of the DOM is body. So we can use `clientX/clientY` to assign its `left/top`: + +- Place the DOM on the position where the mouse currently clicked: + +```javascript +graph.on('canvas:click', event => { + floatDOM.style.left = event.clientX; + floatDOM.style.top = event.clientY; +}); +``` + +- Place the DOM on the position where the currently clicked node on: + +```javascript +const node = graph.getNodes()[0]; +const { x, y } = node.getModel(); // 获得该节点的位置,对应 pointX/pointY 坐标 +const clientXY = graph.getClientByPoint(x, y); +floatDOM.style.left = clientXY.x; +floatDOM.style.top = clientXY.y; +``` + +### Mount DOM on Container DOM + +If we mount the DOM on the Container DOM, the parent of the DOM is the Container DOM, we can use `canvasX/canvasY` to assign its `marginLeft/marginTop`: + +- Place the DOM on the position where the mouse currently clicked: + +```javascript +graph.on('canvas:click', event => { + floatDOM.style.marginLeft = event.canvasX; + floatDOM.style.marginTop = event.canvasY; +}); +``` + +- Place the DOM on the position where the currently clicked node on: + +```javascript +const node = graph.getNodes()[0]; +const { x, y } = node.getModel(); // 获得该节点的位置,对应 pointX/pointY 坐标 +const canvasXY = graph.getCanvasByPoint(x, y); +floatDOM.style.marginLeft = canvasXY.x; +floatDOM.style.marginTop = canvasXY.y; +``` diff --git a/packages/site/docs/manual/advanced/coordinate-system.zh.md b/packages/site/docs/manual/advanced/coordinate-system.zh.md new file mode 100644 index 0000000000..55e75f91fe --- /dev/null +++ b/packages/site/docs/manual/advanced/coordinate-system.zh.md @@ -0,0 +1,225 @@ +--- +title: G6 坐标系深度解析 +order: 0 +--- + +在 G6 中,实例化图时指定 `container` 字段指定了画布 `` 标签的父容器。而 DOM 的坐标与真正绘制图形时的坐标并不是同一套坐标系,这可能会使得如下场景中用户指定坐标时产生困惑: + +- 在画布上放置一个 `position: absolute` 的 DOM 元素,如 tooltip、 menu 等时: + - 在鼠标点击画布上的位置放置; + - 鼠标点击节点时,在节点位置。上面问题在 graph 缩放、平移后更加凸显。下面,我们将深度解析这些坐标之间的关系。 + +## 三个坐标系 + +首先,我们要知道 G6 中有三个坐标系:clientX/clientY、canvasX/canvasY、pointX/pointY。 + +### clientX/clientY + +相对于浏览器的坐标系,原点位于浏览器内容范围的左上角(坐标系位置不随滚动条变化)。如下两图中的 y 轴上页面滚动(y-scroll)位置不同,但 clientX/clientY 坐标系的原点都在左上角。因而 Container DOM 的左上角的 clientX/clientY 不同,左图 Container DOM 的左上角坐标为(100, 1000),右图 Container DOM 的左上角坐标为(100, 200)。 + +img +img + +### canvasX/canvasY + +Container DOM 的自身坐标系。假设示例化图时设定 `width` 与 `height` 分别是 550 与 500: + +```javascript +const Graph = new G6.Graph({ + container: 'container', + width: 550, + height: 500 +}) +``` + +则下图 Container DOM 的宽高即为 550\*500。canvasX/canvsY 的原点在 Container DOM 的左上角,Container DOM 右下角的 canvasX/canvasY 坐标为(550,500)。 + +img + +### pointX/pointY + +以上两种坐标系均可理解为 DOM 相关的坐标,其取值均为整数。而在真正绘制图形时,图形是根据 pointX/pointY 定位的,即节点的 (x, y) 等都是与 pointX/pointY 坐标系相对应的。图的缩放、平移其实是整个 pointX/pointY 坐标系的缩放和平移。 + +## 三个坐标系的关系 + +上面描述比较抽象。下面我们通过具体例子,进一步了解三种坐标系的关系。 + +下面所有图中,灰色的坐标系表示 clientX/clientY,蓝色坐标系表示 canvasX/canvasY,红色坐标系表示 pointX/pointY。灰、蓝、红色的虚线以及数字均是相应坐标系的标注。 + +img + +### 图无变换 + +下图展示了当图没有缩放和平移,也就是说其变换矩阵 matrix 为单位矩阵时,三个坐标系的关系。可以看到 canvasX/canvasY 与 pointX/pointY 两个坐标系是完全重合的,坐标轴尺度、原点位置完全一致。如下图中树图根节点(黑点标注的)位置,其 canvasX/canvasY 和 pointX/pointY 坐标值是一样的。而 clientX/clientY 的原点在浏览器内容左上角,黑点的 clientX/clientY 则需要加上 Container DOM 的左边距和上边距。 + +img + +> 图 1:图无变换时的三种坐标系。 + +### 图缩放 + +当图被缩放:以 pointX/pointY 坐标系的原点为缩放中心,放大为原来的两倍,即图的变换矩阵 matrix 为: + +``` +matrix = + 2 0 0 + 0 2 0 + 0 0 1 +``` + +很容易想到,大部分图可能会超出 Container DOM。 + +事实上,在缩放时,是在缩放 pointX/pointY 的整个坐标系。由于这个例子中缩放中心是 pointX/pointY 坐标系的原点,缩放两倍即是 pointX/pointY 的 x 轴、y 轴的尺度变为原来的两倍。打个比方,我们把 pointX/pointY 的 x 轴、y 轴看作两根绳子,绳子上每隔 1cm 有标记 1、2、3、……这些标记就是 pointX/pointY 的坐标值,我们分别拽住绳子的一端,把它们分别沿正方向拉长一倍,那么相邻标记之间的距离变成了 2cm 。 + +canvasX/canvasY 和 clientX/clientY 坐标系不随图的变换而变化。换句话说,在这种情况下,canvasX/canvasY 的 (90, 0) 对应的 pointX/pointY 坐标为 (45, 0) ,canvasX/canvasY 的 (0, 250) 对应的 pointX/pointY 坐标为 (0, 125) 。而 clientX/clientY 仍然是 canvasX/canvasY 加上 Container DOM 的左/上边距。 + +img + +> 图 2:图缩放变换时的三种坐标系。 + +可以看到上图中,图 1 中的树图根节点位置(黑点 A)的 canvasX/canvasY 和 clientX/clientY 坐标不变,仍然分别是 (90, 250) 和 (290, 350)。但由于 pointX/pointY 的坐标轴尺度发生了变化,所以它的 pointX/pointY 变为了 (45, 125)。而现在的根节点(黑点 B 标记)的绘制坐标不变,pointX/pointY 仍然是 (90, 250),但黑点 B 对应的 canvasX/canvasY 和 clientX/clientY 分别变为了 (180, 500),(380, 600)。 + +### 图缩放 + 平移 + +当图在上一节的基础上再进行平移:把图的左上角移动到 (50, 50) 的位置,即图的变换矩阵 matrix 为: + +``` +matrix = + 2 0 0 + 0 2 0 + 50 50 1 +``` + +很容易想到,大部分图可能会超出 Container DOM 且左上角为出现留白。 + +和上一节缩放相似,平移图其实是在平移 pointX/pointY 的整个坐标系。打个比方,我们把 pointX/pointY 的 x 轴、y 轴看作两根绳子,两根绳子一起向右平移 50,向下平移 50。 + +canvasX/canvasY 和 clientX/clientY 坐标系不随图的变换而变化。换句话说,在这种情况下,canvasX/canvasY 的 (90, 0) 对应的 pointX/pointY 坐标为 ((90-50)/2=20, 0) ,canvasX/canvasY 的 (0, 250) 对应的 pointX/pointY 坐标为 (0, (250-50)/2=100) 。而 clientX/clientY 仍然是 canvasX/canvasY 加上 Container DOM 的左/上边距。 + +img + +> 图 3:图缩放+平移变换时的三种坐标系。 + +可以看到上图中,图 1 中的树图根节点位置(黑点 A)的 canvasX/canvasY 和 clientX/clientY 坐标不变,仍然分别是 (90, 250) 和 (290, 350)。但由于 pointX/pointY 的坐标轴尺度、原点发生了变化,所以它的 pointX/pointY 变为了 (20, 100)。而现在的根节点(黑点 B 标记)的绘制坐标不变,pointX/pointY 仍然是 (90, 250),但黑点 B 对应的 canvasX/canvasY 和 clientX/clientY 分别变为了 (230, 550),(430, 650)。 + +## 使用 API 进行转换 + +了解了三种坐标系的含义后,我们有时需要通过相互转换使用。首先,在 G6 的事件中,event 会包含当前鼠标操作位置的三种坐标值,它们的变量名与上述三种坐标系对应关系如下: + +- event.x, event.y => pointX/pointY; +- event.canvasX, event.canvasY => canvasX/canvasY; +- event.clientX, event.clientY => clientX/clientY。 + +可以发现后两者的名字是直接对应的,我们只需要注意 event 中的 x 和 y 对应的是 pointX/pointY 坐标系即可。 + +### getCanvasByPoint + +将 pointX/pointY 坐标系的坐标值转换为 canvasX/canvasY 的坐标值。 + +### getPointByCanvas + +将 canvasX/canvasY 坐标系的坐标值转换为 pointX/pointY 的坐标值。 + +### getClientByPoint + +将 pointX/pointY 坐标系的坐标值转换为 clientX/clientY 的坐标值。 + +### getPointByClient + +将 clientX/clientY 坐标系的坐标值转换为 pointX/pointY 的坐标值。 + +可以发现 G6 的上述四个 API 都是围绕 point,通过上面四个 API 可以进行组合从而使得 clientX/clientY 与 canvasX/canvasY 进行转换: + +- clientX/clientY 转 canvasX/canvasY: + +```javascript +const point = graph.getPointByClient(clientX, clientY) +const canvasXY = graph.getCanvasByPoint(point.x, point.y); +``` + +- canvasX/canvasY 转 clientX/clientY: + +```javascript +const point = graph.getPointByCanvas(canvasX, canvasY) +const clientXY = graph.getClientByPoint(point.x, point.y); +``` + +## 使用三种坐标系 + +本文开始时,我们提到了如下场景需要我们使用三种坐标系: + +在画布上放置一个 `position: absolute` 的悬浮 DOM 元素,如 tooltip、 menu 等时: + +- 在鼠标点击画布上的位置放置; +- 鼠标点击节点时,在节点位置。 + +如果使用了错误的坐标系来给定悬浮 DOM 元素的位置,将会出现偏移,在图有缩放、平移等变化时,偏移更加严重。在了解如何使用坐标系给悬浮 DOM 定位前,我们先定义一个悬浮 DOM 元素: + +```javascript + const floatDOM = createDom(` +
+ floating dom +
+ `); +``` + +不论上述哪一种情况,我们都推荐两种挂载这个悬浮 DOM 的方式: + +- 方法一:挂载在 body 上: + +```javascript +document.body.appendChild(floatDOM); +``` + +- 方法二:挂载在 Container DOM 上,即与 canvas 标签同一父容器: + +```javascript +const container = document.getElementById('container') // 假设 Container DOM 的 id 为 container +container.appendChild(floatDOM); +``` + +### 悬浮 DOM 挂载在 body 上 + +众所周知,`position: absolute` 的 DOM 元素相对于父容器定位。若我们把悬浮 DOM 挂载在 body 上,它的父容器是 body,我们可以使用 `clientX/clientY` 来指定它的 `left/top`: + +- 在点击画布的位置上放置 DOM: + +```javascript +graph.on('canvas:click', event => { + floatDOM.style.left = event.clientX; + floatDOM.style.top = event.clientY; +}); +``` + +- 在某个节点的位置上放置 DOM: + +```javascript +const node = graph.getNodes()[0]; +const { x, y } = node.getModel(); // 获得该节点的位置,对应 pointX/pointY 坐标 +const clientXY = graph.getClientByPoint(x, y); +floatDOM.style.left = clientXY.x; +floatDOM.style.top = clientXY.y; +``` + +### 悬浮 DOM 挂载在 Container DOM 上 + +若我们把悬浮 DOM 挂载在 Container DOM 上,它的父容器是 Container DOM,我们可以使用 `canvasX/canvasY` 来指定它的 `marginLeft/marginTop`: + +- 在点击画布的位置上放置 DOM: + +```javascript +graph.on('canvas:click', event => { + floatDOM.style.marginLeft = event.canvasX; + floatDOM.style.marginTop = event.canvasY; +}); +``` + +- 在某个节点的位置上放置 DOM: + +```javascript +const node = graph.getNodes()[0]; +const { x, y } = node.getModel(); // 获得该节点的位置,对应 pointX/pointY 坐标 +const canvasXY = graph.getCanvasByPoint(x, y); +floatDOM.style.marginLeft = canvasXY.x; +floatDOM.style.marginTop = canvasXY.y; +``` diff --git a/packages/site/docs/manual/advanced/g6InReact.en.md b/packages/site/docs/manual/advanced/g6InReact.en.md new file mode 100644 index 0000000000..d2741bdaa2 --- /dev/null +++ b/packages/site/docs/manual/advanced/g6InReact.en.md @@ -0,0 +1,110 @@ +--- +title: G6 in React +order: 11 +--- + +### Introduction + +G6 is a JavaScript library without any coupling to other framewroks. That means, G6 can be combined to any front-end framework, such as React, Vue, and Angular. In this document, we provide a demo about using G6 in React. + +The main difference between using G6 in React and HTML is that you need to guarantee the DOM container of graph has been rendered and it is available before instantiating a Graph. + +In this demo, we will implement a simple flow diagram as the figure below: + +img + +### Implementation + +The demo includes these functions: + +- Register a custom node; +- Register a custom edge; +- Utilize node tooltip; +- Utilize edge tooltip; +- Utilize the context menu on node; +- Render the custom React components of tooltip and ContextMenu. + +In React, you can fetch the DOM element by `ReactDOM.findDOMNode(ref.current)`. + +```javascript +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; +import { data } from './data'; +import G6 from '@antv/g6'; + +export default function () { + const ref = React.useRef(null); + let graph = null; + + useEffect(() => { + if (!graph) { + graph = new G6.Graph({ + container: ReactDOM.findDOMNode(ref.current), + width: 1200, + height: 800, + modes: { + default: ['drag-canvas'], + }, + layout: { + type: 'dagre', + direction: 'LR', + }, + defaultNode: { + type: 'node', + labelCfg: { + style: { + fill: '#000000A6', + fontSize: 10, + }, + }, + style: { + stroke: '#72CC4A', + width: 150, + }, + }, + defaultEdge: { + type: 'polyline', + }, + }); + } + graph.data(data); + graph.render(); + }, []); + + return
; +} +``` + +### Render the React Components + +The styles of the built-in tooltips on nodes/edges and thecontext menu on nodes are too simple to satisfy the complex requirements. Now we show how to customize React components for these tools, then the styles of them can be controlled by users. During the interaction, G6 defines the render timing and position of these components. When the timing and the position are available, they can be managed by React state. + +```javascript +// The coordinate of node tooltip +const [showNodeTooltip, setShowNodeTooltip] = useState(false); +const [nodeTooltipX, setNodeToolTipX] = useState(0); +const [nodeTooltipY, setNodeToolTipY] = useState(0); + +// Listen to the mouse event on node +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + const model = item.getModel(); + const { x, y } = model; + const point = graph.getCanvasByPoint(x, y); + + setNodeToolTipX(point.x - 75); + setNodeToolTipY(point.y + 15); + setShowNodeTooltip(true); +}); + +// Hide the tooltip and the contextMenu when the mouseleave event is activated on the node +graph.on('node:mouseleave', () => { + setShowNodeTooltip(false); +}); + +return
{showNodeTooltip && }
; +``` + +The complete code of this demo 「HERE」. + +You are welcome to provide the usages of G6 in Vue and Angular. Thank you! diff --git a/packages/site/docs/manual/advanced/g6InReact.zh.md b/packages/site/docs/manual/advanced/g6InReact.zh.md new file mode 100644 index 0000000000..081f07c21d --- /dev/null +++ b/packages/site/docs/manual/advanced/g6InReact.zh.md @@ -0,0 +1,110 @@ +--- +title: React 中使用 G6 +order: 11 +--- + +### 概述 + +G6 是一个纯 JS 库,不与任何框架耦合,也就是可以在任何前端框架中使用,如 React、Vue、Angular 等。由于我们内部绝大多数都是基于 React 技术栈的,所以我们也仅提供一个 G6 在 React 中使用的 Demo。 + +在 React 中使用 G6,和在 HTML 中使用基本相同,唯一比较关键的区分就是在实例化 Graph 时,要**保证 DOM 容器渲染完成,并能获取到 DOM 元素**。 + +在 Demo 中,我们以一个简单的流程图为例,实现如下的效果。 + +img + +### 功能及实现 + +Demo 包括以下功能点: + +- 自定义节点; +- 自定义边; +- 节点的 tooltip; +- 边的 tooltip; +- 节点上面弹出右键菜单; +- tooltip 及 ContextMenu 如何渲染自定义的 React 组件。 + +在 React 中,通过  `ReactDOM.findDOMNode(ref.current)`获取到真实的 DOM 元素。 + +```javascript +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; +import { data } from './data'; +import G6 from '@antv/g6'; + +export default function () { + const ref = React.useRef(null); + let graph = null; + + useEffect(() => { + if (!graph) { + graph = new G6.Graph({ + container: ReactDOM.findDOMNode(ref.current), + width: 1200, + height: 800, + modes: { + default: ['drag-canvas'], + }, + layout: { + type: 'dagre', + direction: 'LR', + }, + defaultNode: { + type: 'node', + labelCfg: { + style: { + fill: '#000000A6', + fontSize: 10, + }, + }, + style: { + stroke: '#72CC4A', + width: 150, + }, + }, + defaultEdge: { + type: 'polyline', + }, + }); + } + graph.data(data); + graph.render(); + }, []); + + return
; +} +``` + +### G6 中渲染 React 组件 + +节点和边的 tooltip、节点上的右键菜单,G6 中内置的很难满足样式上的需求,这个时候我们就可以通过渲染自定义的 React 组件来实现。Tooltip 和 ContextMenu 都是普通的 React 组件,样式完全由用户控制。交互过程中,在 G6 中需要做的事情就是确定何时渲染组件,以及渲染到何处。在 G6 中获取到是否渲染组件的标识值和渲染位置后,这些值就可以使用 React state 进行管理,后续的所有工作就全部由 React 负责了。 + +```javascript +// 边 tooltip 坐标 +const [showNodeTooltip, setShowNodeTooltip] = useState(false); +const [nodeTooltipX, setNodeToolTipX] = useState(0); +const [nodeTooltipY, setNodeToolTipY] = useState(0); + +// 监听 node 上面 mouse 事件 +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + const model = item.getModel(); + const { x, y } = model; + const point = graph.getCanvasByPoint(x, y); + + setNodeToolTipX(point.x - 75); + setNodeToolTipY(point.y + 15); + setShowNodeTooltip(true); +}); + +// 节点上面触发 mouseleave 事件后隐藏 tooltip 和 ContextMenu +graph.on('node:mouseleave', () => { + setShowNodeTooltip(false); +}); + +return
{showNodeTooltip && }
; +``` + +完整的 Demo 源码请戳 「这里」。 + +关于 G6 如何在 Vue 及 Angular 中使用,还望社区中有相关实践的同学能提供一些,供其他同学学习和参考,非常感谢! diff --git a/packages/site/docs/manual/advanced/iconfont.en.md b/packages/site/docs/manual/advanced/iconfont.en.md new file mode 100644 index 0000000000..ee0442c4d4 --- /dev/null +++ b/packages/site/docs/manual/advanced/iconfont.en.md @@ -0,0 +1,346 @@ +--- +title: Utilizing Iconfont +order: 10 +--- + +## Introduction + +Due to the good compatibility, type diversity, color diversity, The iconfont is popupar for front-end developments now. Refer to the Iconfont Library of Alibaba. + +## Effect + +result + +## Download the iconfont + +Browse the Iconfont Library of Alibaba and download the iconfont you like by searching a iconfont -> adding it to your library -> going to your library by clicking the shopping cart logo on the right top -> adding it to your project (new one if you do not have any project) -> downloading the iconfont in 'my project' -> decompressing. You will get the files as shown below if everything is right:
download + +Copy the files in the red area (there are lots of unecessary files, we can still copy them all since the unused files will not be packed)to your project. In general, the iconfont files are on the directory of 'static/icons' or 'assets/icons'. New the directory if there is no such directory. It is also fine to put them into any directory. But note to import the right path when you use it. Now, the importing process is done. + +PS: The directory for this example is '/static/icons'. + +## Import G6 + +There are several ways to import G6 introduced in [Getting Started](/en/docs/manual/getting-started).
PS: We import G6 by CDN in this example. + +```html + +``` + +## Import the Iconfont + +We import the iconfont in HTML here: + +```html + +``` + +## Using Iconfont + +```javascript +G6.registerNode('iconfont', { + draw(cfg, group) { + const { backgroundConfig: backgroundStyle, style, labelCfg: labelStyle } = cfg; + + if (backgroundStyle) { + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: cfg.size, + ...backgroundStyle, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + } + + const keyShape = group.addShape('text', { + attrs: { + x: 0, + y: 0, + fontFamily: 'iconfont', // 对应css里面的font-family: "iconfont"; + textAlign: 'center', + textBaseline: 'middle', + text: cfg.text, + fontSize: cfg.size, + ...style, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape1', + }); + const labelY = backgroundStyle ? cfg.size * 2 : cfg.size; + + group.addShape('text', { + attrs: { + x: 0, + y: labelY, + textAlign: 'center', + text: cfg.label, + ...labelStyle.style, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape2', + }); + return keyShape; + }, +}); + +const COLOR = '#40a9ff'; +const graph = new G6.TreeGraph({ + container: 'mountNode', + width: 800, + height: 600, + modes: { + default: ['collapse-expand', 'drag-canvas', 'drag-node'], + }, + defaultNode: { + backgroundConfig: { + backgroundType: 'circle', + fill: COLOR, + stroke: 'LightSkyBlue', + }, + type: 'iconfont', + size: 12, + style: { + fill: '#fff', + }, + labelCfg: { + style: { + fill: COLOR, + fontSize: 12, + }, + }, + }, + // 布局相关 + layout: { + type: 'compactBox', + direction: 'LR', + getId(d) { + return d.id; + }, + getHeight() { + return 16; + }, + getWidth() { + return 16; + }, + getVGap() { + return 20; + }, + getHGap() { + return 60; + }, + }, +}); + +graph.edge(({ target }) => { + const fill = target.get('model').backgroundConfig && target.get('model').backgroundConfig.fill; + return { + type: 'cubic-horizontal', + color: fill || COLOR, + label: target.get('model').relation || '', + labelCfg: { + style: { + fill: fill || COLOR, + fontSize: 12, + }, + }, + }; +}); + +const data = { + isRoot: true, + id: 'Root', + label: '可疑人员王**', + text: '\ue6b2', // 对应iconfont.css 里面的content,注意加u,后面的自行修改一下。 + style: { + fill: 'red', + }, + labelCfg: { + style: { + fill: 'red', + }, + }, + backgroundConfig: null, // 自定义项,用于判读是否需要圆背景 + size: 30, + children: [ + { + id: 'SubTreeNode1', + label: '**网咖', + text: '', + relation: '上网', + children: [ + { + id: 'SubTreeNode2', + label: '多伦多', + text: '', + }, + { + id: 'id1', + label: '小王', + text: '', + children: [ + { + id: 'SubTreeNode1.2.1', + label: '182****2123', + text: '', + }, + { + id: 'SubTreeNode4', + label: '今晚在吗', + text: '', + }, + ], + }, + ], + }, + { + id: 'SubTreeNode3', + label: 'subway', + text: '', + children: [ + { + id: 'SubTreeNode3.1', + label: '王五', + text: '', + }, + { + id: 'SubTreeNode3.2', + label: '张三', + text: '', + }, + ], + }, + { + id: 'SubTreeNode5', + label: '小花', + relation: '老婆', + text: '', + backgroundConfig: { + fill: 'Coral', + }, + style: { + fill: '#fff', + }, + labelCfg: { + style: { + fill: 'Coral', + }, + }, + children: [ + { + id: 'SubTreeNode1.2.1', + label: '182****2123', + text: '', + relation: '通话', + backgroundConfig: { + fill: 'Coral', + }, + style: { + fill: '#fff', + }, + labelCfg: { + style: { + fill: 'Coral', + }, + }, + }, + { + id: 'SubTreeNode3.3', + label: '凶器', + text: '', + relation: '指纹', + backgroundConfig: { + fill: 'Coral', + }, + style: { + fill: '#fff', + }, + labelCfg: { + style: { + fill: 'Coral', + }, + }, + }, + ], + }, + { + id: 'SubTreeNode6', + label: '马航37*', + relation: '乘坐', + text: '', + }, + ], +}; + +graph.data(data); +graph.render(); +``` + +## Attention + +In fact, iconfont is a text shape. + +
**1、The `fontFamily` of the text and the `font-family` in iconfont.css shoulde be kept consistent:**
download + +download + +**2、The `text` in data is the `content` in iconfont.css. And add an `u` after `\`.**
+ +download + +download + +**3、If the iconfonts are rendered wrongly (maybe it is rendered as an empty rect), check whether the font file has been loaded:**
+ +If there is no node using font icon in the page, the font file will not be automatically downloaded, so you can add a hidden node to trigger the loading. If it is already loaded, you can try the following code to refresh the rendering. + +```javascript +// Call the following code after graph.render() +setTimeout(() => { + graph.paint(); +}, 16); +``` + +## Tool Function getIcon + +You can write a function as below to transform unicode. Attention, unicode cannot be connected manually (`\\u${icon.unicode}`). Here we use the `code_decimal` in iconfont.json. For more detail, please refer to [MDN String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint). + +```javascript +import fonts from '../fonts/iconfont.json'; + +const icons = fonts.glyphs.map((icon) => { + return { + name: icon.name, + unicode: String.fromCodePoint(icon.unicode_decimal), // `\\u${icon.unicode}`, + }; +}); +const getIcon = (type: string) => { + const matchIcon = icons.find((icon) => { + return icon.name === type; + }) || { unicode: '', name: 'default' }; + return matchIcon.unicode; +}; +``` + +### Usage + +```javascript + { + type: 'text', + attrs: { + id: 'node-icon', + x: 0, + y: 0, + fontSize: iconSize, + fill: primaryColor, + text: getIcon('logo'), //logo is the name of the unicode + fontFamily: 'iconfont', // same as font-family: "iconfont"; in CSS + textAlign: 'center', + textBaseline: 'middle', + }, + } +``` diff --git a/packages/site/docs/manual/advanced/iconfont.zh.md b/packages/site/docs/manual/advanced/iconfont.zh.md new file mode 100644 index 0000000000..b1c629ea37 --- /dev/null +++ b/packages/site/docs/manual/advanced/iconfont.zh.md @@ -0,0 +1,344 @@ +--- +title: 使用 Iconfont +order: 10 +--- + +## 简介 + +为什么使用 iconfont?  兼容性好、种类多、多色等。在此不做过多介绍,请直接移步 阿里巴巴-iconfont 平台。 + +## 效果 + +result + +## 下载字体图标 + +直接到 阿里巴巴字体图标库 搜索下载即可,简要操作流程是:搜索图标(例如篮球)->  选择自己喜欢的图标添加入库  ->  点击页面右上角的购物车可以看到我们加入的图标 -> 添加至项目,如果没有项目到话可以新建一个  -> 在我到项目里面点击下载至本地 -> 解压。如果一切操作正常的话可以得到如下解压文件:
download + +选中红色区域的所有文件(这里面很多文件是不需要的,为了方便起见,我们全部复制即可,不需要的也不会被打包),复制到项目里面,一般放在目录 'static/icons' 或者 'assets/icons' 下面,如果没有的话可以新建目录,当然你也可以放到任意你喜欢的位置,只要引入的时候路径对即可,到此 iconfont 引入完毕。 + +PS: 本案文件目录为 '/static/icons'。 + +## 引入 G6 + +多种引入方式,请移步[快速上手](/zh/docs/manual/getting-started)。
PS: 本案例简单粗暴,通过 CDN 的方式引入。 + +```html + +``` + +## 添加字体图标 + +引入方式可自行选择,下面为在 HTML 中引入的例子: + +```html + +``` + +## 使用字体 + +```javascript +G6.registerNode('iconfont', { + draw(cfg, group) { + const { backgroundConfig: backgroundStyle, style, labelCfg: labelStyle } = cfg; + + if (backgroundStyle) { + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: cfg.size, + ...backgroundStyle, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'circle-shape', + }); + } + + const keyShape = group.addShape('text', { + attrs: { + x: 0, + y: 0, + fontFamily: 'iconfont', // 对应css里面的font-family: "iconfont"; + textAlign: 'center', + textBaseline: 'middle', + text: cfg.text, + fontSize: cfg.size, + ...style, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'text-shape1', + }); + const labelY = backgroundStyle ? cfg.size * 2 : cfg.size; + + group.addShape('text', { + attrs: { + x: 0, + y: labelY, + textAlign: 'center', + text: cfg.label, + ...labelStyle.style, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'text-shape1', + }); + return keyShape; + }, +}); + +const COLOR = '#40a9ff'; +const graph = new G6.TreeGraph({ + container: 'mountNode', + width: 800, + height: 600, + modes: { + default: ['collapse-expand', 'drag-canvas', 'drag-node'], + }, + defaultNode: { + backgroundConfig: { + backgroundType: 'circle', + fill: COLOR, + stroke: 'LightSkyBlue', + }, + type: 'iconfont', + size: 12, + style: { + fill: '#fff', + }, + labelCfg: { + style: { + fill: COLOR, + fontSize: 12, + }, + }, + }, + // 布局相关 + layout: { + type: 'compactBox', + direction: 'LR', + getId(d) { + return d.id; + }, + getHeight() { + return 16; + }, + getWidth() { + return 16; + }, + getVGap() { + return 20; + }, + getHGap() { + return 60; + }, + }, +}); + +graph.edge(({ target }) => { + const fill = target.get('model').backgroundConfig && target.get('model').backgroundConfig.fill; + return { + type: 'cubic-horizontal', + color: fill || COLOR, + label: target.get('model').relation || '', + labelCfg: { + style: { + fill: fill || COLOR, + fontSize: 12, + }, + }, + }; +}); + +const data = { + isRoot: true, + id: 'Root', + label: '可疑人员王**', + text: '\ue6b2', // 对应iconfont.css 里面的content,注意加u,后面的自行修改一下。 + style: { + fill: 'red', + }, + labelCfg: { + style: { + fill: 'red', + }, + }, + backgroundConfig: null, // 自定义项,用于判读是否需要圆背景 + size: 30, + children: [ + { + id: 'SubTreeNode1', + label: '**网咖', + text: '', + relation: '上网', + children: [ + { + id: 'SubTreeNode2', + label: '多伦多', + text: '', + }, + { + id: 'id1', + label: '小王', + text: '', + children: [ + { + id: 'SubTreeNode1.2.1', + label: '182****2123', + text: '', + }, + { + id: 'SubTreeNode4', + label: '今晚在吗', + text: '', + }, + ], + }, + ], + }, + { + id: 'SubTreeNode3', + label: 'subway', + text: '', + children: [ + { + id: 'SubTreeNode3.1', + label: '王五', + text: '', + }, + { + id: 'SubTreeNode3.2', + label: '张三', + text: '', + }, + ], + }, + { + id: 'SubTreeNode5', + label: '小花', + relation: '老婆', + text: '', + backgroundConfig: { + fill: 'Coral', + }, + style: { + fill: '#fff', + }, + labelCfg: { + style: { + fill: 'Coral', + }, + }, + children: [ + { + id: 'SubTreeNode1.2.1', + label: '182****2123', + text: '', + relation: '通话', + backgroundConfig: { + fill: 'Coral', + }, + style: { + fill: '#fff', + }, + labelCfg: { + style: { + fill: 'Coral', + }, + }, + }, + { + id: 'SubTreeNode3.3', + label: '凶器', + text: '', + relation: '指纹', + backgroundConfig: { + fill: 'Coral', + }, + style: { + fill: '#fff', + }, + labelCfg: { + style: { + fill: 'Coral', + }, + }, + }, + ], + }, + { + id: 'SubTreeNode6', + label: '马航37*', + relation: '乘坐', + text: '', + }, + ], +}; + +graph.data(data); +graph.render(); +``` + +## 注意事项 + +看了代码大家应该很清楚了,实质就是用了 text 图形,但有几个需要注意的地方:
**1、text 的 `fontFamily` 必须和 iconfont.css 里面的 `font-family` 保持一致:**
download + +download + +**2、data 里面的 `text` 使用的是 iconfont.css 里面的 `content`,注意加 `u` 。如有需要可自行复制。**
+ +download + +download + +**3、若出现了第一次渲染 iconfont 错误(可能显示成一个方框),可以检查下是否已经加载字体文件**
+ +如果页面中没有使用字体图标的节点,字体文件是不会自动下载的,那么可以添加一个隐藏节点用以触发加载。如果已经加载,那么可以尝试以下代码刷新渲染。 + +```javascript +// 在 graph.render() 之后调用以下语句: +setTimeout(() => { + graph.paint(); +}, 16); +``` + +## 工具函数 getIcon + +我们可以将 unicode 的转化封装成函数使用。这里注意,手动拼接 unicode 是不行的(`\\u${icon.unicode}`)。这里采用 iconfont.json 中的 `code_decimal` 进行转化。详细参考《[MDN String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint)》。 + +```javascript +import fonts from '../fonts/iconfont.json'; + +const icons = fonts.glyphs.map((icon) => { + return { + name: icon.name, + unicode: String.fromCodePoint(icon.unicode_decimal), // `\\u${icon.unicode}`, + }; +}); +const getIcon = (type: string) => { + const matchIcon = icons.find((icon) => { + return icon.name === type; + }) || { unicode: '', name: 'default' }; + return matchIcon.unicode; +}; +``` + +### 用法 + +```javascript + { + type: 'text', + attrs: { + id: 'node-icon', + x: 0, + y: 0, + fontSize: iconSize, + fill: primaryColor, + text: getIcon('logo'), // logo 为 unicode 对应的 name + fontFamily: 'iconfont', // 对应 CSS 里面的 font-family: "iconfont"; + textAlign: 'center', + textBaseline: 'middle', + }, + } +``` diff --git a/packages/site/docs/manual/advanced/mode-and-custom-behavior.en.md b/packages/site/docs/manual/advanced/mode-and-custom-behavior.en.md new file mode 100644 index 0000000000..bd3b0f6f72 --- /dev/null +++ b/packages/site/docs/manual/advanced/mode-and-custom-behavior.en.md @@ -0,0 +1,212 @@ +--- +title: Using multiple modes +order: 7 +--- + +In this chapter, we will introduce the interactions in G6 by adding nodes and edges. You nee to be familiar with the following before reading this chapter: + +- [Custom Behavior](/en/docs/manual/middle/states/custom-behavior); +- [Mode](/en/docs/manual/middle/states/mode). + +setmode + +
The final result in shown above. The complete code: Adding Items
There are three mode options in the drop-down menu on the upper left. + +- Switch to the default interactive mode when the "Default" button is selected: The dragged node will move with the mouse; The node will be selected by clicking; +- Switch to the addNode interactive mode when the "Add Node" button is selected: Add a node by clicking canvas; Select a node by clicking node; +- Switch to the addEdge interactive mode when the "Add Edge" button is selected: Add an edge by clicking the end nodes in order. + +**The reason for using multiple modes:**
The same mouse operation has different meanings in different scenarios. For example: + +- Canceling the selected state by clicking the canvas V.S. Adding new node on the clicked position on the canvas. Both these two requirements are binded to the event of clicking the canvas; +- Selecting a node by clicking it V.S. Adding an edge by clicking two end nodes. Both these two requirements are binded to the event of clicking the node. + +To distinguish the meanings of these operations, we utilize the interaction modes on a graph for different scenarios .
+ +## Prerequisite Code + +Here goes the basic HTML code for this chapter. We will add new codes incrementally to enable new functions. This prerequisite code defines the drop-down menu and the source `data`. + +```html + + + + + Interactively Add + + + + +
+ + + + +``` + +## Configure the Interaction Mode + +The following code instantiates the Graph, and configure the interaction `modes`, including `default` Mode, `addNode` Mode, and `addEdge` Mode. There are several interaction Behaviors inside each Mode, where `'drag-node'` and `'click-select'` are the built-in Behaviors of G6. `'click-add-node'` and `'click-add-edge'` are the custom Behavior to be defined. + +```javascript +// const data = ... +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + // The set of interaction Modes + modes: { + // Default mode + default: ['drag-node', 'click-select'], + // The Mode of adding nodes + addNode: ['click-add-node', 'click-select'], + // The Mode of adding edges + addEdge: ['click-add-edge', 'click-select'], + }, + // The node styles in different states + nodeStateStyles: { + // The node styles in selected state, corresponds to the built-in click-select behavior + selected: { + stroke: '#666', + lineWidth: 2, + fill: 'steelblue' + } +}); + +graph.data(data); +graph.render(); + +// Listen to the change of the drop-down menu to swith the interaction Mode +document.getElementById('selector').addEventListener('change', e => { + const value = e.target.value; + // Switch the interaction Mode + graph.setMode(value); +}); +``` + +#### Add a Node + +When user select the 'Add Node' button in the menu, the Mode will be switched to the addNode, which includes two Behaviors: `'click-add-node'` and `'click-select'`. The `'click-add-node'` is registered by `G6.registerBehavior`. P.S. the name of `'click-add-node'` can be assigned to any one you like. + +```javascript +// The count of the added nodes, it will be used to generate unique id for added node +let addedNodeCount = 0; +// Register the custom Behavior of adding a node by clicking +G6.registerBehavior('click-add-node', { + // Bind the events and response functions for this custom Behavior + getEvents() { + return { + 'canvas:click': 'onClick', // The event to be listned is canvas:click. The response function is onClick + }; + }, + // The click event + onClick(ev) { + const graph = this.graph; + // Add a new node on the canvas + const node = this.graph.addItem('node', { + x: ev.canvasX, + y: ev.canvasY, + id: `node-${addedNodeCount}`, // Generate a unique id + }); + addedNodeCount++; + }, +}); +``` + +#### Add a Node + +To add an edge between two end nodes, the users need to switch to the `addEdge` Mode, which includes two behaviors: `'click-add-edge'` and `'click-select'`. The `'click-add-edge'` is registered by `G6.registerBehavior`. P.S. the name of `'click-add-edge'` can be assigned to any one you like. + +```javascript +// Register the custom Behavior of adding a edge by clicking +G6.registerBehavior('click-add-edge', { + // Bind the events and response functions for this custom Behavior + getEvents() { + return { + 'node:click': 'onClick', // The event to be listned is node:click. The response function is onClick + mousemove: 'onMousemove', // The event to be listned is mousemove. The response function is onMousemove + 'edge:click': 'onEdgeClick', // The event to be listned is edge:click. The response function is onEdgeClick + }; + }, + // The response function for 'node:click' defined in getEvents + onClick(ev) { + const node = ev.item; + const graph = this.graph; + // The position of the node where the mouse is currently clicking on + const point = { x: ev.x, y: ev.y }; + const model = node.getModel(); + if (this.addingEdge && this.edge) { + graph.updateItem(this.edge, { + target: model.id, + }); + + this.edge = null; + this.addingEdge = false; + } else { + // Add a new edge to the graph with the currently clicked node's position as the end point + this.edge = graph.addItem('edge', { + source: model.id, + target: point, + }); + this.addingEdge = true; + } + }, + // The response function for mousemove defined in getEvents + onMousemove(ev) { + // The current position of the mouse + const point = { x: ev.x, y: ev.y }; + if (this.addingEdge && this.edge) { + // Update the end point of the edge to be the current position of the mouse + this.graph.updateItem(this.edge, { + target: point, + }); + } + }, + // The response function for 'edge:click' defined in getEvents + onEdgeClick(ev) { + const currentEdge = ev.item; + // The click event while dragging + if (this.addingEdge && this.edge == currentEdge) { + graph.removeItem(this.edge); + this.edge = null; + this.addingEdge = false; + } + }, +}); +``` + +## Complete COde + +Adding Items. diff --git a/packages/site/docs/manual/advanced/mode-and-custom-behavior.zh.md b/packages/site/docs/manual/advanced/mode-and-custom-behavior.zh.md new file mode 100644 index 0000000000..084c564ebf --- /dev/null +++ b/packages/site/docs/manual/advanced/mode-and-custom-behavior.zh.md @@ -0,0 +1,214 @@ +--- +title: 使用多种交互模式 +order: 7 +--- + +本章以添加节点及在两个节点之间连线为例进行介绍 G6 中的交互。在阅读本章之前,需要先熟悉以下内容: + +- [自定义交互行为 Behavior](/zh/docs/manual/middle/states/custom-behavior); +- [交互模式 Mode](/zh/docs/manual/middle/states/mode)。 + +setmode + +
上图是本文要实现的最终效果。完整 demo 代码参见:动态添加元素
左上方的下拉菜单中有三个选项,用于切换交互模式 mode: + +- 选择 “Default” 按钮时,切换到 default 交互模式:拖拽节点时节点跟随鼠标移动;点击节点时选中该节点; +- 选择 “Add Node” 按钮时,切换到  addNode 交互模式:点击空白区域在点击处增加一个节点;点击节点时选中该节点; +- 选择 “Add Edge” 按钮时,切换到 addEdge 交互模式:依次点击两个节点将会在这两个节点之间添加一条边。 + +**使用多个 mode 的原因**
  相同的鼠标操作,在不同场景下有不同的含义。例如: + +- 点击空白画布取消目前图上所有节点的选中状态、点击空白画布在响应位置添加节点,这两种需求都对应了用户点击画布空白处的操作; +- 点击选中、点击两个节点添加边都涉及到了鼠标在节点上的点击操作。 + +为了区分这些操作的含义,我们使用交互模式 mode 划分不同的场景。
+ +## 前提代码 + +下面 HTML 代码是本文的基础代码,后续功能将在这份代码中增量添加。下面代码定义了左上方的下拉菜单,以及后面将会用到图上的初始数据 `data`。 + +```html + + + + + Interactively Add + + + + +
+ + + + +``` + +## 配置交互模式 + +下面代码实例化了图,并配置了交互模式的集合 `modes`,其中包括 `default` 默认交互模式、`addNode` 增加节点交互模式、`addEdge` 增加边交互模式。每种交互模式中都包含了各自的交互行为,其中  `'drag-node'`(拖拽节点) 和  `'click-select'`(点击选中) 是 G6 内置的交互行为,`'click-add-node'`(点击空白画布添加节点) 和  `'click-add-edge'`(点击两个节点添加边) 需要我们在后面进行自定义。 + +```javascript +// const data = ... +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + // 交互模式集合 + modes: { + // 默认交互模式 + default: ['drag-node', 'click-select'], + // 增加节点交互模式 + addNode: ['click-add-node', 'click-select'], + // 增加边交互模式 + addEdge: ['click-add-edge', 'click-select'], + }, + // 节点在不同状态下的样式集合 + nodeStateStyles: { + // 节点在 selected 状态下的样式,对应内置的 click-select 行为 + selected: { + stroke: '#666', + lineWidth: 2, + fill: 'steelblue' + } +}); + +graph.data(data); +graph.render(); + +// 监听左上角下拉菜单的变化,根据其变化切换图的交互模式 +document.getElementById('selector').addEventListener('change', e => { + const value = e.target.value; + // 切换交互模式 + graph.setMode(value); +}); +``` + +#### 添加节点 + +在上面的例子中,当选中添加节点按钮时,会切换到 `addNode` 的 Mode 上。`addNode` Mode 包含了 `'click-add-node'`, `'click-select'` 两个 Behavior。`'click-add-node'` 实现了在点击空白画布时,在点击位置添加节点。这是通过使用 `G6.registerBehavior` 自定义一个名为 `'click-add-node'`(名字可以自由设定) 的 Behavior 实现的。 + +```javascript +// 添加的节点数量,用于生成唯一 id +let addedNodeCount = 0; + +// 封装点击添加节点的交互 +G6.registerBehavior('click-add-node', { + // 设定该自定义行为需要监听的事件及其响应函数 + getEvents() { + // 监听的事件为 canvas:click,响应函数是 onClick + return { + 'canvas:click': 'onClick', + }; + }, + // 点击事件 + onClick(ev) { + const graph = this.graph; + // 在图上新增一个节点 + const node = this.graph.addItem('node', { + x: ev.canvasX, + y: ev.canvasY, + id: `node-${addedNodeCount}`, // 生成唯一的 id + }); + addedNodeCount++; + }, +}); +``` + +#### 添加边 + +在上面的例子中,当需要在两个节点之间连线时,要先切换到添加边的 Mode 上。下面代码自定义了名为  `'click-add-edge'`(名字可以自由设定)的 Behavior 实现两个节点之间连线。 + +```javascript +// 封装点击添加边的交互 +G6.registerBehavior('click-add-edge', { + // 设定该自定义行为需要监听的事件及其响应函数 + getEvents() { + return { + 'node:click': 'onClick', // 监听事件 node:click,响应函数是 onClick + mousemove: 'onMousemove', // 监听事件 mousemove,响应函数是 onMousemove + 'edge:click': 'onEdgeClick', // 监听事件 edge:click,响应函数是 onEdgeClick + }; + }, + // getEvents 中定义的 'node:click' 的响应函数 + onClick(ev) { + const node = ev.item; + const graph = this.graph; + // 鼠标当前点击的节点的位置 + const point = { x: ev.x, y: ev.y }; + const model = node.getModel(); + if (this.addingEdge && this.edge) { + graph.updateItem(this.edge, { + target: model.id, + }); + + this.edge = null; + this.addingEdge = false; + } else { + // 在图上新增一条边,结束点是鼠标当前点击的节点的位置 + this.edge = graph.addItem('edge', { + source: model.id, + target: point, + }); + this.addingEdge = true; + } + }, + // getEvents 中定义的 mousemove 的响应函数 + onMousemove(ev) { + // 鼠标的当前位置 + const point = { x: ev.x, y: ev.y }; + if (this.addingEdge && this.edge) { + // 更新边的结束点位置为当前鼠标位置 + this.graph.updateItem(this.edge, { + target: point, + }); + } + }, + // getEvents 中定义的 'edge:click' 的响应函数 + onEdgeClick(ev) { + const currentEdge = ev.item; + // 拖拽过程中,点击会点击到新增的边上 + if (this.addingEdge && this.edge == currentEdge) { + graph.removeItem(this.edge); + this.edge = null; + this.addingEdge = false; + } + }, +}); +``` + +## 完整代码 + +完整 demo 代码参见:动态添加元素。 diff --git a/packages/site/docs/manual/advanced/state-new.en.md b/packages/site/docs/manual/advanced/state-new.en.md new file mode 100644 index 0000000000..9b498f43ef --- /dev/null +++ b/packages/site/docs/manual/advanced/state-new.en.md @@ -0,0 +1,266 @@ +--- +title: Take Use of State Mechanism +order: 5 +--- + +⚠️ Attention: State with multiple values, mutually exclusive state, updating styles for sub shapes are supported after V3.4. + +### Background + +State of item (Node/Edge) build the fast relationships between 「interactions/data changes」 and 「changes of item styles」. + +e.g.: the 'hover' state of a node is activated when the mouse enters the node, and the style of the node is changed to response the interaction; 'hover' state is inactivated when the mouse leave the node, and the style of the node is resumed. + +In actual scene, state has lots of implicity recommand and complexity. + +### Challenges + +- **Configure the target state quickly**: Assign a new state and clear all the existing states on a node; +- **State with multiple values**: e.g. the 'bodyState' of a node representing a person has four values 'healthy', 'suspect', 'ill', and 'dead'; +- **Mutually exclusive state**: e.g. 'healthy', 'suspect', 'ill', and 'dead' for 'bodyState' are mutually exclusive to each other, any two of them will not exist on a person in the same time; +- **Update the styles for all the sub shapes on a node or an edge**: e.g. a node consist of a rect, a text and a icon image. When the state of the node is chagned, styles of all the shapes can be changed to response it; Modify the state configurations: modify the style configurations for a state easily. + +### Solution + +To address the issues above, we have the following functions for states in G6 3.4: + +- Define a state with unified method; +- Set state value with `setItemState` function; +- Update state value with `updateItem` function; +- Cancel state with `clearItemStates` function. + +### Define a State + +#### Global State + +The global state in G6 is defined by `nodeStateStyles` and `edgeStateStyles` on the graph instance. + +```javascript +const graph = new G6.Graph({ + container, + width, + height, + nodeStateStyles: { + hover: { + fill: 'red', + 'keyShape-name': { + fill: 'red', + }, + }, + }, + edgeStateStyles: {}, +}); +``` + +The state style of [keyShape](/en/docs/manual/middle/elements/shape/shape-keyshape/#keyshape) can be defined in `nodeStateStyles` or `edgeStateStyles` directly. You can also define the styles in the object with the key equals to the `name` of the keyShape. + +#### State for Single Node/Edge + +Expect set the global styles for items(nodes/edges), you can also define different styles for different items by assgin `stateStyles` in `graph.node(fn)` / `graph.edge(fn)` function. + +```javascript +graph.node((node) => { + return { + ...node, + stateStyles: {}, + }; +}); + +const data = { + nodes: [ + { + id: 'node', + stateStyles: {}, + }, + ], +}; +``` + +#### State Styles for Sub-shapes + +On the ascpect of drawing, an item(node/edge) has a graphics group, which contains a keyShape and several sub-shapes. Before V3.4, state styles are only available on keyShape, which means users need to define state styles for other sub shapes in `setState` function when custom a node or an item type. + +G6 3.4 supports state styles for sub shapes. They can also be defined by two ways as [Global State](/#global-state) and State for [Single Node/Edge](/#state-for-single-nodeedge). Now we show how to define the global state styles for sub shapes as an example. + +```javascript +const graph = new G6.Graph({ + container, + width, + height, + nodeStateStyles: { + selected: { + 'sub-element': { + fill: 'green', + }, + 'text-element': { + stroke: 'red', + }, + }, + }, + edgeStateStyles: {}, +}); +``` + +In [Global State](/#global-state), we recommand define the keyShape's state styles by an object with key equals to the `name` of the keyShape. Similary, you can define the state styles for any sub shape with an object with key equals to its `name`. + +As the shown in the above code, we define the state styles for two sub shapes with `name`s `'sub-element'` and `'text-element'` respectively. When we set the state for an item by calling `graph.setItemState(item, 'selected', true)`, the styles of the sub shapes named `'sub-element'` and `'text-element'` will be updated as well. + +```javascript +// Calling the following code, the styles of sub-element and text-element will be changed +graph.setItemState(item, 'selected', true); +``` + +Besides, G6 also supports `updateItem` function to update the state styles for an item. + + +⚠️ NOTICE: + +The state styles for sub-shapes are only available for the sub-shapes which are the chilren of the root graphics group of a node/edge, but not other descendant shapes grouped by nested sub-graphics-groups. The sub-shapes in the built-in nodes/edges are all the children of the root graphics group of a node/edge. If you are customizing a node/edge type, this rule should be noticed. + +### Set State + +G6 V3.4 supports state with multiple values and binary values: + +> Binary: the value can be `true` or `false`, means the state is activated or inactivated respectivly; Multiple value: e.g. a node represents a person with 'bodyState', which has four values: 'healthy', 'suspect', 'ill', 'dead'. + +#### Binary State + +Binary state is commonly used in interactions, e.g. hover, selected, etc. When a node is selected, the selected state is activated with true value; the selected state is inactivated with false when the node is deselected. + +Set the binary state by calling `graph.setItemState(item, 'selected', true)`. + +```javascript +const graph = new G6.Graph({ + //... + nodeStateStyles: { + selected: { + fill: 'red', + }, + }, +}); + +graph.setItemState(item, 'selected', true); +``` + +#### State with Multiple Values + +State with multiple values exists in complex actual cases, e.g. the 'bodyState' of a node representing a person has four values 'healthy', 'suspect', 'ill', and 'dead'. The binary state can not satisfy such situation. + +```javascript +const graph = new Graph({ + // ... Other configurations + // The state styles in different states + nodeStateStyles: { + // bodyState with multiple values and matually exclusive + 'bodyState:healthy': { + // the state styles for the keyShape + fill: 'green', + }, + 'bodyState:suspect': {}, + 'bodyState:ill': {}, + }, +}); + +graph.setItemState(item, 'bodyState', 'healthy'); +``` + +#### Matually Exclusive State + +State with multiple values also solves the matually exclusive problem. We now use the same example as above, `bodyState` has four values: `healthy`, `suspect`, `ill`, `dead`. + +```javascript +// Matually exclusive state +graph.setItemState(item, 'bodyState', 'healthy'); +// Call the following code, the value of bodyState will be changed to dead, +// and item.hasState('bodyState:healthy') will return false +graph.setItemState(item, 'bodyState', 'dead'); +``` + +After calling the code above, the value of the item's `bodyState` is `dead`. + +### Update the Configurations for State Styles + +Before V3.3, G6 does not support modification on the configurations for state styles. And `updateItem` can only be used to update the default style for keyShape. With V3.4, `updateItem` supports updating the default styles and state styles for keyShape and other sub shapes. + +#### Update Default Styles + +You can update the default styles for keyShape and sub shapes by assigning the object with the key equals the `name` of the sub shape in `style` of the `updateItem`'s second parameter. + +```javascript +// Update item with the default style of keyShape and other sub shapes +graph.updateItem(item, { + style: { + // for keyShape's fill, stroke, and opacity + fill: 'green', + stroke: 'green', + opacity: 0.5, + // the styles for the sub shape named 'node-text' + 'node-text': { + stroke: 'yellow', + }, + }, +}); +``` + +#### Update State Styles + +`updateItem` also can be used to update the state styles for keyShape and sub shapes with `stateStyles`. + +```javascript +graph.updateItem(item, { + style: { + stroke: 'green', + 'node-text': { + stroke: 'yellow', + }, + }, + stateStyles: { + hover: { + opacity: 0.1, + 'node-text': { + stroke: 'blue', + }, + }, + }, +}); +graph.setItemState(item, 'hover', true); +``` + +There might be two situations when calling `updateItem`: + +- The state style to be updated is already activated on an item, `item.hasState('hover') === true`: the style will be changed immediately after calling `updateItem`; +- The state style to be updated is not active on an item, `item.hasState('hover') === false`: The style will be changed after calling `graph.setItemState(item, 'hover', true)`. + +### Cancel States + +`graph.clearItemStates` can be used to cancel one or more states set by `graph.setItemState`. + +```javascript +graph.setItemState(item, 'bodyState', 'healthy'); +graph.setItemState(item, 'selected', true); +graph.setItemState(item, 'active', true); + +// Cancel a single state +graph.clearItemStates(item, 'selected'); +graph.clearItemStates(item, ['selected']); + +// Cancel multiple states +graph.clearItemStates(item, ['bodyState:healthy', 'selected', 'active']); +``` + +### State Priority + +G6 does not explicitly provide the state priority mechanism. But the `hasState` function which is used to get the value of a state, helps users to control the priority by themselves. e.g.: + +```javascript +// Activate the 'active' state of the item to be true +graph.setItemState(item, 'active', true); + +// returns the value of 'active' state +const hasActived = item.hasState('active'); + +// If the value of 'active' state is false, the 'hover' state can be set to true +if (!hasActived) { + graph.setItemState(item, 'hover', true); +} +``` diff --git a/packages/site/docs/manual/advanced/state-new.zh.md b/packages/site/docs/manual/advanced/state-new.zh.md new file mode 100644 index 0000000000..77acd804ae --- /dev/null +++ b/packages/site/docs/manual/advanced/state-new.zh.md @@ -0,0 +1,274 @@ +--- +title: G6 状态管理的最佳实践 +order: 5 +--- + +⚠️ 注意: 多值状态、互斥状态、设置子图形状态样式、修改状态样式配置在 V3.4 后支持。 + +### 背景 + +元素(节点/边)的状态(state)用于反馈用户交互、数据变化。通过状态,可以将「交互/数据变化」与视图中「元素的样式变化」快速关联。最常见的例子:鼠标进入节点,该节点为 hover 状态,并被高亮;离开节点,该节点为非 hover 状态,并复原样式。 + +乍看之下,状态似乎很简单,但从目前业务痛点与经验来看,“状态”有许多隐藏的需求和复杂性。简单的实现方式,会造成易用性低下的问题。 + +### 挑战 + +要解决业务中常见的状态问题,并提供清晰明了、简单易用的方案必须要考虑以下问题: + +- **简单快速地设置目标状态**:当一个节点或一条边上已经设置了大量不同状态后,要再设置一个新状态时,能够快速将之前所有状态清除; +- **状态多值**,即一个状态量存在多个不同的值,如节点代表人,有“健康”、“疑似”、“确诊”、“死亡”四种状态; +- **状态间互斥**:“健康”、“疑似”、“确诊”、“死亡”四种状态中,“死亡”与其他三种就是互斥的,不可能同时存在“健康”和“死亡”两种状态; +- **节点或边中所有元素的状态更新**:如一个由文本和圆组成的节点,状态变化时不仅能设置圆的样式,也可以设置文本的样式; +- **更新状态样式**,当更新节点的样式时,能够同时更新状态的样式。 + +### 方案 + +为了解决以上问题,我们将 G6 的状态管理分为以下几层: + +- 定义状态:统一的定义方式; +- 设置状态:`setItemState` 方法; +- 更新状态:`updateItem` 支持更新状态; +- 取消状态:`clearItemStates` 方法。 + +### 定义状态 + +#### 全局状态 + +G6 中定义全局状态是在实例化 Graph 时通过 `nodeStateStyles` 和 `edgeStateStyles` 来定义。 + +```javascript +const graph = new G6.Graph({ + container, + width, + height, + nodeStateStyles: { + hover: { + fill: 'red', + 'keyShape-name': { + fill: 'red', + }, + }, + }, + edgeStateStyles: {}, +}); +``` + +当设置 [keyShape](/zh/docs/manual/middle/elements/shape/shape-keyshape/#keyshape) 的状态时,可以直接在 `nodeStateStyles` 或 `edgeStateStyles` 中定义样式,或者将 keyShape 的 `name` 属性值作为 key 定义样式,我们建议使用后者。 + +#### 节点/边状态 + +除过全局状态外,G6 也支持针对不同节点定义不同的状态,使用 `graph.node(fn)` / `graph.edge(fn)` 或在数据中设置 `stateStyles` 即可。 + +```javascript +graph.node((node) => { + return { + ...node, + stateStyles: {}, + }; +}); + +const data = { + nodes: [ + { + id: 'node', + stateStyles: {}, + }, + ], +}; +``` + +#### 子图形状态样式 + +在 G6 中,通常一个节点在绘制层面上,有一个图形分组,该图形分组包含多个图形组成的。G6 3.3 之前的版本,仅支持设置 keyShape 的状态,如果要设置其他部分元素的状态,就需要在自定义元素时复写 `setState` 方法,这意味着状态样式的管理完全需要自定义,用户写起来将会非常复杂。G6 3.4 版本中,我们支持了定义子元素的状态,从此再也不用去复写 `setState` 方法了。 + +子元素状态也支持两种设置方式,为了演示方便,在这里我们只演示在在全局状态中定义子元素状态。 + +```javascript +const graph = new G6.Graph({ + container, + width, + height, + nodeStateStyles: { + selected: { + 'sub-element': { + fill: 'green', + }, + 'text-element': { + stroke: 'red', + }, + }, + }, + edgeStateStyles: {}, +}); +``` + +在「定义状态」部分,我们建议在 `nodeStateStyles`/`edgeStateStyles` 中定义状态时,使用 keyShape 的 `name` 属性作为 key 值来定义状态样式,因为我们在定义子元素的状态时也采用同样的方式,结构上比较统一。 + +如上示例代码所示,我们定义了节点中 `name` 属性值为 `'sub-element'` 和 `'text-element'` 两部分的样式,当我们通过 `graph.setItemState(item, 'selected', true)` 设置指定 item 的状态时,子元素 `'sub-element'` 和 `'text-element'` 的样式也会同步更新。 + +```javascript +// 执行下面语句以后,name 为 sub-element 和 name 为 text-element +// 的元素填充色和描边色都会改变 +graph.setItemState(item, 'selected', true); +``` + +另外,G6 也支持在使用 `updateItem` 更新节点或边的时候定义状态。 + +⚠️ 注意: + +子图形状态样式仅限于指定节点/边的图形分组下平铺的图形,不支持嵌套图形分组下的图形。内置节点/边的图形分组内的图形均为平铺,在自定义节点时需要注意该规则。 + +### 设置状态 + +G6 中状态支持多值和二值两种情况。 + +> 二值:值只能为 `true` / `false`,无其他选择,即要么有这个状态,要么没有;多值:如节点代表人,有“健康”、“疑似”、“确诊”、“死亡”四种状态。 + +#### 二值状态 + +二值状态一般常用于交互过程中,如 hover、selected 等状态,当节点被选中时,节点应用 selected 状态的样式,取消选中时,去掉 selected 状态的样式。 + +在 G6 中,使用 `graph.setItemState(item, 'selected', true)` 来设置二值状态。 + +```javascript +const graph = new G6.Graph({ + //... + nodeStateStyles: { + selected: { + fill: 'red', + }, + }, +}); + +graph.setItemState(item, 'selected', true); +``` + +#### 多值状态 + +除过像 hover、selected 这种交互状态外,还会存在很多的业务状态,如节点代表人,有“健康”、“疑似”、“确诊”、“死亡”四种状态,此时使用 `true`/`false` 的形式就不能满足,需要支持单个状态可以有多个值的情况。 + +```javascript +const graph = new Graph({ + // ... 其他配置 + // 节点在不同状态下的样式 + nodeStateStyles: { + // 实现 bodyState 的【多值】【互斥】 + 'bodyState:healthy': { + // keyShape 该状态下的样式, 可以使用三种方式指定: + fill: 'green', + }, + 'bodyState:suspect': {}, + 'bodyState:ill': {}, + }, +}); + +graph.setItemState(item, 'bodyState', 'healthy'); +``` + +#### 互斥状态 + +上面的多值状态,也很好地解决了状态互斥的问题,还以上面的 `bodyState` 状态为例,该状态共有 `healthy`、`dead`、`ill` 等值。 + +```javascript +//【互斥】 +graph.setItemState(item, 'bodyState', 'healthy'); +// 执行下面这句话 bodyState 将会被改变成 dead, +// item.hasState('bodyState:healthy') 为 false +graph.setItemState(item, 'bodyState', 'dead'); +``` + +执行上面的两句后, item 只会有 `bodyState` 状态的 `dead` 一个值,而二值状态不能解决这个问题。 + +```javascript +graph.setItemState(item, 'select', true); +graph.setItemState(item, 'active', true); +``` + +执行上面的两句设置二值状态的语句后,item 具有 select 和 active 所有的属性值,不能满足互斥需求。 + +### 修改状态样式配置 + +G6 3.3 及 以下的版本中,不支持修改状态样式的配置。`updateItem` 方法只能更新 keyShape 的默认样式。从 G6 3.4 版本开始,`updateItem` 支持更新 item 中所有子图形的默认样式和状态样式。 + +#### 更新默认样式 + +`updateItem` 可用于更新 keyShape 以及其他子图形的默认样式。使用 `updateItem` 更新子图形样式时,只需要在 `style` 中以子图形的 `name` 属性作为为 key 即可。 + +```javascript +// 更新 item,除过更新 keyShape 外,还更新 name 值为 node-text 的图形 +graph.updateItem(item, { + style: { + fill: 'green', + stroke: 'green', + opacity: 0.5, + 'node-text': { + stroke: 'yellow', + }, + }, +}); +``` + +#### 更新状态样式 + +`updateItem` 也支持更新 keyShape 与其他子图形的状态样式,使用 `stateStyles` 属性。 + +```javascript +graph.updateItem(item, { + style: { + stroke: 'green', + 'node-text': { + stroke: 'yellow', + }, + }, + stateStyles: { + hover: { + opacity: 0.1, + 'node-text': { + stroke: 'blue', + }, + }, + }, +}); +graph.setItemState(item, 'hover', true); +``` + +使用 `updateItem` 更新状态的样式时,会存在两种情况: + +- 使用 `updateItem` 更新时,item 已有指定状态,即 `item.hasState('hover') === true`,此时状态值对应的属性会立即生效; +- 使用 `updateItem` 更新时,item 没有指定的状态,即 `item.hasState('hover') === false`,更新以后,当用户执行  `graph.setItemState(item, 'hover', true)` 后,hover 状态的 `stroke` 属性值为 `updateItem` 时设置的值。 + +### 取消状态 + +在 G6 中,我们建议使用 `graph.clearItemStates`  来取消 `graph.setItemState` 设置的状态,`graph.clearItemStates` 支持一次取消单个或多个状态。 + +```javascript +graph.setItemState(item, 'bodyState', 'healthy'); +graph.setItemState(item, 'selected', true); +graph.setItemState(item, 'active', true); + +// 取消单个状态 +graph.clearItemStates(item, 'selected'); +graph.clearItemStates(item, ['selected']); + +// 取消多个状态 +graph.clearItemStates(item, ['bodyState:healthy', 'selected', 'active']); +``` + +以上就是 G6 中状态的定义、设置和取消的全过程,很清晰明了,但总感觉缺少了点什么,没错,想必聪明的你已经发现了,缺少了更新子元素及和 `updateItem` 配合使用的方案。不要着急,接着放下看。 + +### 状态优先级 + +G6 中提供了 `hasState` 方法用于判断元素是否有某种状态。但具体哪个状态的优先级高,哪个状态值应该覆盖其他的类似问题我们就没有再做任何限制,完全由业务用户控制,实现这种控制也非常简单,如一般情况下,鼠标 hover 到某个节点后,该节点会高亮,但希望当该节点处于 active 状态时,鼠标 hover 上去后也不要覆盖 active 的状态,即 active 优先级高于 hover。 + +```javascript +// 设置节点处于 active 状态 +graph.setItemState(item, 'active', true); + +// 鼠标 hover +const hasActived = item.hasState('active'); + +// 当节点没有 active 时才设置 hover 状态 +if (!hasActived) { + graph.setItemState(item, 'hover', true); +} +``` diff --git a/packages/site/docs/manual/cases/edgeBundling.en.md b/packages/site/docs/manual/cases/edgeBundling.en.md new file mode 100644 index 0000000000..7f9fe5557c --- /dev/null +++ b/packages/site/docs/manual/cases/edgeBundling.en.md @@ -0,0 +1,331 @@ +--- +title: Edge Bundling +order: 2 +--- + +## Background + +Most graphs are visualized as node-link diagram, which is appropriate for traffic network with geographical information on nodes, e.g. migration graph and ariline network.
img img + +> (Left) Figure 1. The airlines of France. (Right) Figure 2. The airlines of United States. + + + + +> (Left) Figure 3. The world IXP peering network. (Right) Figure 4. The American immigration network. + +## Problem + +Though the node-link diagram is intuitive, the severe visual clutter problem still exists when the graph has large amount of data. The visual clutter of the node-link diagram mostly moes from the edge crossings and congestion. As shown in Figure 1~4, in the traffic networks, the positions of the node often have well-defined geographical meanings, which means the node positions are usually non-editable for reducing the visual clutter. Lots of research works focus on the methods to improve the visual clustter on edges, where the Edge Bundling is a widely used way to achieve it. The researches about edge bundlings are summarized 「HERE」. + +Here goes a example with complicated American flights data, where the nodes represent the cities with latitute and longitute; the edges represent the flights: + +```json +{ + "nodes": [ + { + "x": -922.24444, + "y": 347.29444, + "id": "0", + "lon": -92.224444, + "lat": 34.729444 + }, + { + "x": -922.24444, + "y": 347.29444, + "id": "1", + "lon": -92.224444, + "lat": 34.729444 + } + // ... Other nodes + ], + "edges": [ + { + "source": "0", + "target": "21", + "id": "e0" + }, + { + "source": "2", + "target": "13", + "id": "e1" + } + // ... Other edges + ] +} +``` + +Render the nodes and edges by G6 directly, we will obtain the result:
img + +> Figure 5. Render the source data by G6. + +Figure 5 shows the result with chaotic crossings which is hard for users to figure out the details and global trends. + +## Expected Effect + +We wish to improve the visual clutter of Figure 5 by edge bundling to show the global trends and structures and highlight the important cities with many flights. These cities might be the important traffic pivots. We also try to illustrate some statistical informations for analysis. Powered by G6, we are able to achive the result with: Bundling the edges, Mapping the edge directions to gradient colors(departure-orange, arrival-cyan) of the edge; Mapping the total number of flights about the cities to the size of the node; Adding interactions of hover; Utilizing the tooltip to show the longitute and latitute. + +
+img + +> The expected effect and the tooltip. + +## Implement Steps + +### Statistics Information + +First, we count the total degrees of each node based on the data by simple JavaScript code. The `degree` of a node indicates the total number of the flights arriving and leaving the city; The `outDegree` indicates the leaving flights; The `inDegree` indicates the arriving flights. + +```javascript +const nodes = data.nodes; +const edges = data.edges; +nodes.forEach((n) => { + n.y = -n.y; + n.degree = 0; + n.inDegree = 0; + n.outDegree = 0; +}); +// Compute the degree of each node +const nodeIdMap = new Map(); +nodes.forEach((node) => { + nodeIdMap.set(node.id, node); +}); +edges.forEach((e) => { + const source = nodeIdMap.get(e.source); + const target = nodeIdMap.get(e.target); + source.outDegree++; + target.inDegree++; + source.degree++; + target.degree++; +}); +let maxDegree = -9999, + minDegree = 9999; +nodes.forEach((n) => { + if (maxDegree < n.degree) maxDegree = n.degree; + if (minDegree > n.degree) minDegree = n.degree; +}); +const sizeRange = [1, 20]; +const degreeDataRange = [minDegree, maxDegree]; +// The range of the degree is degreeDataRange, now we map it onto sizeRange and write the 'size' into node data. +scaleNodeProp(nodes, 'size', 'degree', degreeDataRange, sizeRange); +``` + +`scaleNodeProp()` maps the node property `refPropName` to another property `propName` with the range `outRange`: + +```javascript +/** + * Mapping properties + * @param {array} nodes The array of nodes + * @param {string} propName The name of the property to be writed + * @param {string} refPropName The name of the property to be normalized + * @param {array} dataRange The range of the property to be normalized, [min, max] + * @param {array} outRange The arange of the property to be writed, [min, max] + */ +function scaleNodeProp(nodes, propName, refPropName, dataRange, outRange) { + const outLength = outRange[1] - outRange[0]; + const dataLength = dataRange[1] - dataRange[0]; + nodes.forEach((n) => { + n[propName] = ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0]; + }); +} +``` + +Now, we have normalized the degrees onto the `size`s of nodes. + +### Instantiate the Bundling Plugin + +The edge bunlding technique in G6 is implemented according to the paper FEDB (Force-Directed Edge Bundling for Graph Visualization). By tuning the configurations, you can adjust the bundling result easily. + +```javascript +const edgeBundling = new Bundling({ + bundleThreshold: 0.6, // The tolerance of bundling. Lower number, the higher similarity of the bundled edges is required, the smaller number of edges to be bundled together. + K: 100, // The strength of the bundling +}); +``` + +### Custom Pie Node + +In the first step, we have mapped the degrees of nodes onto their size. To demonstrate the ratio of leaving and arriving flights, we design a pie-chart node for each city. For example, img, the orange fan represents the number of arriving flights, and the cyan fan represents the number of leaving flights. The built-in nodes in G6 do not meet such requirement. Thus, we now register a custom node by the custom mechanism of G6: + +```javascript +const lightBlue = 'rgb(119, 243, 252)'; +const lightOrange = 'rgb(230, 100, 64)'; + +// Register a type of custom node named pie-node +G6.registerNode( + 'pie-node', + { + drawShape: (cfg, group) => { + const radius = cfg.size / 2; // The radius the of node + const inPercentage = cfg.inDegree / cfg.degree; // The percentage of the inDegree + const inAngle = inPercentage * Math.PI * 2; // The angle of the fan of inDegree + const outAngle = Math.PI * 2 - inAngle; // The angle of the fan of outDegree + const inArcEnd = [radius * Math.cos(inAngle), radius * Math.sin(inAngle)]; // The end point of the inDegree fan + let isInBigArc = 1, + isOutBigArc = 0; + if (inAngle > Math.PI) { + isInBigArc = 0; + isOutBigArc = 1; + } + // The inDegree fan + const fanIn = group.addShape('path', { + attrs: { + path: [ + ['M', radius, 0], + ['A', radius, radius, 0, isInBigArc, 0, inArcEnd[0], inArcEnd[1]], + ['L', 0, 0], + ['B'], + ], + lineWidth: 0, + fill: lightOrange, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'in-fan-shape', + }); + // The outDegree fan + const fanOut = group.addShape('path', { + attrs: { + path: [ + ['M', inArcEnd[0], inArcEnd[1]], + ['A', radius, radius, 0, isOutBigArc, 0, radius, 0], + ['L', 0, 0], + ['B'], + ], + lineWidth: 0, + fill: lightBlue, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'out-fan-shape', + }); + // return the keyshape + return fanIn; + }, + }, + 'single-node', +); +``` + +The code above registers a 'pie-node' type node. + +### Instantiate the Graph + +Now, we are going to register a graph and assign the Edge Bundling plugin, node type ('pie-node'), and item styles for it. + +```javascript +const edgeBundling = new Bundling({ + bundleThreshold: 0.6, // The tolerance of bundling. Lower number, the higher similarity of the bundled edges is required, the smaller number of edges to be bundled together. + K: 100, // The strength of the bundling +}); +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 800, + plugins: [edgeBundling], // Add the plugin + fitView: true, + defaultNode: { + size: 3, + color: 'steelblue', + fill: 'steelblue', + }, + nodeStyle: { + default: { + lineWidth: 0, + fill: 'steelblue', + }, + }, + edgeStyle: { + default: { + lineWidth: 0.7, + strokeOpacity: 0.1, // The opacity of the edge. The transparency of the gathered edges will be superimposed, which has the effect of highlighting high-density areas + stroke: 'l(0) 0:' + llightBlue16 + ' 1:' + llightOrange16, + }, + }, +}); +``` + +The edge begin with `llightBlue16` color and end with `llightOrange16` color: + +```javascript +const llightBlue16 = '#C8FDFC'; +const llightOrange16 = '#FFAA86'; +``` + +Set the background of the body to be black to reach a better visual effect: + +```html + +``` + +### Execute the Bundling and Render + +The Graph and the Edge Bundling Plugin have been instantiated to `graph` and `edgeBundling`. The following code executes the bundling and load the data, render the graph: + +```javascript +edgeBundling.bundling(data); // Execute the bundling +graph.data(data); +graph.render(); +``` + +#### Configure Tooltip and Interactions + +Tooltip shows the detail information when the mouse hovers on a node. We first configure the style for the tooltip in HTML: + +```html + +``` + +Then, we add a configuration `modes` onto the graph in the code about instantiating the Graph. As shown below, the `drag-canvas`, `zoom-canvas`, and `tooltip` are activated. The content of the `tooltip` is defined in `formatText`: + +```javascript + modes: { + default: [ 'drag-canvas', 'zoom-canvas', { + type: 'tooltip', + formatText(model) { + const text = 'Longitude: ' + model.lon + '\n Latitude: ' + model.lat; + return text; + }, + shouldUpdate: e => { + return true; + } + }] + } +``` + +After these configurations, the `tooltip` with longitude and latitude will show up when mouse hovers a node:
img + +> tooltip + +In the same time, the canvas is draggable and zoomable:
img + +> Drag and zoom the canvas + +## Analysis + +img + +> The final result. The size of the node indicates the total flights about the city. The pie node indicates the ratio of leaving flights and arriving flights (orange for arriving, cyan for leaving). The gradient color of an edge indicates its direction (cyan for start, orange for end). + +Now, let's analyze the final result: + +- Large nodes are mainly concentrated in the east-central region. According to the positions, It can be speculated that these cities are: Atlanta, New York, Chicago, Houston, Kansas, etc. All these cities are important transportation hubs in the United States; +- There are lots of orange edges in the east American, which means there are more arriving flights in east American; +- In contrast, there are more leaving flights from western cities; +- Flight directions are start from east and end in west overall; +- The eastern flights are also denser and more frequent than the western ones; +- There are more flights on the west coast from Seattle and Portland to Los Angeles. + +The above findings can be easily explained: The eastern United States is the economic and political concentration region of the United States. diff --git a/packages/site/docs/manual/cases/edgeBundling.zh.md b/packages/site/docs/manual/cases/edgeBundling.zh.md new file mode 100644 index 0000000000..f25d890ea1 --- /dev/null +++ b/packages/site/docs/manual/cases/edgeBundling.zh.md @@ -0,0 +1,329 @@ +--- +title: Edge Bundling +order: 2 +--- + +## 背景 + +大多数图数据在可视化时被展示成点-线图(Node-link Diagram)的形式。点-线图特别适用于如交通网络图一类的关系数据的展示,这种数据的节点通常带有地理位置信息,例如迁徙图、移民图、航线图等。
img img + +> (左)图 1. 法国航线图。(右)图 2. 美国航线图。 + +img +img + +> (左)图 3. 世界网络 IXP 对等图。(右)图 4. 美国移民图。 + +## 问题 + +虽然点-线图提供了直观的可视化,但是当数据存在大量节点和边时,视觉混乱(Visual Clutter)很快成为严重的问题。点-线图中的视觉混乱通常是边缘拥塞的直接结果,而在如交通网络一类数据中,节点位置通常具有明确定义的含义,并不总是可以修改节点位置以减少视觉混乱,如图 1 ~ 4 四个例子。因此,学术界诸多研究者设计了各种通过优化边的方式减轻上述视觉混乱,其中边绑定(Edge Bundling)方法被广泛研究和应用。各种边绑定的方法总结在「链接」。 + +例如下面这一个复杂的美国航线数据集,节点代表美国城市,带有坐标和经纬度信息;一条边代表一条航线: + +```json +{ + "nodes": [ + { + "x": -922.24444, + "y": 347.29444, + "id": "0", + "lon": -92.224444, + "lat": 34.729444 + }, + { + "x": -922.24444, + "y": 347.29444, + "id": "1", + "lon": -92.224444, + "lat": 34.729444 + } + // ... 其他节点 + ], + "edges": [ + { + "source": "0", + "target": "21", + "id": "e0" + }, + { + "source": "2", + "target": "13", + "id": "e1" + } + // ... 其他边 + ] +} +``` + +如果使用 G6 简单地将节点和边渲染出来,将会得到如下结果:
img + +> 图 5. G6 渲染原始数据结果 + +我们发现简单地将该数据渲染后的结果航线纵横交错,穿梭在密集的城市当中,视觉上十分混乱,即难以看清细节,也不能发现航线的总体趋势。 + +## 期待效果 + +我们希望可以通过边绑定的方法降低图 5 的视觉混乱,从而清晰图的整体走势、结构,突出航线频繁的城市,它们可能是重要的交通枢纽,并展示更多的统计信息,以便观察者进行分析。借助 G6,我们可以实现如下效果。通过边绑定,边的交错混乱情况被降低,颜色映射航班的飞行方向(出发(橙红色)与降落(青色))。节点大小表示到达与离开该城市的航班总数量,每个节点使用了饼图展示达到(橙红色)和离开(青色)航班的比例。并增加 hover 的交互,使用 tooltip 展示每个城市的经纬度。
img + +> 期待效果图及 tooltip 效果。 + +## 实现步骤 + +### 统计必要信息 + +首先,我们使用简单的 JS 根据数据统计每个节点的总度数(degree,即出入该城市的航线总数)、出度(outDegree,即飞出该城市的航线数)、入度(inDegree,即飞入该城市成航线数),为后续映射到节点上做好准备。 + +```javascript +const nodes = data.nodes; +const edges = data.edges; +nodes.forEach((n) => { + n.y = -n.y; + n.degree = 0; + n.inDegree = 0; + n.outDegree = 0; +}); +// compute the degree of each node +const nodeIdMap = new Map(); +nodes.forEach((node) => { + nodeIdMap.set(node.id, node); +}); +edges.forEach((e) => { + const source = nodeIdMap.get(e.source); + const target = nodeIdMap.get(e.target); + source.outDegree++; + target.inDegree++; + source.degree++; + target.degree++; +}); +let maxDegree = -9999, + minDegree = 9999; +nodes.forEach((n) => { + if (maxDegree < n.degree) maxDegree = n.degree; + if (minDegree > n.degree) minDegree = n.degree; +}); +const sizeRange = [1, 20]; +const degreeDataRange = [minDegree, maxDegree]; +// 将范围是 degreeDataRange 的 degree 属性映射到范围 sizeRange 上后, +// 写入到 nodes 中元素的 ‘size’ 属性中 +scaleNodeProp(nodes, 'size', 'degree', degreeDataRange, sizeRange); +``` + +`scaleNodeProp()` 方法将指定的节点属性 `refPropName` 根据给定数值范围 `outRange` 归一化,映射到另一个属性 `propName` 上: + +```javascript +/** + * 映射属性 + * @param {array} nodes 对象数组 + * @param {string} propName 写入的属性名称 + * @param {string} refPropName 被归一化的属性名称 + * @param {array} dataRange 被归一化的属性的值范围 [min, max] + * @param {array} outRange 写入的属性的值范围 [min, max] + */ +function scaleNodeProp(nodes, propName, refPropName, dataRange, outRange) { + const outLength = outRange[1] - outRange[0]; + const dataLength = dataRange[1] - dataRange[0]; + nodes.forEach((n) => { + n[propName] = ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0]; + }); +} +``` + +通过上面两段代码,我们已经将归一化的度数映射到节点大小 `size` 上。 + +### 实例化边绑定插件 + +G6 中提供的边绑定插件是基于 FEDB(Force-Directed Edge Bundling for Graph Visualization)一文的实现。可以通过调节参数调整边绑定的效果。 + +```javascript +const edgeBundling = new Bundling({ + bundleThreshold: 0.6, // 绑定的容忍度。数值越低,被绑定在一起的边相似度越高,即被绑在一起的边更少。 + K: 100, // 绑定的强度 +}); +``` + +### 自定义饼图节点 + +在第一步中,我们已经为节点大小 size 映射了每个节点的总度数。为了更详细展示每个城市飞出和飞入航班的比例,我们希望在每个节点上显示一个类似于饼图的效果。例如img ,桔红色扇形代表飞入该城市的航班比例,青色代表飞出该城市的航班比例。G6 内置的 circle 、rect 等节点形状不能满足这一需求,但 G6 提供了节点的扩展机制,通过下面的代码片段,可以在 G6 中注册一个自定义的节点: + +```javascript +const lightBlue = 'rgb(119, 243, 252)'; +const lightOrange = 'rgb(230, 100, 64)'; + +// 注册自定义名为 pie-node 的节点类型 +G6.registerNode( + 'pie-node', + { + drawShape: (cfg, group) => { + const radius = cfg.size / 2; // 节点半径 + const inPercentage = cfg.inDegree / cfg.degree; // 入度占总度数的比例 + const inAngle = inPercentage * Math.PI * 2; // 入度在饼图中的夹角大小 + const outAngle = Math.PI * 2 - inAngle; // 出度在饼图中的夹角大小 + const inArcEnd = [radius * Math.cos(inAngle), radius * Math.sin(inAngle)]; // 入度饼图弧结束位置 + let isInBigArc = 1, + isOutBigArc = 0; + if (inAngle > Math.PI) { + isInBigArc = 0; + isOutBigArc = 1; + } + // 定义代表入度的扇形形状 + const fanIn = group.addShape('path', { + attrs: { + path: [ + ['M', radius, 0], + ['A', radius, radius, 0, isInBigArc, 0, inArcEnd[0], inArcEnd[1]], + ['L', 0, 0], + ['B'], + ], + lineWidth: 0, + fill: lightOrange, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'in-fan-shape', + }); + // 定义代表出度的扇形形状 + const fanOut = group.addShape('path', { + attrs: { + path: [ + ['M', inArcEnd[0], inArcEnd[1]], + ['A', radius, radius, 0, isOutBigArc, 0, radius, 0], + ['L', 0, 0], + ['B'], + ], + lineWidth: 0, + fill: lightBlue, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'out-fan-shape', + }); + // 返回 keyshape + return fanIn; + }, + }, + 'single-node', +); +``` + +这样,我们就在 G6 中注册了一个名为  pie-node 的节点类型。 + +### 实例化图 + +在这一步中,我们在实例化图时,并为之指定边绑定插件、节点类型(刚才自定义的 pie-node)、节点样式、边样式(渐变色)。 + +```javascript +const edgeBundling = new Bundling({ + bundleThreshold: 0.6, // 绑定的容忍度。数值越低,被绑定在一起的边相似度越高,即被绑在一起的边更少。 + K: 100, // 绑定的强度 +}); +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 800, + plugins: [edgeBundling], // 加入插件 + fitView: true, + defaultNode: { + size: 3, + color: 'steelblue', + fill: 'steelblue', + }, + nodeStyle: { + default: { + lineWidth: 0, + fill: 'steelblue', + }, + }, + edgeStyle: { + default: { + lineWidth: 0.7, + strokeOpacity: 0.1, // 设置边透明度,在边聚集的部分透明度将会叠加,从而具备突出高密度区域的效果 + stroke: 'l(0) 0:' + llightBlue16 + ' 1:' + llightOrange16, + }, + }, +}); +``` + +这里出发端的颜色为 `llightBlue16`,结束端的颜色为 `llightOrange16`: + +```javascript +const llightBlue16 = '#C8FDFC'; +const llightOrange16 = '#FFAA86'; +``` + +为了配合节点和边的颜色,这里将页面的 body 的颜色设置为黑色: + +```html + +``` + +### 执行绑定和渲染 + +有了 graph 实例和 edgeBundling 实例后,我们执行下面代码进行绑定操作和图的数据读入及渲染: + +```javascript +edgeBundling.bundling(data); // 执行插件的绑定操作 +graph.data(data); +graph.render(); +``` + +#### 设置 tooltip 与交互操作 + +使用 tooltip,可以在鼠标 hover 到节点上时展示该节点的其他属性值。首先在 HTML 中设定 tooltip 的样式: + +```html + +``` + +然后,在上一步实例化 `graph` 时,增加一个名为 `modes` 的配置项到参数中,如下写法启动了 `drag-canvas` 画图拖动操作、`zoom-canvas` 画布放缩操作,以及 `tooltip`,在  `formatText` 函数中指定了 `tooltip` 显示的文本内容: + +```javascript + modes: { + default: [ 'drag-canvas', 'zoom-canvas', { + type: 'tooltip', + formatText(model) { + const text = 'Longitude: ' + model.lon + '\n Latitude: ' + model.lat; + return text; + }, + shouldUpdate: e => { + return true; + } + }] + } +``` + +这样,当鼠标移动到节点上时,带有经纬度信息的 `tooltip` 将会出现:
img + +> tooltip + +同时,可以拖拽和放缩画布: img + +> 缩放和拖动画布 + +## 分析 + +img + +> 最终效果图。节点大小代表飞入及飞出该城市航线总数。节点饼图展示飞出与飞入航线比例统计信息(橙红色为飞入,青色为飞出)。边的渐变色代表航班的飞行方向。起始端:青色;结束端:橙红色。 + +最后,让我们一起分析如下的最终结果图给我们带来的信息: + +- 大节点主要集中在中偏东部,根据其经纬度,可以推测这些城市有:亚特兰大、纽约、芝加哥、休斯顿、堪萨斯等,这些城市都是美国重要的交通枢纽; +- 美国东部的线桔红色居多,说明东部城市的飞入航班较多; +- 相反,西部城市的飞出航班较多; +- 整体飞行方向从东至西; +- 东部的航线也较之于西部更加密集、频繁; +- 西海岸由西雅图和波特兰飞往洛杉矶的航班较多。 + +上述发现很容易被解释:美国东部是美国的经济、政治集中区域。 diff --git a/packages/site/docs/manual/cases/relations.en.md b/packages/site/docs/manual/cases/relations.en.md new file mode 100644 index 0000000000..a3f388aba8 --- /dev/null +++ b/packages/site/docs/manual/cases/relations.en.md @@ -0,0 +1,105 @@ +--- +title: Relationship Analysis Powered by G6 +order: 1 +--- + +## Background + +Social network is an important scenario of graph visualization. The relationships between people and people, people and organization are getting complicated. It is hard to meet the analyzing requirements by the classical methods. Therefore, graph visualization and analysis become important. + +## Functional Overview + +It is a graph analysis application powered by G6. It simulates a relation analysis scenario with mock data, and demonstrates the analyzing abilities of G6: + +- Expand the Relationships; +- Relationship Prediction; +- Relationship Clustering; +- Circle Detection; +- Circle Query; +- Efficient Analysis: + - Data Filtering; + - Mark the Node and Edge; + - Hide / Show Node; + - Hide / Show Label. + +img + +## Expand the Relationships + +Users can query a person by inputing some keywords into the syste, and then do some analysis by expanding the 1-6 degree relationships of the person. The relationships between the person and other organizations can be obtained too. + +**Applicable Scene:** The 1-degree relationships indicate the friends of one person in the social network, 2-degree relationships indicate the friends' friend of the person. + +img + +## Relationship Prediction + +The types of relationships are various in social network with large data. For example, we suppose that we know that A is a friend of B. We do not know whether C and D belong to og too; How will the graph transform if we add C and D onto the current graph; How will the graph transform if we add a 'prediction' type edge to C and D. Relationship prediction helps us to explore the unknown information. + +img + +**Applicable Scene:** In social network, we want to know how the graph transform when there is a new connection added to two unrelated nodes. + +img + +## Relationship Clustering + +There might be multiple 'colleague' type relationships betwenn node A and B, e.g. A and B are colleague in company1 and company2. These two 'colleague' relationships have different timestamp. We do not render all the similar relationships onto the graph, but render a clustered edge which can be expanded by clicking. + +**Applicable Scene:** Cluster relationships with same type into one edge to reduce the visual clutter. + +## Circle Detection + +Suppose that we know a circle of friends: A is a friend of B, B is a friend of C, C is a friend of A. Now we want to figure out if D and E are related to the known circle. Input D and E into circle detection, D and E will show up if they are related to the circle. + +**Applicable Scene:** Detect the existence and relationships of a node in a known circle. + +img + +## Circle Query + +For the known circles, we are able to query to nodes and relationships about them. + +**Applicable Scene:** Query the person and relationships in the circles of friends. + +img + +## Efficient Analysis + +To improve the efficiency of analyzing, we provides data filtering, node/edge marking, node/edge hiding/showing, label hiding/showing, etc. + +### Data Filtering + +It is hard for users to explore a graph with large amount of nodes and edges. By utilizing data filtering, the unconcerned items will be hided. + +img + +### Mark the Node and Edge + +Marking the important nodes and edges helps users analyze the information about the focus items. + +img + +This function is appropriate for the exploration on complex network. + +img + +### Hide / Show Node + +During the analysis process, we can selectively hide unimportant nodes and their related edges, so that we can focus on the important nodes. When the analysis is completed, we can choose to display all hidden items. + +img + +### Hide / Show Label + +When the number of edges is particularly large, the labels on the edges overlap each other, affecting our further analysis. At this point, you can choose to hide the labels on the edges. + +img + +## Conclusion + +This application uses simulated social network data as an example to demonstrate a graph analysis application powered by G6. In actual scenarios, it is not limited to social network data. Any relational data can be analyzed using graph analysis techniques, such as risk control, anti-money laundering, credit card fraud, and other business fields. G6 is an open source graph visualization engine that focuses on the demonstration and the analysis of relational data, and it is appropriate for building graph analysis applications. + +## Application Address + +
Official website of G6: https://g6.antv.antgroup.com/
GitHub of G6: https://github.com/antvis/g6 diff --git a/packages/site/docs/manual/cases/relations.zh.md b/packages/site/docs/manual/cases/relations.zh.md new file mode 100644 index 0000000000..56a4a6bf3f --- /dev/null +++ b/packages/site/docs/manual/cases/relations.zh.md @@ -0,0 +1,105 @@ +--- +title: 基于 G6 的图分析应用 +order: 1 +--- + +## 背景 + +社交网络分析是图可视化中一个重要的应用场景。
随着社交网络越来越流行,人与人、人与组织之间的关系变得越来越复杂,使用传统的分析手段,已经很难满足我们的分析需求。在这种情况下,图分析及图可视化显得愈发重要。 + +## 功能概述 + +基于 G6 实现一个图分析应用,模拟了一个关系分析场景,使用模拟数据,来展示在图分析应用中我们可以做的一些事情: + +- 关系扩散; +- 关系预判; +- 关系聚合; +- 圈检测; +- 圈查询; +- 高效分析; + - 数据过滤; + - 实时标记; + - 隐藏 / 显示 Label; + - 隐藏 / 显示节点。 + +img + +## 关系扩散 + +通过输入的方式查询到具体的个人后,可以针对个人做 1-6 度的关系扩散,也可以进一步分析与他相关的人或组织的关系。 + +适用场景:在社交网络中,通过 A 的一度关系可以查询到他的所有朋友,通过二度关系,可以查询到他的朋友的朋友,通过分析相关动态,可以了解到关于 A 的更多的信息。 + +img + +## 关系预判 + +在海量数据的社交网络中,人与人、人与组织之间会存在多种类型的关系。假设已知,如 A 是 B 的朋友,B 属于 og 组织。未知:C 与 D 是否也属于 og 组织;如果加入 C 和 D 点到当前图上,网络会如何变化;为 C 和 D 增加“预测”类型的关系,网络会如何变化。为了解这些未知的信息,我们使用关系预判功能。 + +img + +适用场景:在社交网络中,为两个原本不相关的节点增加预测边,查看网络发生的变化,以确定该操作对图产生的效果。 + +img + +## 关系聚合 + +A 和 B 之间可能会存在多个同事关系,如 A 和 B 在 company1 公司是同事,在 company2 公司也是同事,只是两条同事关系的时间戳不同。对于这种情况,我们没必要将全有的关系都绘制到页面上,可以将同类型的关系合并成一条关系,点击时再展开。 + +适用场景:为了降低视觉干扰,将多条同类型的关系合并成一条。 + +## 圈检测 + +在社交网络数据中,假设我们已知朋友圈:A 是 B 的朋友,B 是 C 的朋友,C 又是 A 的朋友。此时,我们想知道 D 和 E 是否与我们已知的朋友圈相关。使用圈检测的功能,输入 D 或 E,若他们存在于已知的朋友圈中,则会被展示出来。 + +适用场景:检测用户是否存在于已知的朋友圈中。 + +img + +## 圈查询 + +对于已知的圈,我们可以查询每个圈中包含的节点,以及它们之间的关系。 + +适用场景:查询指定朋友圈中所有的用户以及用户之间的关系。 + +img + +## 高效分析 + +图分析应用中,为了提升分析的效率,我们提供了数据过滤、标记重点节点和边、隐藏 / 显示 Label 等辅助功能,可帮助用户更快更好地进行分析。 + +### 数据过滤 + +当画布上存在大量的节点及边时,想要进行高效分析是件很困难的事情,我们可以通过过滤的功能,将暂时不需要关注的类型的节点和边先隐藏起来,以便我们将精力放在重点的节点和边上面。 + +img + +### 标记节点及边 + +在分析过程中,将重点需要关注的节点和边进行标记,可以在复杂的网络关系中很清晰地呈现出我们需要重点关注的内容。 + +img + +该功能尤其适用于探索特别复杂的网络中。 + +img + +### 隐藏 / 显示节点 + +在分析过程中,我们可以选择性地隐藏不重要的节点及其相关边,方便我们将注意力集中在重点的节点上面。当分析完成以后,可以选择将隐藏的节点全部显示出来。 + +img + +### 隐藏 / 显示 Label + +当边的数量特别大时,边上的 label 相互重叠,影响我们进一步的分析。此时,可以选择将边上的 label 隐藏。 + +img + +## 总结 + +本应用以模拟的社交网络数据为例,演示了使用 G6 构建的一个图分析的应用。在实际的场景中,不局限于社交网络数据,任何重关系类的数据,都可使用图分析的技术进行分析,如风控、反洗钱、信用卡诈骗等金融领域;商品、商家及卖家等电商领域。G6 是一款开源的图可视化引擎,专注于关系数据的展示与分析,非常适合用于构建重型的图分析应用。 + +## 应用地址 + +
G6 官网:https://g6.antv.antgroup.com/
G6 GitHub:https://github.com/antvis/g6 diff --git a/packages/site/docs/manual/cases/sequenceTime.en.md b/packages/site/docs/manual/cases/sequenceTime.en.md new file mode 100644 index 0000000000..5e7fa4cd94 --- /dev/null +++ b/packages/site/docs/manual/cases/sequenceTime.en.md @@ -0,0 +1,88 @@ +--- +title: Dynamic Relationship Analysis Powered by G6 +order: 0 +--- + +## Background + +With the rapid development of Internet business, the business and application systems, middleware and their relationships and dependencies have become more and more complex. It is hard for the developers, testing personnel, architect, and maintenance personnel to control and maintain the super complex relationships between the applications and middleware. Once a problem occurs online, the entire process from the occurrence of a failure to the organization for emergency requires multiple parties to participate. They use multiple system tools and platforms to query and synchronize information. There are breaks between multiple stages of the process, the impact assessment and change retrospection take a long time, and the requirements for emergency personnel are relatively high. There is no efficient and systematic solution for the overall failure emergency response. For example, after receiving an online alarm, the impact area and source of change cannot be located quickly. It is difficult to make accurate decisions. And it is not possible to quickly minimize online failures. + +Based on this background, we try to provide a set of emergency plan visualizations to solve a series of emergency decision-making assistance information and methods such as processes, influence areas, emergency plans, ..., to quickly stop bleeding to reduce and avoid failure upgrades. + +## Feature Design + +### 1. Demonstration of Emergency + +The demonstration of emergency is the main view after users enter the emergency mode. It contains six features:
1)Shows the node where the exception occurred on, the nodes that have a calling relationship with the abnormal node, and the calling relationship link;
2)Shows the remark information about paths or nodes;
3)The interaction abilities: show detail information when mouse hover or click a node;
4)Combines with the left panel to display;
5)Allows the menu of the node to be extended;
6)Time series analysis: time bar with bar chart to show the error counts in different time slots, allows refreshing the graph by clicking a bar.
+ +### 2. Affect Area Panel + +The affect area: The impact of each anomaly. On the one hand, it is a bussiness affect area if it happens on bussiness; On the other hand, it is a front-end affect area if it happens on the front-end; If it happens on the specific applications, it will be a application affect area.
The affect area is shown in the left panel with the features:
1)Open / close the affect area panel;
2)Show the list of affect area;
3)Swich to bussiness affect area / front-end affect area / application affect area;
4)Show the affect trends;
5)Show the list of logs;
6)The information view is collapsible;
7)Basic architecture dependencies.
+ +### 3. Operation Panel + +The entrance of the operation panel is the detail link of the list of affect area.
The operations include:
1)Show and manipulate the summary information;
2)The list of emergency assistances and operations with pop-up icons;
3)Open / close the affect area panel;
4)The information view is collapsible.
+ +### 4. Details Ppanel Extensions + +There are some extensions in the detail view of the app:
1)Log demonstration;
2)Emergency assistances.
+ +### 5. Other Features + +1. Share links. The links have timestamps for going back to the abnormal place; +2. Be able to limit the flow; +3. Highlight the key words. Use regular to match some keywords and process the HTML text to be displayed. + +## Implementation of Main Technologies + +### Demonstration of Emergency + +The demonstration of emergency is an architecture graph with states, which aims to visualize and analyze the relationships between systems and showing the abnormal states for operation and maintenance. Users can find the abnormal nodes easily by viewing the demonstration of emergency.
We provide two views for the demonstration of emergency: flow and path.
+ +img + +img + +
The flow view looks fantastic at first glance, but there are lots of redundant information in actual emergency works. It is not helpful for handling the key problem quickly. Therefore, we use the path view on the emergency workbench to show the graph more clearly. The core data of these two views are the same. The path view bundles the flow paths.
+ +### Time Series Analysis + +Actually, a graph can only map the state profile of the actual system at a moment. The problem does not occur instantaneously, but more like the undulating waters of a lake. To understand the state of the system at any time, we need to use the ability of timing analysis. It can be achieved by the time series analysis tool:
img + +This tool has three levels of time control: minutes -> hour -> day. The user switches between different days through the date selection control, analyzes the 24-hour fluctuation trend through the time axis throughout the day, and selects the specific time period to determine the minute-level analysis to be performed. Finally, the analysis is performed by selecting the specific aspect.
+ +img + +The timing analysis module has added a time axis. By default, a bar chart of the total number of errors per hour in the last hour is displayed. Clicking the bar chart indicates that a certain time point is selected. After selection, all function request interfaces are triggered to refresh all functions.
After selecting a specific time point, all functions in the page need to be refreshed again. Therefore, the data model layer of the selection time is relatively global in this page module, and all submodules that need to be changed based on this data should listen to this data during the life cycle. The demonstration also needs to be updated after new data is requested. After handling the above details, our demonstration has a very powerful timing analysis capability. + +### Node Information Extensions + +From the perspective of experience, when we deal with emergency work, what we want to do most is that users do not rely on other applications, and can directly find the most fundamental cause of the problem on our demonstation. However, due to the limitations of the current graph visualization, all the information currently converges to a "node" color block, and the information that can be mapped is very limited. Therefore, a natural idea is to expand the node expression ability.
Thanks to G6's rendering capabilities, we can easily expand the expressions we want on the nodes. Through the following example, you can see that the node extension capability of G6 is very powerful. + +img + +
The abilities after extend: + +img + +We achieve these features by G6: + + + +
With such a foundation, node styles are not limited to simple geometric shapes, but instead map representative information to nodes so that the shape of a node can roughly reflect the characteristics of a node at a glance. This is also meaningful in some scenarios where multiple nodes need to be compared.

In this emergency workbench, the error data within one hour is selected to expand the node information. The specific solution is to distribute these time series information on the circumference of the original node, and the size of the information value is mapped with a radial histogram.
+ +img + +### Complete Demo + + + +## Conclusion & Future Work + +In general, the emergency workbench is deeply focused on business scenarios, providing complete functions, reasonable logic, and complete processes. There are many things that can be deepened in the future. For example, the extension of node information can be more abundant, and richer data properties can be mapped into the node shapes; The current layout is based on the hierarchical layout: Dagre, some complex paths are not clear enough; Although the current emergency workbench is based on the previous Nebula demonstration, it has discarded the 'location' information of nodes in the entire domain architecture. All these problems should be optimized continually in the future. + +## Demo Address + +Code: https://github.com/scaletimes/g6-flow-demo
Official website of G6: https://g6.antv.antgroup.com/
GitHub of G6: https://github.com/antvis/g6 diff --git a/packages/site/docs/manual/cases/sequenceTime.zh.md b/packages/site/docs/manual/cases/sequenceTime.zh.md new file mode 100644 index 0000000000..f49cf7f493 --- /dev/null +++ b/packages/site/docs/manual/cases/sequenceTime.zh.md @@ -0,0 +1,88 @@ +--- +title: 基于 G6 的关系时序分析应用 +order: 0 +--- + +## 背景分析 + +随着互联网业务不断快速发展,业务和应用系统、中间件及其之间的关系、依赖变得越来越复杂。不论是研发、测试还是架构、运维人员,对公司线上的应用、中间件等超级复杂关系的整体把控越来越无力维护。一旦线上出现问题,从故障发生到组织应急和应急的整个过程需要多方参与并使用多个系统工具和平台来回查询、不停同步信息。过程的多个阶段之间有断裂,影响面评估和变更追溯耗时较长,且对应急人员的要求有较高的门槛。整体故障应急缺乏高效的、系统的解决方案。例如,线上告警后不能快速定位影响面和变更源,很难准确进行决策,不能迅速最小化的处理线上故障。 + +基于这样的背景,我们尝试提一套应急方案可视化,解决应急过程中流程、影响面、应急预案等一系列应急决策辅助信息和手段,快速止血以减少和避免故障升级。 + +## 功能设计 + +### 1. 应急模式大图展示 + +应急模式大图是应急模式进入后的主页面,大图应该包括的功能点有:
1)本次发生异常的节点、与异常节点的有调用关系的其他节点,以及其调用关系链路展示;
2)链路或者节点的一些备注信息展示;
3)节点上有交互能力:比如 hover 或者 click 之后有详细信息展示;
4)与左侧影响面中选择项联动展示大图;
5)节点的展开菜单扩展;
6)时序分析功能,时间轴,展示最近一小时每分钟的错误总数柱形图,点击柱形图表示选择某时间点,选择后可触发全部功能请求接口,刷新全部功能。
+ +### 2. 影响面面板功能 + +影响面的概念是指:每一次发生异常产生的影响。这个影响一方面体现在业务层面,是业务影响面;另一方面体现在更具体的前端页面上,叫前端影响面;体现在具体应用,是应用影响面。
影响面以悬浮面板的形式吸附在页面边缘(左侧),应该具有的功能有:
1)打开 / 收起影响面面板;
2)展示影响面列表信息;
3)切换业务影响面/前端影响面/应用影响面;
4)影响趋势图;
5)日志查看列表;
6)信息模块可折叠;
7)基础架构依赖。
+ +### 3. 执行操作面板功能 + +执行操作面板的入口是:点击查看影响面列表中的详情。
执行操作含的功能有:
1)概要信息列表展示和操作功能;
2)应急辅助的列表展示以及操作功能(有弹出展示图标);
3)打开 / 收起影响面面板;
4)信息模块可折叠。
+ +### 4. APP 详情面板扩展功能 + +在原有的 APP 详情页上有添加一些扩展功能:
1)日志展示;
2)应急辅助。
+ +### 5. 其他功能 + +1. 链接分享,分享的链接中带有时间参数,用以回到异常现场; +2. 可以限流; +3. 关键字高亮,使用正则去匹配部分关键字并处理待显示的 HTML 文本。 + +## 主要技术实现 + +### 应急大图 + +应急大图是一幅有状态的架构大图,主要目的是其一展示系统间关系,分析系统或接口间依赖,其二,除架构上的相关信息外,整个系统集群在某时刻的正常或异常状态的展示和分析,从运维的角度看则更具备意义。通过大图用户就能直观看到业务链路上那个节点出了问题。
我们提供了两种模式的应急大图:流量视角和链路视角。
+ +img + +img + +
流量视角的大家第一眼看起来会觉得很酷炫,但实际在应急工作中有太多的冗余信息,对快速把握问题的核心原因没有帮助,所以在应急工作台上大图的构建采用了更加清晰的链路视图。二者的核心数据是一致的,只是链路视图是将流量线做了合并。
+ +### 大图的时序分析 + +实际上,一张图上某一时刻只能映射出实际系统某一刻的状态剖面。但问题的发生不是瞬时抖动的,而更像是湖面波澜起伏的湖水时高时低。想要了解任意时刻系统的状态,这个时候我们就需要借助时序分析的能力,这个就是通过下面时序分析的工具来实现的:
img + +这个工具对时间的控制分为了三个层次:分 -> 小时 -> 天。用户通过日期选择控件在不同日期间切换,通过全天的时间轴分析 24 小时波动趋势,并选择具体时段确定要进行分析分钟级切面,最终通过选择具体切面来进行分析。
+ +img + +时序分析模块增加了时间轴,默认展示最近一小时每分钟的错误总数柱形图,点击柱形图表示选择某时间点,选择后可触发全部功能请求接口,刷新全部功能。
当选择出具体时间点后,页面中所有功能需要重新刷新。所以,选择时间的数据模型层在本页面模块相对全局的位置,而所有需要依据此数据而变化的子模块应该在生命周期中监听该数据。大图数据当然也需要在请求到新的数据后更新。
处理好以上细节后,我们的大图就有了一个非常强大的时序分析能力。 + +### 节点信息扩展 + +从体验上考虑,当我们处理应急工作时最想做到的是用户能够不依赖其他应用,直接在我们的图上能够找到问题最根本的原因。但是受限于目前图可视化的局限,目前所有的信息都收敛到一个“node”的色块上,能够映射上去的信息非常有限。所以,一个自然的思路是对节点表达能力进行扩展。
得益于 G6 的渲染能力,我们可以比较容易的在节点上扩展出我们想要的表达方式。通过下面的示例,可以看到 G6 的节点扩展能力是非常强悍的。 + +img + +
我们再来看看扩展后的交互能力: + +img + +接下来,我们将使用 G6 实现下面的功能: + + + +
有了这样的基础后,节点样式不局限于简单的几何形状,而是把具有代表意义的信息映射到节点上,从而使节点的形状一眼看上就能大致反应某节点的特点。这样在某些多个节点需要对比的场景下也是具有意义的。

本次应急工作台,先选用一小时内的错误量数据来扩展节点信息。具体方案是,将这些时序信息分布到原有节点的圆周上,信息值的大小用放射状的柱状图来映射。
+ +img + +### 完整的应用演示 + + + +## 总结展望 + +总体来讲,应急工作台深度聚焦业务场景,提供的功能齐备、逻辑合理、流程完整。未来可以深入的事情还有很多,比如,节点信息的扩展可以更加丰富,有更多更丰富的数据属性可以被映射到节点图形中;当前布局是基于 Dagre 的有向图层次布局,在布局复杂链路的时候可能还是存在不够清晰的情况;当前的应急工作台尽管基于此前的星云大图,但是却舍弃了节点在整个域架构中的“位置”信息。我们将持续深入优化这些问题。 + +## 应用地址 + +源码:https://github.com/scaletimes/g6-flow-demo
G6 官网:https://g6.antv.antgroup.com/
G6 GitHub:https://github.com/antvis/g6 diff --git a/packages/site/docs/manual/getting-started.en.md b/packages/site/docs/manual/getting-started.en.md new file mode 100644 index 0000000000..5027e34fa2 --- /dev/null +++ b/packages/site/docs/manual/getting-started.en.md @@ -0,0 +1,203 @@ +--- +title: Getting Started +order: 1 +--- + +## The First Example + + + +## Installation & Import + +There are two ways to import G6: by NPM; by CDN. + +### 1 Import G6 by NPM + +**Step 1:** Run the following command under the your project's directory in terminal: + +```bash + npm install --save @antv/g6 +``` + +**Step 2:** Import the JS file to the file where G6 is going to be used: + +```javascript +import G6 from '@antv/g6'; +``` + +### 2 Import by CDN in HTML + +```html +// version <= 3.2 + + +// version >= 3.3 + + +// version >= 4.0 + + +``` + +⚠️Attention: + +- Replace `{$version}` by the version number. e.g. `3.7.1`; +- The last version and its number can be found on NPM; +- Please refer to the branch in Github: https://github.com/antvis/g6/tree/master for more detail. + +## Getting Started + +The following steps lead to a Graph of G6: + +1. Create an HTML container for graph; +2. Prepare the data; +3. Instancialize the Graph; +4. Load the data and render. + +### Step 1 Create a HTML Container + +Create an HTML container for graph canvas, `div` tag in general. G6 will append a `canvas` tag to it and draw graph on the `canvas`. + +```html +
+``` + +### Step 2 Data Preparation + +The data for G6 should be JSON format, includes arrays `nodes` and `edges`: + +```javascript +const data = { + // The array of nodes + nodes: [ + { + id: 'node1', // String, unique and required + x: 100, // Number, the x coordinate + y: 200, // Number, the y coordinate + }, + { + id: 'node2', // String, unique and required + x: 300, // Number, the x coordinate + y: 200, // Number, the y coordinate + }, + ], + // The array of edges + edges: [ + { + source: 'node1', // String, required, the id of the source node + target: 'node2', // String, required, the id of the target node + }, + ], +}; +``` + +⚠️Attention: + +- `nodes` is an array of nodes, the `id` is unique and required property; the `x` and `y` are coordinates of the node; +- `edges` is an array of edges, `source` and `target` are required, represent the `id` of the source node and the `id` of the target node respectively; +- The properties of node and edge are described in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) and [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge). + +### Step 3 Instantiate the Graph + +The container, width, and height are required configurations when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', // String | HTMLElement, required, the id of DOM element or an HTML node + width: 800, // Number, required, the width of the graph + height: 500, // Number, required, the height of the graph +}); +``` + +### Step 4 Load the Data and Render + +```javascript +graph.data(data); // Load the data defined in Step 2 +graph.render(); // Render the graph +``` + +### The Final Result + +img + +## The Complete Code + +```html + + + + + Tutorial Demo + + + /* The container of the graph */ +
+ + /* Import G6 by CDN */ + + + + + +``` + +## Using G6 with React + +We provide a demo about using G6 with React: Demo. + +For more information about it, please refer to [Using G6 with React](/en/docs/manual/advanced/g6InReact). Welcome the Issues. + +## More + +In this chapter, we only briefly introduce the installation and usage. In G6 Tutorial, you will learn: + +- Common configurations of Graph; +- Set the properties and styls for items (node/edge); +- Configure the layout; +- Configure the interaction; +- Configure the animation; +- The usage of components. + +For more advanced functions, please refer to [Key Concepts](/en/docs/manual/middle/graph) and [Further Reading](/en/docs/manual/advanced/coordinate-system). diff --git a/packages/site/docs/manual/getting-started.zh.md b/packages/site/docs/manual/getting-started.zh.md new file mode 100644 index 0000000000..af095e5fd1 --- /dev/null +++ b/packages/site/docs/manual/getting-started.zh.md @@ -0,0 +1,203 @@ +--- +title: 快速上手 +order: 1 +--- + +## 第一个示例 + + + +## 安装 & 引用 + +在项目中引入 G6 有以下两种方式:NPM 引入;CDN 引入。 + +### 1 在项目中使用 NPM 包引入 + +**Step 1:** 使用命令行在项目目录下执行以下命令: + +```bash + npm install --save @antv/g6 +``` + +**Step 2:** 在需要用的 G6 的 JS 文件中导入: + +```javascript +import G6 from '@antv/g6'; +``` + +### 2 在 HTML 中使用  CDN 引入 + +```html +// version <= 3.2 + + +// version >= 3.3 + + +// version >= 4.0 + + +``` + +⚠️ 注意: + +- 在  `{$version}` 中填写版本号,例如  `3.7.1`; +- 最新版可以在  NPM  查看最新版本及版本号; +- 详情参考 Github 分支:https://github.com/antvis/g6/tree/master。 + +## 快速试用 + +创建一个 G6 的关系图仅需要下面几个步骤: + +1. 创建关系图的 HTML 容器; +2. 数据准备; +3. 创建关系图; +4. 配置数据源,渲染。 + +### Step 1 创建容器 + +需要在 HTML 中创建一个用于容纳 G6 绘制的图的容器,通常为 `div`  标签。G6 在绘制时会在该容器下追加 `canvas` 标签,然后将图绘制在其中。 + +```html +
+``` + +### Step 2 数据准备 + +引入 G6 的数据源为 JSON 格式的对象。该对象中需要有节点(`nodes`)和边(`edges`)字段,分别用数组表示: + +```javascript +const data = { + // 点集 + nodes: [ + { + id: 'node1', // String,该节点存在则必须,节点的唯一标识 + x: 100, // Number,可选,节点位置的 x 值 + y: 200, // Number,可选,节点位置的 y 值 + }, + { + id: 'node2', // String,该节点存在则必须,节点的唯一标识 + x: 300, // Number,可选,节点位置的 x 值 + y: 200, // Number,可选,节点位置的 y 值 + }, + ], + // 边集 + edges: [ + { + source: 'node1', // String,必须,起始点 id + target: 'node2', // String,必须,目标点 id + }, + ], +}; +``` + +注意 + +- `nodes` 数组中包含节点对象。每个节点对象中唯一的、必要的 `id` 以标识不同的节点,`x`、 `y` 指定该节点的位置; +- `edges` 数组中包含边对象。`source` 和 `target` 是每条边的必要属性,分别代表了该边的起始点 `id` 与 目标点 `id`。 +- 点和边的其他属性参见链接:[内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 和 [内置边](/en/docs/manual/middle/elements/edges/defaultEdge)。 + +### Step 3 创建关系图 + +创建关系图(实例化)时,至少需要为图设置容器、宽和高。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', // String | HTMLElement,必须,在 Step 1 中创建的容器 id 或容器本身 + width: 800, // Number,必须,图的宽度 + height: 500, // Number,必须,图的高度 +}); +``` + +### Step 4 配置数据源,渲染 + +```javascript +graph.data(data); // 读取 Step 2 中的数据源到图上 +graph.render(); // 渲染图 +``` + +### 最终的结果 + +img + +## 完整代码 + +```html + + + + + Tutorial Demo + + + /* 图的画布容器 */ +
+ + /* 引入 G6 */ + + + + + +``` + +## React 中使用 G6 + +如果你想在 React 中使用 G6 ,可以参考我们提供了的 React 中使用 G6 的 Demo。 + +更多关于 React 中如何使用 G6,请参考 [React 中使用 G6 的文档](/zh/docs/manual/advanced/g6InReact)。有任何问题都可以通过页面底部的钉钉交流群和我们沟通,也非常欢迎给我们提 issues 或 PR: https://github.com/antvis/g6/tree/master。 + +## 更多 + +本章仅仅介绍了如何安装以及最简单的场景,在 G6 Tutorial 中其他的章节中我们会教会你: + +- 实例化图时的常见配置; +- 设置元素(节点/边)属性、样式; +- 设置布局; +- 增加交互; +- 增加动画; +- 使用辅助组件。 + +想了解更高阶的功能,请参见 [G6 核心概念](/zh/docs/manual/middle/graph),[G6 扩展阅读](/zh/docs/manual/advanced/coordinate-system)。 diff --git a/packages/site/docs/manual/introduction.en.md b/packages/site/docs/manual/introduction.en.md new file mode 100644 index 0000000000..2a5c361b08 --- /dev/null +++ b/packages/site/docs/manual/introduction.en.md @@ -0,0 +1,139 @@ +--- +title: Introduction +order: 0 +redirect_from: + - /en/docs/manual +--- + +g6 banner + +[![](https://img.shields.io/travis/antvis/g6.svg)](https://travis-ci.org/antvis/g6) [![Coverage Status](https://coveralls.io/repos/github/antvis/G6/badge.svg)](https://coveralls.io/github/antvis/G6) ![](https://img.shields.io/badge/language-javascript-red.svg) ![](https://img.shields.io/badge/license-MIT-000000.svg) [![npm package](https://img.shields.io/npm/v/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6) [![NPM downloads](http://img.shields.io/npm/dm/@antv/g6.svg)](https://npmjs.org/package/@antv/g6) [![Percentage of issues still open](http://isitmaintained.com/badge/open/antvis/g6.svg)](http://isitmaintained.com/project/antvis/g6 'Percentage of issues still open') + +[中文简介](/zh/docs/manual/introduction/) + +## What is G6 + +G6 is a graph visualization engine, which provides a set of basic mechanisms, including rendering, layout, analysis, interaction, animation, and other auxiliary tools. G6 aims to simplify the complex relationships, and help people to obtain the insight of relational data. + +img + +Developers are able to build graph visualization **analysis** applications or graph visualization **modeling** applications easily. + +imgimgimg + +imgimgimg + +> Powerful Layouts + +img + +> Powerful Animation and Interactions + +## Features + +- Excellent Perforamnce: Supports large graph visualization and interactive exploration; +- Abundant Built-in Items: Nodes and edges with free configurations; +- Steerable Interactions: More than 10 basic interaction behaviors ; +- Powerful Layouts: More than 10 layout algorithms; +- Convenient Components: Outstanding abilities and performance; +- Friendly User Experience: Complete documents for different levels of requirements. TypeScript supported. + +G6 concentrates on the principle of 'good by default'. In addition, the custom mechanism of the item, interation behavior, and layout satisfies the customization requirements. + +img + +> Abundant Built-in Items + +## Installation + +```bash +$ npm install @antv/g6 +``` + +## Usage + +img + +```javascript +// The source data +const data = { + // The array of nodes + nodes: [ + { + id: 'node1', + x: 100, + y: 200, + }, + { + id: 'node2', + x: 300, + y: 200, + }, + ], + // The array of edges + edges: [ + // An edge links from node1 to node2 + { + source: 'node1', + target: 'node2', + }, + ], +}; + +// Instantiate the Graph +const graph = new G6.Graph({ + container: 'mountNode', // The container id or HTML node of the graph canvas. + // The width and the height of graph canvas + width: 800, + height: 500, +}); +// Load the data +graph.data(data); +// Render the graph +graph.render(); +``` + +[![Edit compassionate-lalande-5lxm7](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/compassionate-lalande-5lxm7?fontsize=14&hidenavigation=1&theme=dark) + +For more information about the usage, please refer to [Getting Started](/en/docs/manual/getting-started). + +## Documents + +- [Tutorial](/en/docs/manual/tutorial/preface) +- [Key Concepts](/en/docs/manual/middle/graph) +- [Further Reading](/en/docs/manual/advanced/coordinate-system) +- [API Reference](/en/docs/api/Graph) + +## Links + +Official independent product: Graphin + +Graphin stands for Graph Insight. It's a toolkit based on G6 and React, that focuses on relational visual analysis. It's simple, efficient, out of the box. + +- github: https://github.com/antvis/Graphin +- website: https://graphin.antv.vision + +Some implementations combined with front-end libraries from the third party: + +- Flow visualization editor powered by G6 and React - Workflow Designer; +- Visualization editor powered by G6 and Vue; +- Visualization and graphics editor powered by G6 and Vue - A visual graph editor based on G6 and Vue +- Visualization and graphics ER editor powered by G6 and React; +- Visualization and graphics editor powered by G6 and Angular +- Visualization Flow editor powered by G6 and Vue + +## G6 Communication Group + +Welcome to join the **G6 Communication Group** or **G6 Communication Group-2** (DingTalk groups). We also welcome the github issues. + + + + + +## How to Contribute + +Please let us know what you are you going to help. Do check out issues for bug reports or suggestions first. + +## License + +MIT license. diff --git a/packages/site/docs/manual/introduction.zh.md b/packages/site/docs/manual/introduction.zh.md new file mode 100644 index 0000000000..74b9945437 --- /dev/null +++ b/packages/site/docs/manual/introduction.zh.md @@ -0,0 +1,148 @@ +--- +title: 简介 +order: 0 +redirect_from: + - /zh/docs/manual +--- + +g6 banner + +[![](https://img.shields.io/travis/antvis/g6.svg)](https://travis-ci.org/antvis/g6) [![Coverage Status](https://coveralls.io/repos/github/antvis/G6/badge.svg)](https://coveralls.io/github/antvis/G6) ![](https://img.shields.io/badge/language-javascript-red.svg) ![](https://img.shields.io/badge/license-MIT-000000.svg) [![npm package](https://img.shields.io/npm/v/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6) [![NPM downloads](https://img.shields.io/npm/dm/@antv/g6.svg)](https://npmjs.org/package/@antv/g6) [![Percentage of issues still open](https://isitmaintained.com/badge/open/antvis/g6.svg)](https://isitmaintained.com/project/antvis/g6 'Percentage of issues still open') + +[English Introduction](/en/docs/manual/introduction/) + +## 什么是 G6 + +G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。旨在让关系变得透明,简单。让用户获得关系数据的 Insight。 + +img + +基于 G6,用户可以快速搭建自己的 **图分析** 或 **图编辑** 应用。 + +如果您还没有使用过 G6, 建议通过 [快速上手](/zh/docs/manual/getting-started) 抢先体验 G6 的魅力。 + +imgimgimg + +imgimgimg + +> 强大的布局 + +img + +> 强大的动画及交互 + +## G6 的特性 + +G6 作为一款专业的图可视化引擎,具有以下特性: + +- 优秀的性能:支持大规模图数据的交互与探索; +- 丰富的元素:内置丰富的节点与边元素,自由配置,支持自定义; +- 可控的交互:内置 10+ 交互行为,支持自定义交互; +- 强大的布局:内置了 10+ 常用的图布局,支持自定义布局; +- 便捷的组件:优化内置组件功能及性能; +- 友好的体验:根据用户需求分层梳理文档,支持 TypeScript 类型推断。 + +除了默认好用、配置自由的内置功能,元素、交互、布局均具有高可扩展的自定义机制。 + +img + +> 丰富的图元素 + +## G6 文档 + +- [G6 入门教程](/zh/docs/manual/tutorial/preface) +- [G6 核心概念](/zh/docs/manual/middle/graph) +- [G6 扩展阅读](/zh/docs/manual/advanced/coordinate-system) +- [API](/zh/docs/api/Graph) +- [G6 Blog](https://www.yuque.com/antv/g6-blog) + +## 快速上手 + +img + +```javascript +// 定义数据源 +const data = { + // 点集 + nodes: [ + { + id: 'node1', + x: 100, + y: 200, + }, + { + id: 'node2', + x: 300, + y: 200, + }, + ], + // 边集 + edges: [ + // 表示一条从 node1 节点连接到 node2 节点的边 + { + source: 'node1', + target: 'node2', + }, + ], +}; + +// 创建 G6 图实例 +const graph = new G6.Graph({ + container: 'mountNode', // 指定图画布的容器 id + // 画布宽高 + width: 800, + height: 500, +}); +// 读取数据 +graph.data(data); +// 渲染图 +graph.render(); +``` + +更详细的内容请参考 [快速上手](/zh/docs/manual/getting-started) 文档。 + +## 友情链接 + +官方独立产品:Graphin + +Graphin 取名意为 Graph Insight(图的分析洞察),是一个基于 G6 封装的 React 组件库,专注在关系可视分析领域,简单高效,开箱即用。 + +- github: https://github.com/antvis/Graphin +- 官网:https://graphin.antv.vision + +结合前端库的第三方实现: + +- 基于 G6 和 React 的可视化流程编辑器 - Workflow Designer; +- 基于 G6 和 Vue 的可视化编辑器[](); +- 基于 G6 和 Vue 的可视化图形编辑器 - A visual graph editor based on G6 and Vue; +- 基于 G6 和 React 实现的 ER 图编辑器; +- 基于 G6 和 Angular 实现的编辑器 +- 基于 G6 和 Vue 的流程图编辑器 + +## G6 图可视化交流群 + +欢迎各界 G6 使用者、图可视化爱好者加入 **G6 图可视化交流群** 及 **G6 图可视化交流二群**(钉钉群,使用钉钉扫一扫加入)讨论与交流。Graphin 的使用者,爱好者请加入 **Graphin's Group Chat** + +> **G6 图可视化交流群** 已满员,该群会不定期移除不活跃的成员。 + +> 由于维护精力有限,**G6 图可视化交流群** 仅供社区同学相互交流,不进行答疑。欢迎对 G6 感兴趣的同学加入到答疑中来,非常感谢! + +

+ + + + + + + + + +

+ +## 如何贡献 + +请让我们知道您要解决或贡献什么,所以在贡献之前请先提交 issues 描述 bug 或建议。 + +## License + +MIT license。 diff --git a/packages/site/docs/manual/middle/animation.en.md b/packages/site/docs/manual/middle/animation.en.md new file mode 100644 index 0000000000..449003216e --- /dev/null +++ b/packages/site/docs/manual/middle/animation.en.md @@ -0,0 +1,479 @@ +--- +title: Basic Animation +order: 5 +--- + +There are two levels of animation in G6: + +- GLobal animation: Transform the graph animatively when the changes are global; +- Item animation: The animation on a node or an edge. + +
+ +## Global Animation + +The global animation is controlled by Graph instance. It takes effect when some global changes happen, such as: + +- `graph.updateLayout(cfg)` change the layout; +- `graph.changeData()` change the data. + +Configure `animate: true` when instantiating a graph to achieve it. And the `animateCfg` is the configurations for the animate, see [animateCfg](#animateCfg) for more detail.
+ +```javascript +const graph = new G6.Graph({ + // ... // Other configurations + animate: true, // Boolean, whether to activate the animation when global changes happen + animateCfg: { + duration: 500, // Number, the duration of one animation + easing: 'linearEasing', // String, the easing function + }, +}); +``` + +## Item Animation + +All the built-in nodes and edges are static withou animation. To animate node or edge, please register your type of [Custom Node](/en/docs/manual/middle/elements/nodes/custom-node) or [Custom Edge](/en/docs/manual/middle/elements/edges/custom-edge), and override the `afterDraw` function. + +### Node Animation + +The animation frames are applied on one graphics shape of a node. We are going to introduce this part by three demos: + +- The graphics animation (Left of the figure below); +- The background animation (Center of the figure below); +- Partial animation (Right of the figure below). + +download +download +download +
+ +The code of the three demos can be found at: Node Animation. + +#### The Graphics Animation + +In this example, we are going to magnify and shrink the node.
+ +download + +We first find the graphics shape to be animated by `group.get('children')[0]`. Here we find the 0th graphics shape of this type of node. Then, we call `animate` for the node to define the properties for each frame(The first parameter is a function which returns the properties of each frame; the second parameter defines the configuration for animation, see [animateCfg](#animateCfg)). + +```javascript +// Magnify and shrink animation +G6.registerNode( + 'circle-animate', + { + afterDraw(cfg, group) { + // Get the first graphics shape of this type of node + const shape = group.get('children')[0]; + // The animation + shape.animate( + (ratio) => { + // Returns the properties for each frame. The input parameter ratio is a number that range from 0 to 1. The return value is an object that defines the properties for this frame. + // Magnify first, and then shrink + const diff = ratio <= 0.5 ? ratio * 10 : (1 - ratio) * 10; + let radius = cfg.size; + if (isNaN(radius)) radius = radius[0]; + // The properties for this frame. Only radius for this example + return { + r: radius / 2 + diff, + }; + }, + { + repeat: true, // Whehter play the animation repeatly + duration: 3000, // The duration of one animation is 3000 + easing: 'easeCubic', // The easing fuction is 'easeCubic' + }, + ); + }, + }, + 'circle', +); // This custom node extend the built-in node 'circle'. Except for the overrode afterDraw, other functions will extend from 'circle' node +``` + +#### Background Animation + +You can add extra shape with animation in `afterDraw`.
+ +In `afterDraw` of this demo, we draw three background circle shape with different filling colors. And the `animate` is called for magnifying and fading out the three circles. We do not use set the first parameter as a function here, but assign the target style for each animation to the input paramter: magify the radius to 10 and reduce the opacity to 0.1. The second parameter defines the configuration for the animation, see [animateCfg](#animateCfg).
+ +download + +```javascript +G6.registerNode('background-animate', { + afterDraw(cfg, group) { + let r = cfg.size / 2; + if (isNaN(r)) { + r = cfg.size[0] / 2; + }; + // The first background circle + const back1 = group.addShape('circle',{ + zIndex: -3, + attrs: { + x: 0, + y: 0, + r, + fill: cfg.color, + opacity: 0.6 + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape1' + }); + // The second background circle + const back2 = group.addShape('circle',{ + zIndex: -2, + attrs: { + x: 0, + y: 0, + r, + fill: 'blue', + opacity: 0.6 + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape2' + }); + // The third background circle + const back3 = group.addShape('circle',{ + zIndex: -1, + attrs: { + x: 0, + y: 0, + r, + fill: 'green', + opacity: 0.6 + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape3' + }); + group.sort(); // Sort the graphic shapes of the nodes by zIndex + + // Magnify the first circle and fade it out + back1.animate({ + r: r + 10, + opacity: 0.1 + }, { + repeat: true, // Play the animation repeatly + duration: 3000, + easing: 'easeCubic' + delay: 0 // No delay + }) + + // Magnify the second circle and fade it out + back2.animate({ + r: r + 10, + opacity: 0.1 + }, { + repeat: true // Play the animation repeatly + duration: 3000, + easing: 'easeCubic', + delay: 1000 // Delay 1s + }) + + // Magnify the third circle and fade it out + back3.animate({ + r: r + 10, + opacity: 0.1 + }, { + repeat: true // Play the animation repeatly + duration: 3000, + easing: 'easeCubic', + delay: 2000 // Delay 2s + }) + } +}, 'circle'); +``` + +#### Partial Animation + +In this demo, we add extra graphics shape(an image) in `afterDraw`, and set a rotation animation for it. Note that the rotation animation is a little complicated, which should be manipulated by matrix. The first parameter of `animate()` is a function which returns the properties of each frame; the second parameter defines the configuration for animation, see [animateCfg](#animateCfg)
+ +download + +```javascript +G6.registerNode( + 'inner-animate', + { + afterDraw(cfg, group) { + const size = cfg.size; + const width = size[0] - 12; + const height = size[1] - 12; + // Add an image shape + const image = group.addShape('image', { + attrs: { + x: -width / 2, + y: -height / 2, + width: width, + height: height, + img: cfg.img, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'image-shape', + }); + // Add animation for the image + image.animate( + (ratio) => { + // Returns the properties for each frame. The input parameter ratio is a number that range from 0 to 1. The return value is an object that defines the properties for this frame. + // Rotate by manipulating matrix + // The current matrix + const matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; + // The target matrix + const toMatrix = Util.transform(matrix, [['r', ratio * Math.PI * 2]]); + // The properties of this frame. Only target matrix for this demo + return { + matrix: toMatrix, + }; + }, + { + repeat: true, // Play the animation repeatly + duration: 3000, + easing: 'easeCubic', + }, + ); + }, + }, + 'rect', +); +``` + +### Edge Animation + +We are going to introduce this part by three demos: + +- A circle move along the edge (Left of the figure below); +- A running dashed line (Center of the figure below. The gif may look like a static edge due to the low fps problem. You can check out the demo by link); +- A growing line (Right of the figure below). + +download +download +download + +The code of the three demo can be found in: Edge Animation. + +#### A Moving Circle + +In this demo, we add a circle shape with moving animation in `afterDraw`. In each frame, we return the relative position of the circle on the edge. The first parameter of `animate()` is a function which returns the properties of each frame; the second parameter defines the configuration for animation, see [animateCfg](#animateCfg)
+ +download + +```javascript +G6.registerEdge( + 'circle-running', + { + afterDraw(cfg, group) { + // Get the first graphics shape of this type of edge, which is the edge's path + const shape = group.get('children')[0]; + // The start point of the edge's path + const startPoint = shape.getPoint(0); + + // Add a red circle shape + const circle = group.addShape('circle', { + attrs: { + x: startPoint.x, + y: startPoint.y, + fill: 'red', + r: 3, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + + // Add the animation to the red circle + circle.animate( + (ratio) => { + // Returns the properties for each frame. The input parameter ratio is a number that range from 0 to 1. The return value is an object that defines the properties for this frame + // Get the position on the edge according to the ratio + const tmpPoint = shape.getPoint(ratio); + // Return the properties of this frame, x and y for this demo + return { + x: tmpPoint.x, + y: tmpPoint.y, + }; + }, + { + repeat: true, // Play the animation repeatly + duration: 3000, // The duration for one animation + }, + ); + }, + }, + 'cubic', +); // Extend the built-in edge cubic +``` + +#### Running Dashed Line + +The running dashed line is achieved by modifying the `lineDash` in every frame. The first parameter of `animate()` is a function which returns the properties of each frame; the second parameter defines the configuration for animation, see [animateCfg](#animateCfg)
+ +download + +```javascript +const lineDash = [4, 2, 1, 2]; + +G6.registerEdge( + 'line-dash', + { + afterDraw(cfg, group) { + let index = 0; + // Define the animation + shape.animate( + () => { + index++; + if (index > 9) { + index = 0; + } + const res = { + lineDash, + lineDashOffset: -index, + }; + // Returns the configurations to be modified in this frame. Here the return value contains lineDash and lineDashOffset + return res; + }, + { + repeat: true, // whether executed repeatly + duration: 3000, // animation's duration + }, + ); + }, + }, + 'cubic', +); // Extend the built-in edge cubic +``` + +#### A Growing Edge + +A growing edge can also be implemented by calculating the `lineDash`. The first parameter of `animate()` is a function which returns the properties of each frame; the second parameter defines the configuration for animation, see [animateCfg](#animateCfg)
+ +download + +```javascript +G6.registerEdge( + 'line-growth', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = group.getTotalLength(); + shape.animate( + (ratio) => { + // Returns the properties for each frame. The input parameter ratio is a number that range from 0 to 1. The return value is an object that defines the properties for this frame + const startLen = ratio * length; + // Calculate the lineDash + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + { + repeat: true, // Play the animation repeatly + duration: 2000, // The duration for one animation + }, + ); + }, + }, + 'cubic', +); // Extend the built-in edge cubic +``` + +### Interaction Animation + +G6 allows user to add animation for the interaction. As showin in the figure beow, when the mouse enters the node, the related edges will show the dashed line animation.
![交互动画.gif](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-90pSrm4hkUAAAAAAAAAAABkARQnAQ)
The code for the demo can be found in: Animation of State Changing. + +This kind of animation is related to the [State](/en/docs/manual/middle/states/state) of edge. Override the function `setState` to response the state changing. When the mouse enters a node, some state of the related edges are activated. The `setState` of the edges activate the animation once it receive the state changing. The steps are: + +- Override the `setState` in custom edge, and listen to the state changing in this function; +- Listen the `mouseenter` and `mouseleave` of the nodes to activate the state of the related edges. + +The code below is a part of the code in Animation of State Changing. Please note that we have omit some code to emphasize the code related to the animation. + +The first parameter of `animate()` is a function which returns the properties of each frame; the second parameter defines the configuration for animation, see [animateCfg](#animateCfg) + +```javascript +// const data = ... +// const graph = new G6.Graph({...}); + +const lineDash = [4, 2, 1, 2]; + +// Register a type of edge named 'can-running' +G6.registerEdge( + 'can-running', + { + // Override setState + setState(name, value, item) { + const shape = item.get('keyShape'); + // Response the running state + if (name === 'running') { + // When the running state is turned to be true + if (value) { + let index = 0; + shape.animate( + () => { + index++; + if (index > 9) { + index = 0; + } + const res = { + lineDash, + lineDashOffset: -index, + }; + // Returns the configurations to be modified in this frame. Here the return value contains lineDash and lineDashOffset + return res; + }, + { + repeat: true, // whether executed repeatly + duration: 3000, // animation's duration + }, + ); + } else { + // When the running state is turned to be false + // Stop the animation + shape.stopAnimate(); + // Clear the lineDash + shape.attr('lineDash', null); + } + } + }, + }, + 'cubic-horizontal', +); // Extend the built-in edge cubic-horizontal + +// Listen the mouseenter event on node +graph.on('node:mouseenter', (ev) => { + // Get the target node of the event + const node = ev.item; + // Get the related edges of the target node + const edges = node.getEdges(); + // Turn the running state of all the related edges to be true. The setState function will be activated now + edges.forEach((edge) => graph.setItemState(edge, 'running', true)); +}); + +// Listen the mouseleave event on node +graph.on('node:mouseleave', (ev) => { + // Get the target node of the event + const node = ev.item; + // Get the related edges of the target node + const edges = node.getEdges(); + // Turn the running state of all the related edges to be false. The setState function will be activated now + edges.forEach((edge) => graph.setItemState(edge, 'running', false)); +}); + +// graph.data(data); +// graph.render(); +``` + +   ⚠️Attention: When `running` is turned to be `false`, the animation should be stopped and the `lineDash` should be cleared. + +## animateCfg + +| Configuration | Type | Default Value | Description | +| --- | --- | --- | --- | +| duration | Number | 500 | The duration for animating once | +| easing | boolean | 'linearEasing' | The easing function for the animation, see [Easing Function](#easing-Function) for more detail | +| delay | Number | 0 | Execute the animation with delay | +| repeat | boolean | false | Whether execute the animation repeatly | +| callback | Function | undefined | Callback function after the animation finish | +| pauseCallback | Function | undefined | Callback function after the animation is paused by shape.pause() | +| resumeCallback | Function | undefined | Callback function after the animation is resume by shape.resume() | + +### Easing Function + +G6 supports all the easing functions in d3.js. Thus, the options of `easing` in `animateCfg`:
`'easeLinear'`,
`'easePolyIn'`, `'easePolyOut'`, `'easePolyInOut'` ,
`'easeQuad'`, `'easeQuadIn'`, `'easeQuadOut'`, `'easeQuadInOut'` . + +For more detail of the easing functions, please refer to: d3 Easings. diff --git a/packages/site/docs/manual/middle/animation.zh.md b/packages/site/docs/manual/middle/animation.zh.md new file mode 100644 index 0000000000..c13618654f --- /dev/null +++ b/packages/site/docs/manual/middle/animation.zh.md @@ -0,0 +1,492 @@ +--- +title: 基础动画 +order: 5 +--- + +G6 中的动画分为两个层次: + +- 全局动画:全局性的动画,图整体变化时的动画过渡; +- 元素(边和节点)动画:节点或边上的独立动画。 + +
+ +## 全局动画 + +G6 的全局动画指通过图实例进行某些全局操作时,产生的动画效果。例如: + +- `graph.updateLayout(cfg)` 布局的变化 +- `graph.changeData()` 数据的变化 + +通过实例化图时配置 `animate: true`,可以达到每次进行上述操作时,动画效果变化的目的。配合 `animateCfg` 配置动画参数,`animateCfg` 具体配置参见 [animateCfg](#animateCfg)
+ +```javascript +const graph = new G6.Graph({ + // ... // 图的其他配置项 + animate: true, // Boolean,切换布局时是否使用动画过度,默认为 false + animateCfg: { + duration: 500, // Number,一次动画的时长 + easing: 'linearEasing', // String,动画函数 + }, +}); +``` + +## 元素动画 + +由于 G6 的内置节点和边是没有动画的,需要实现节点和边上的动画需要通过[自定义节点](/zh/docs/manual/middle/elements/nodes/custom-node)、[自定义边](/zh/docs/manual/middle/elements/edges/custom-edge)时复写  `afterDraw`  实现。 + +### 节点动画 + +节点上的动画,即每一帧发生变化的是节点上的某一个图形。关于节点动画,以下面三个动画示例进行讲解: + +- 节点上图形的动画(如下图左); +- 增加带有动画的背景图形(如下图中); +- 节点上部分图形的旋转动画(如下图右)。 + +download +download +download +
+ +以上三个动画节点的 demo 代码见: 节点动画。 + +#### 节点上图形的动画 + +
+ +download + +本例实现节点放大缩小,通过  `group.get('children')[0]` 找到需要更新的图形(这里找到该节点上第 0 个图形),然后调用该图形的 `animate` 方法指定动画的参数及每一帧的变化(  第一个参数是返回每一帧需要变化的参数集的函数,其参数 `ratio` 是当前正在进行的一次动画的进度,范围 [0, 1];第二个参数是动画的参数,动画参数的具体配置参见 [animateCfg](#animateCfg))。 + +```javascript +// 放大、变小动画 +G6.registerNode( + 'circle-animate', + { + afterDraw(cfg, group) { + // 获取该节点上的第一个图形 + const shape = group.get('children')[0]; + // 该图形的动画 + shape.animate( + (ratio) => { + // 每一帧的操作,入参 ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。 + // 先变大、再变小 + const diff = ratio <= 0.5 ? ratio * 10 : (1 - ratio) * 10; + let radius = cfg.size; + if (isNaN(radius)) radius = radius[0]; + // 返回这一帧需要变化的参数集,这里只包含了半径 + return { + r: radius / 2 + diff, + }; + }, + { + // 动画重复 + repeat: true, + duration: 3000, + easing: 'easeCubic', + }, + ); // 一次动画持续的时长为 3000,动画效果为 'easeCubic' + }, + }, + 'circle', +); // 该自定义节点继承了内置节点 'circle',除了被复写的 afterDraw 方法外,其他按照 'circle' 里的函数执行。 +``` + +#### 增加带有动画的背景图形 + +在 `afterDraw` 方法中为已有节点添加额外的 shape ,并为这些新增的图形设置动画。
+ +本例在 `afterDraw` 方法中,绘制了三个背景 circle ,分别使用不同的颜色填充,再调用 `animate` 方法实现这三个 circle 逐渐变大、变淡的动画。本例中没有使用函数参数的形式,直接在 `animate` 函数的第一个参数中设置每次动画结束时的最终目标样式,即半径增大 10,透明度降为 0.1。第二个参数设置动画的配置,动画参数的具体配置参见 [animateCfg](#animateCfg)。
+ +download + +```javascript +G6.registerNode( + 'background-animate', + { + afterDraw(cfg, group) { + let r = cfg.size / 2; + if (isNaN(r)) { + r = cfg.size[0] / 2; + } + // 第一个背景圆 + const back1 = group.addShape('circle', { + zIndex: -3, + attrs: { + x: 0, + y: 0, + r, + fill: cfg.color, + opacity: 0.6, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'circle-shape1', + }); + // 第二个背景圆 + const back2 = group.addShape('circle', { + zIndex: -2, + attrs: { + x: 0, + y: 0, + r, + fill: 'blue', // 为了显示清晰,随意设置了颜色 + opacity: 0.6, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'circle-shape2', + }); + // 第三个背景圆 + const back3 = group.addShape('circle', { + zIndex: -1, + attrs: { + x: 0, + y: 0, + r, + fill: 'green', + opacity: 0.6, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'circle-shape3', + }); + group.sort(); // 排序,根据 zIndex 排序 + + // 第一个背景圆逐渐放大,并消失 + back1.animate( + { + r: r + 10, + opacity: 0.1, + }, + { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: 0, // 无延迟 + }, + ); + + // 第二个背景圆逐渐放大,并消失 + back2.animate( + { + r: r + 10, + opacity: 0.1, + }, + { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: 1000, // 1 秒延迟 + }, + ); // 1 秒延迟 + + // 第三个背景圆逐渐放大,并消失 + back3.animate( + { + r: r + 10, + opacity: 0.1, + }, + { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: 2000, // 2 秒延迟 + }, + ); + }, + }, + 'circle', +); +``` + +#### 部分图形旋转动画 + +这一例也是在 `afterDraw` 方法中为已有节点添加额外的 shape (本例中为 image),并为这些新增的图形设置旋转动画。旋转动画较为复杂,需要通过矩阵的操作实现。`animate` 函数的第一个参数是返回每一帧需要变化的参数集的函数,其参数 `ratio` 是当前正在进行的一次动画的进度,范围 [0, 1];第二个参数是动画的参数,动画参数的具体配置参见 [animateCfg](#animateCfg)。
+ +download + +```javascript +G6.registerNode( + 'inner-animate', + { + afterDraw(cfg, group) { + const size = cfg.size; + const width = size[0] - 12; + const height = size[1] - 12; + // 添加图片 shape + const image = group.addShape('image', { + attrs: { + x: -width / 2, + y: -height / 2, + width: width, + height: height, + img: cfg.img, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'image-shape', + }); + // 该图片 shape 的动画 + image.animate( + (ratio) => { + // 每一帧的操作,入参 ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。 + // 旋转通过矩阵来实现 + // 当前矩阵 + const matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; + // 目标矩阵 + const toMatrix = Util.transform(matrix, [['r', ratio * Math.PI * 2]]); + // 返回这一帧需要的参数集,本例中只有目标矩阵 + return { + matrix: toMatrix, + }; + }, + { + repeat: true, // 动画重复 + duration: 3000, + easing: 'easeCubic', + }, + ); + }, + }, + 'rect', +); +``` + +### 边动画 + +关于边动画,以下面三个动画示例进行讲解: + +- 圆点在沿着线运动(下图左); +- 虚线运动的效果(下图中,gif 图片的帧率问题导致看起来是静态的,可以访问下面的 demo 链接查看); +- 线从无到有的效果(下图右)。 + +download +download +download + +以上三个边动画的 demo 代码见:边动画。 + +#### 圆点运动 + +本例通过在 `afterDraw` 方法中为边增加了一个 circle 图形,该图形沿着线运动。沿着线运动的原理:设定每一帧中,该 circle 在线上的相对位置。`animate` 函数的第一个参数是返回每一帧需要变化的参数集的函数,其参数 `ratio` 是当前正在进行的一次动画的进度,范围 [0, 1];第二个参数是动画的参数,动画参数的具体配置参见 [animateCfg](#animateCfg)。
+ +download + +```javascript +G6.registerEdge( + 'circle-running', + { + afterDraw(cfg, group) { + // 获得当前边的第一个图形,这里是边本身的 path + const shape = group.get('children')[0]; + // 边 path 的起点位置 + const startPoint = shape.getPoint(0); + + // 添加红色 circle 图形 + const circle = group.addShape('circle', { + attrs: { + x: startPoint.x, + y: startPoint.y, + fill: 'red', + r: 3, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'circle-shape', + }); + + // 对红色圆点添加动画 + circle.animate( + (ratio) => { + // 每一帧的操作,入参 ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。 + // 根据比例值,获得在边 path 上对应比例的位置。 + const tmpPoint = shape.getPoint(ratio); + // 返回需要变化的参数集,这里返回了位置 x 和 y + return { + x: tmpPoint.x, + y: tmpPoint.y, + }; + }, + { + repeat: true, // 动画重复 + duration: 3000, + }, + ); // 一次动画的时间长度 + }, + }, + 'cubic', +); // 该自定义边继承内置三阶贝塞尔曲线 cubic +``` + +#### 虚线运动的效果 + +虚线运动的效果是通过计算线的 `lineDash` ,并在每一帧中不断修改实现。`animate` 函数的第一个参数是返回每一帧需要变化的参数集的函数,其参数 `ratio` 是当前正在进行的一次动画的进度,范围 [0, 1];第二个参数是动画的参数,动画参数的具体配置参见 [animateCfg](#animateCfg)。
+ +download + +```javascript +// lineDash 的差值,可以在后面提供 util 方法自动计算 +const lineDash = [4, 2, 1, 2]; +G6.registerEdge( + 'line-dash', + { + afterDraw(cfg, group) { + // 获得该边的第一个图形,这里是边的 path + const shape = group.get('children')[0]; + let index = 0; + // 边 path 图形的动画 + shape.animate( + () => { + index++; + if (index > 9) { + index = 0; + } + const res = { + lineDash, + lineDashOffset: -index, + }; + // 返回需要修改的参数集,这里修改了 lineDash,lineDashOffset + return res; + }, + { + repeat: true, // 动画重复 + duration: 3000, // 一次动画的时长为 3000 + }, + ); + }, + }, + 'cubic', +); // 该自定义边继承了内置三阶贝塞尔曲线边 cubic +``` + +#### 线从无到有 + +线从无到有的动画效果,同样可以通过计算 `lineDash` 来实现。`animate` 函数的第一个参数是返回每一帧需要变化的参数集的函数,其参数 `ratio` 是当前正在进行的一次动画的进度,范围 [0, 1];第二个参数是动画的参数,动画参数的具体配置参见 [animateCfg](#animateCfg)。
+ +download + +```javascript +G6.registerEdge( + 'line-growth', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = group.getTotalLength(); + shape.animate( + (ratio) => { + const startLen = ratio * length; + // 计算 lineDash + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + { + repeat: true, // 是否重复执行 + duration: 2000, // 一次动画持续时长 + }, + ); + }, + }, + 'cubic', +); // 该自定义边继承了内置三阶贝塞尔曲线边 cubic +``` + +### 交互动画 + +在交互的过程中也可以添加动画。如下图所示,当鼠标移到节点上时,所有与该节点相关联的边都展示虚线运动的动画。
![交互动画.gif](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-90pSrm4hkUAAAAAAAAAAABkARQnAQ)
上图完整 demo 即代码参见:状态切换动画。 + +这种动画涉及到了边的 [状态](/zh/docs/manual/middle/states/state)。在自定义边时复写 `setState` 方法,可对边的各种状态进行监听。鼠标移动到节点上,相关边的某个状态被开启,`setState` 方法中监听到后开启动画效果。步骤如下: + +- 自定义边中复写 `setState` 方法监听该边的状态,以及某状态下的动画效果; +- 监听中间的节点的 `mouseenter` 和 `mouseleave` 事件,触发相关边的状态变化。 + +下面代码节选自 demo 状态切换动画,请注意省略了部分代码,只展示了交互相关以及边动画相关的代码。`animate` 函数的第一个参数是返回每一帧需要变化的参数集的函数,其参数 `ratio` 是当前正在进行的一次动画的进度,范围 [0, 1];第二个参数是动画的参数,动画参数的具体配置参见 [animateCfg](#animateCfg)。 + +```javascript +// const data = ... +// const graph = new G6.Graph({...}); + +const lineDash = [4, 2, 1, 2]; + +// 注册名为 'can-running' 的边 +G6.registerEdge( + 'can-running', + { + // 复写setState方法 + setState(name, value, item) { + const shape = item.get('keyShape'); + // 监听 running 状态 + if (name === 'running') { + // running 状态为 true 时 + if (value) { + let index = 0; // 边 path 图形的动画 + shape.animate( + () => { + index++; + if (index > 9) { + index = 0; + } + const res = { + lineDash, + lineDashOffset: -index, + }; + // 返回需要修改的参数集,这里修改了 lineDash,lineDashOffset + return res; + }, + { + repeat: true, // 动画重复 + duration: 3000, // 一次动画的时长为 3000 + }, + ); + } else { + // running 状态为 false 时 + // 结束动画 + shape.stopAnimate(); + // 清空 lineDash + shape.attr('lineDash', null); + } + } + }, + }, + 'cubic-horizontal', +); // 该自定义边继承了内置横向三阶贝塞尔曲线边 cubic-horizontal + +// 监听节点的 mouseenter 事件 +graph.on('node:mouseenter', (ev) => { + // 获得当前鼠标操作的目标节点 + const node = ev.item; + // 获得目标节点的所有相关边 + const edges = node.getEdges(); + // 将所有相关边的 running 状态置为 true,此时将会触发自定义节点的 setState 函数 + edges.forEach((edge) => graph.setItemState(edge, 'running', true)); +}); + +// 监听节点的 mouseleave 事件 +graph.on('node:mouseleave', (ev) => { + // 获得当前鼠标操作的目标节点 + const node = ev.item; + // 获得目标节点的所有相关边 + const edges = node.getEdges(); + // 将所有相关边的 running 状态置为 false,此时将会触发自定义节点的 setState 函数 + edges.forEach((edge) => graph.setItemState(edge, 'running', false)); +}); + +// graph.data(data); +// graph.render(); +``` + +   ⚠️ 注意: `running` 为 `false` 时,要停止动画,同时把 `lineDash` 清空。 + +## animateCfg + +| 配置项 | 类型 | 默认值 | 描述 | +| -------------- | -------- | -------------- | ---------------------------------------- | +| duration | Number | 500 | 一次动画的时长 | +| easing | boolean | 'linearEasing' | 动画函数,见 [easing 函数](#easing-函数) | +| delay | Number | 0 | 延迟一段时间后执行动画 | +| repeat | boolean | false | 是否重复执行动画 | +| callback | Function | undefined | 动画执行完时的回调函数 | +| pauseCallback | Function | undefined | 动画暂停时(`shape.pause()`)的回调函数 | +| resumeCallback | Function | undefined | 动画恢复时(`shape.resume()`)的回调函数 | + +### easing 函数 + +easing 函数是指动画的函数。例如线性插值、先快后慢等。
G6 支持所有 d3.js 中的动画函数。因此,上面代码中 `animateCfg` 配置中的 String 类型的 `easing` 可以取值有:
`'easeLinear'` ,
`'easePolyIn'` ,`'easePolyOut'` , `'easePolyInOut'` ,
`'easeQuad'` ,`'easeQuadIn'` ,`'easeQuadOut'` , `'easeQuadInOut'` 。 + +更多取值及所有取值含义参见:d3 Easings。 diff --git a/packages/site/docs/manual/middle/elements/advanced-style/gradient.en.md b/packages/site/docs/manual/middle/elements/advanced-style/gradient.en.md new file mode 100644 index 0000000000..755f8938fb --- /dev/null +++ b/packages/site/docs/manual/middle/elements/advanced-style/gradient.en.md @@ -0,0 +1,40 @@ +--- +title: Gradient Color for Objects +order: 4 +--- + +In G6, you can set **Linear Gradient** for stroke and **Circular Gradient** for filling color. + +### Linear Gradient for Stroke + +#### Demonstration + +img + +> `l` is the flag for linear gradient, the text in green can be modified to satisfy your requirements. + +#### Usage + +Assign the `stroke` as below while [Configurating the Node or Edge](/en/docs/manual/tutorial/elements): + +```javascript +// Using the linear gradient for the stroke. The gradient angle is 0, and the begin color is #ffffff, the color of the midpoint is #7ec2f3, and the end color is #1890ff +stroke: 'l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff' +``` + +### Circular Gradient for Fill + +#### Demonstration + +img + +> `r` is the flag for circular gradient, the text in green can be modified to satisfy your requirements. The `x` `y` and `r` are the relative values and range from 0 to 1. + +#### Usage + +Assign the `fill` as below while [Configurating the Node or Edge](/en/docs/manual/tutorial/elements): + +```javascript +// Using the radial gradient for filling color. The center of the circular gradient is the center of the filled shape's bbox(bounding box). The radius is equal to 0.1 multiples the length of the diagonal of the bbox. The begin color is #ffffff, the color of the midpoint is #7ec2f3, and the end color is #1890ff +fill: 'r(0.5, 0.5, 0.1) 0:#ffffff 1:#1890ff' +``` diff --git a/packages/site/docs/manual/middle/elements/advanced-style/gradient.zh.md b/packages/site/docs/manual/middle/elements/advanced-style/gradient.zh.md new file mode 100644 index 0000000000..eca0b9b862 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/advanced-style/gradient.zh.md @@ -0,0 +1,42 @@ +--- +title: 设置渐变色 +order: 4 +--- + +G6 提供描边的**线性渐变**和填充的**环形渐变**两种形式。 + +### 描边线性渐变 + +#### 示例 + +img + +> 说明:`l` 表示使用线性渐变,绿色的字体为可变量,由用户自己填写。 + +#### 用法 + +在[配置节点或边](/zh/docs/manual/tutorial/elements)的样式时,指定 `stroke` 属性如下: + +```javascript +// 使用渐变色描边,渐变角度为 0,渐变的起始点颜色 #ffffff,中点的渐变色为 #7ec2f3,结束的渐变色为 #1890ff + +stroke: 'l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff' +``` + +### 填充环形渐变 + +#### 示例 + +img + +> 说明:r 表示使用放射状渐变,绿色的字体为可变量,由用户自己填写,开始圆的 x y r 值均为相对值,0 至 1 范围。 + +#### 用法 + +在[配置节点或边](/zh/docs/manual/tutorial/elements)的样式时,指定 `fill` 属性如下: + +```javascript +// 使用渐变色填充,渐变起始圆的圆心坐标为被填充物体的包围盒中心点,半径为(包围盒对角线长度 / 2) 的 0.1 倍,渐变的起始点颜色 #ffffff,中点的渐变色为 #7ec2f3,结束的渐变色为 #1890ff + +fill: 'r(0.5, 0.5, 0.1) 0:#ffffff 1:#1890ff' +``` diff --git a/packages/site/docs/manual/middle/elements/advanced-style/set-label-bg.en.md b/packages/site/docs/manual/middle/elements/advanced-style/set-label-bg.en.md new file mode 100644 index 0000000000..6e9e7bb954 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/advanced-style/set-label-bg.en.md @@ -0,0 +1,47 @@ +--- +title: Set the background of the label on node or edge +order: 0 +--- + +### Problem + +In versions below G6 3.4.4, when we want to add the background to label of a node or edge, wo need use `group.addShape('rect', {})` to implement it, the implementation method is not friendly enough. + +### Solution + +In G6 3.4.5 version, uses can set the background for nodes or edges through `background` configuration. + +**Important Hint:** The [PR](https://github.com/antvis/G6/pull/1354) comes from GitHub use @zhanba. + +```javascript +const graph = new G6.Graph({ + // ... + defaultNode: { + labelCfg: { + position: 'left', + style: { + background: { + fill: '#ffffff', + stroke: 'green', + padding: [3, 2, 3, 2], + radius: 2, + lineWidth: 3, + }, + }, + } + }, + defaultEdge: { + labelCfg: { + autoRotate: true, + style: { + background: { + fill: '#ffffff', + stroke: '#000000', + padding: [2, 2, 2, 2], + radius: 2, + }, + }, + } + } +}) +``` diff --git a/packages/site/docs/manual/middle/elements/advanced-style/set-label-bg.zh.md b/packages/site/docs/manual/middle/elements/advanced-style/set-label-bg.zh.md new file mode 100644 index 0000000000..412d44f681 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/advanced-style/set-label-bg.zh.md @@ -0,0 +1,47 @@ +--- +title: 设置节点或边的背景 +order: 0 +--- + +### 问题 + +在 G6 3.4.4 以下的版本中,当我们想要给节点或边的 label 添加背景时,需要用户自己使用 `group.addShape('rect', {})` 来实现,对 G6 不太熟悉的用户来说,处理起来可能比较麻烦,且实现方式不够友好。 + +### 解决方法 + +在 G6 3.4.5 版本中,我们增加了配置项,用户可以直接通过以下配置为节点或边设置背景。 + +**特别说明:** 该功能是由 GitHub 用户 @zhanba 贡献 [feat: add label background](https://github.com/antvis/G6/pull/1354) 。 + +```javascript +const graph = new G6.Graph({ + // ... + defaultNode: { + labelCfg: { + position: 'left', + style: { + background: { + fill: '#ffffff', + stroke: 'green', + padding: [3, 2, 3, 2], + radius: 2, + lineWidth: 3, + }, + }, + } + }, + defaultEdge: { + labelCfg: { + autoRotate: true, + style: { + background: { + fill: '#ffffff', + stroke: '#000000', + padding: [2, 2, 2, 2], + radius: 2, + }, + }, + } + } +}) +``` diff --git a/packages/site/docs/manual/middle/elements/advanced-style/texture.en.md b/packages/site/docs/manual/middle/elements/advanced-style/texture.en.md new file mode 100644 index 0000000000..db7d79214d --- /dev/null +++ b/packages/site/docs/manual/middle/elements/advanced-style/texture.en.md @@ -0,0 +1,18 @@ +--- +title: Fill with Texture in G6 +order: 5 +--- + +G6 support filling a shape with texture with **Image** or **Data URL**. + +img + +> `p` is the flag for using texture; the text in green can be modified to satisfy your requirements; `a` is a way of repeating of the texture, which can be changed into: + +> - `a`: Repeat in horizontal and vertical; - `x`: Repeat only in horizontal; - `y`: Repeat only in vertical; - `n`: No repeat. + +Assign the `fill` as below while [Configurating the Node or Edge](/en/docs/manual/tutorial/elements): + +```javascript +shape.attr('fill', 'p(a)https://gw.alipay.com/cube.png'); +``` diff --git a/packages/site/docs/manual/middle/elements/advanced-style/texture.zh.md b/packages/site/docs/manual/middle/elements/advanced-style/texture.zh.md new file mode 100644 index 0000000000..64c1bb96d5 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/advanced-style/texture.zh.md @@ -0,0 +1,21 @@ +--- +title: 设置纹理 +order: 5 +--- + +G6 支持用特定的纹理填充图形。G6 支持的纹理内容可以直接是**图片**或者 **Data URL**。 + +img + +> 说明:`p` 表示使用纹理;绿色的字体为可变量,您可以修改它们以满足您的需求;`a` 表示纹理的重复方式,可选值如下: +> +> - `a`: 该模式在水平和垂直方向重复; +> - `x`: 该模式只在水平方向重复; +> - `y`: 该模式只在垂直方向重复; +> - `n`: 该模式只显示一次(不重复)。 + +在[配置节点或边](/zh/docs/manual/tutorial/elements)的样式时,指定 `fill` 属性如下: + +```javascript +shape.attr('fill', 'p(a)https://gw.alipay.com/cube.png'); +``` diff --git a/packages/site/docs/manual/middle/elements/advanced-style/updateText.en.md b/packages/site/docs/manual/middle/elements/advanced-style/updateText.en.md new file mode 100644 index 0000000000..976776deb2 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/advanced-style/updateText.en.md @@ -0,0 +1,79 @@ +--- +title: Update Label +order: 2 +--- + +There are three ways to modify the styles for labels in G6. + +#### Configure When Instantiating Graph + +When instantiating a Graph, assign `labelCfg` in `defaultNode` or `defaultEdge` to configure the styles for labels of global nodes and global edges respectively. This is a way to define the configurations of labels in global. + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 800, + defaultNode: { + type: 'node', + labelCfg: { + style: { + fill: '#fff', + fontSize: 14, + }, + }, + }, + defaultEdge: { + type: 'line-with-arrow', + labelCfg: { + style: { + fill: '#fff', + fontSize: 14, + }, + }, + }, +}); +``` + +#### Configure Style in Data + +By this way, you can configure the `labelCfg` for different nodes and edges. + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + labelCfg: { + style: { + fill: '#fff', + fontSize: 12, + }, + }, + }, + ], +}; +``` + +#### update/updateItem + +When using `update/updateItem` to update a node or edge, the label can be updated as well. This is used for updating the configurations of the label. + +```javascript +graph.updateItem(node, { + // The style of the node + style: { + stroke: 'blue', + }, + // The configurations of the label on the node + labelCfg: { + style: { + fill: '#fff', + fontSize: 12, + }, + }, +}); +``` + +For more information about the label styles, refer to [Label on Node](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) and [Label on Edge](/en/docs/manual/middle/elements/edges/defaultEdge/#label-and-labelcfg). diff --git a/packages/site/docs/manual/middle/elements/advanced-style/updateText.zh.md b/packages/site/docs/manual/middle/elements/advanced-style/updateText.zh.md new file mode 100644 index 0000000000..561656cfc4 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/advanced-style/updateText.zh.md @@ -0,0 +1,79 @@ +--- +title: 更新文本样式 +order: 2 +--- + +在 G6 中,可以通过以下三种方式更新文本样式。 + +#### 实例化 Graph + +实例化 Graph 时,可以通过在 `defaultNode` 或 `defaultEdge` 中指定 `labelCfg` 属性修改文本的样式。这种方式指定了全局的文本样式。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 800, + defaultNode: { + type: 'node', + labelCfg: { + style: { + fill: '#fff', + fontSize: 14, + }, + }, + }, + defaultEdge: { + type: 'line-with-arrow', + labelCfg: { + style: { + fill: '#fff', + fontSize: 14, + }, + }, + }, +}); +``` + +#### 数据中指定 labelCfg + +在数据中为每个节点和边指定 `labelCfg` 可以达到为不同节点或边定制不同文本样式的目的。 + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + labelCfg: { + style: { + fill: '#fff', + fontSize: 12, + }, + }, + }, + ], +}; +``` + +#### 使用 update/updateItem + +使用 `update/updateItem` 更新节点或边时,也可以更新节点或边上的文本。该方法用于动态更新文本样式。 + +```javascript +graph.updateItem(node, { + // 节点的样式 + style: { + stroke: 'blue', + }, + // 节点上文本的样式 + labelCfg: { + style: { + fill: '#fff', + fontSize: 12, + }, + }, +}); +``` + +想知道文本都可以设置哪些属性,请参考 [节点上的文本属性](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg) 或 [边上的文本属性](/zh/docs/manual/middle/elements/edges/defaultEdge/#标签文本-label-及其配置-labelcfg)。 diff --git a/packages/site/docs/manual/middle/elements/combos/built-in/circle.en.md b/packages/site/docs/manual/middle/elements/combos/built-in/circle.en.md new file mode 100644 index 0000000000..3090493c38 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/combos/built-in/circle.en.md @@ -0,0 +1,132 @@ +--- +title: Circle +order: 1 +--- + +Built-in Circle Combo has the default style as below, the label is drawed on the center of it. Demo
img + +## Usage + +As stated in [Built-in Combos](/en/docs/manual/middle/elements/combos/defaultCombo) , there are three methods to configure combos: Configure combos globally when instantiating a Graph; Configure combos in their data; Configure combos by `graph.combo(comboFn)`. Their priorities are: + +`graph.combo(comboFn` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, `parentId`, and `label` which should be assigned to every single combo data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/combos/defaultCombo#common-property) and in each combo type (refer to doc of each combo type) support to be assigned by the three ways. + +⚠️ Attention: Must set the `groupByTypes` to `false` when instantiating the graph, which will result in rendering result with reasonable visual zIndex for combos. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'circle'` in the `defaultCombo` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + type: 'circle', // The type of the combo + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different combo with different properties, you can write the properties into the combo data. + +```javascript +const data = { + nodes: [ + ... // nodes + ], + edges: [ + ... // edges + ], + combos: [ + { + id: 'combo1', + type: 'circle', // The tyep of the combo + ... // Other configurations + }, + ... // Other combos + ] +}; +``` + +## Property + +The [Combo Common Properties](/en/docs/manual/middle/elements/combos/defaultCombo/#common-property) are available for Circle combo, some special properties are shown below. The property with Object type will be described after the table:
+ +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| size | The MINIMUM diameter of the combo (not for fixing the size) | number / number[] | When it is an array, the first element will take effect | +| fixSize | Fix the size of the combo | number | If it is not assigned, the rendering size of the combo depends on the sizes and distribution of its children items. If the `fixSize` is assigned but the `fixCollapseSize` is not, the size of the collapsed combo will still be `fixSize` | +| fixCollapseSize | Fix the size of the collapsed combo | number | If it is not assigned and the `fixSize` is assigned, the size of the collapsed Combo is `fixSize`; and if `fixCollapseSize` and `fixSize` are both not assigned, the size of the collapsed Combo is `size` | | +| style | The default style of circle combo | Object | Refer to the [style](./circle#style) | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | Refer to the [labelCfg](/circle#labelcfg) | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | + +### style + +The [Combo Common Styles](/en/docs/manual/middle/elements/nodes/defaultNode/#style) are available for Circle combo. `style` is an object to configure the filling color, stroke, and other styles. The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + combos: [ + { + label: 'combo_circle', + type: 'circle', + label: 'Circle', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + // type: 'circle', // The type has been assigned in the data, we do not have to define it any more + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object to configure the label of the combo. The [Combo Common Label Configurations](/en/docs/manual/middle/elements/combos/defaultCombo/#label-and-labelcfg) are available. Base on the code in [style](#style) section, we add `labelCfg` to `defaultCombo`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + // ... Other properties for combos + labelCfg: { + position: 'bottom', + refX: 5, + style: { + fill: '#bae637', + fontSize: 15, + // ... The style of the label + }, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/combos/built-in/circle.zh.md b/packages/site/docs/manual/middle/elements/combos/built-in/circle.zh.md new file mode 100644 index 0000000000..2780c6e71d --- /dev/null +++ b/packages/site/docs/manual/middle/elements/combos/built-in/circle.zh.md @@ -0,0 +1,132 @@ +--- +title: Circle +order: 1 +--- + +G6 内置了圆  Circle Combo,其默认样式如下。标签文本位于圆形上方。 Demo
img + +## 使用方法 + +如 [内置 Combo](/zh/docs/manual/middle/elements/combos/defaultCombo) 一节所示,配置 Combo 的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.combo(comboFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.combo(comboFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`parentId`、`label` 应当配置到每个 Combo 数据中外,其余的 [Combo 的通用属性](#/zh/docs/manual/middle/elements/combos/defaultCombo#combo-的通用属性) 以及各个 Combo 类型的特有属性(见内置 Combo 类型)均支持三种配置方式。 + +⚠️ 注意: 使用 Combo 时,必须在示例化图时配置 `groupByTypes` 设置为 `false`,图中元素的视觉层级才能合理。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultCombo` 指定 `type` 为 `'circle'`,即可使用 `circle` Combo。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + defaultCombo: { + type: 'circle', // Combo 类型 + // ... 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同 Combo 有不同的配置,可以将配置写入到 Combo 数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + ... // 节点 + ], + edges: [ + ... // 边 + ], + combos: [ + { + id: 'combo1', + type: 'circle', // Combo 类型 + ... // 其他配置 + }, + ... // 其他 Combo + ], +} +``` + +## 配置项说明 + +Circle Combo 支持 [Combo 通用配置](/zh/docs/manual/middle/elements/combos/defaultCombo#combo-的通用属性),下表对部分属性进行解释。对于 Object 类型的配置项将在后面有详细讲解:
+ +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| size | 圆的最小直径(非固定直径),渲染大小由子元素的大小与分布决定 | number / number[] | `size` 为数组时,取第一个值 | +| fixSize | 固定该 Combo 的直径 | number | 不指定时 Combo 大小由内部元素的分布和大小来决定。若指定了 `fixSize` 而没有指定 `fixCollapseSize`,则即使该 Combo 在收起状态下仍然保持 `fixSize` 指定的尺寸 | +| fixCollapseSize | 固定该 Combo 收起时的直径 | number | 不指定时,若未指定 `fixSize` 则由 `size` 决定收起时的直径,否则统一为 `fixSize` 直径 | | +| style | circle 默认样式 | Object | 参见下文 [样式属性  style](./circle#样式属性-style) 内容 | +| label | 标签文本内容 | String | | +| labelCfg | 标签文本配置项 | Object | 参见下文 [标签文本配置 labelCfg](./circle#标签文本配置-labelcfg) | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | + +### 样式属性  style + +Object 类型。支持 [Combo 通用样式](/zh/docs/manual/middle/elements/combos/defaultCombo#样式属性-style)。通过 `style` 配置来修改 Combo 的填充色、描边等属性。下面代码演示在实例化图时全局配置方法中配置 `style`,使之达到如下图效果。
img + +```javascript +const data = { + combos: [ + { + label: 'combo_circle', + type: 'circle', + label: 'Circle', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + defaultCombo: { + // type: 'circle', // 在数据中已经指定 type,这里无需再次指定 + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。通过 `labelCfg` 配置标签文本。支持 [Combo 通用标签配置](/zh/docs/manual/middle/elements/combos/defaultCombo/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultCombo` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + defaultCombo: { + // ... Combo 其他属性 + labelCfg: { + position: 'left', + refX: 5, + style: { + fill: '#bae637', + fontSize: 15, + // ... 其他文本样式的配置 + }, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/combos/built-in/rect.en.md b/packages/site/docs/manual/middle/elements/combos/built-in/rect.en.md new file mode 100644 index 0000000000..6e4098021b --- /dev/null +++ b/packages/site/docs/manual/middle/elements/combos/built-in/rect.en.md @@ -0,0 +1,132 @@ +--- +title: Rect +order: 2 +--- + +Built-in Rect Combo has the default style as below, the label is drawed on the left top inside. Demo
img + +## Usage + +As stated in [Built-in Combos](/en/docs/manual/middle/elements/combos/defaultCombo) , there are three methods to configure combos: Configure combos globally when instantiating a Graph; Configure combos in their data; Configure combos by `graph.combo(comboFn)`. Their priorities are: + +`graph.combo(comboFn` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, `parentId`, and `label` which should be assigned to every single combo data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/combos/defaultCombo#common-property) and in each combo type (refer to doc of each combo type) support to be assigned by the three ways. + +⚠️ Attention: Must set the `groupByTypes` to `false` when instantiating the graph, which will result in rendering result with reasonable visual zIndex for combos. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'rect'` in the `defaultCombo` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + type: 'rect', // The type of the combo + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different combo with different properties, you can write the properties into the combo data. + +```javascript +const data = { + nodes: [ + ... // nodes + ], + edges: [ + ... // edges + ], + combos: [ + { + id: 'combo1', + type: 'rect', // The tyep of the combo + ... // Other configurations + }, + ... // Other combos + ] +}; +``` + +## Property + +The [Combo Common Properties](/en/docs/manual/middle/elements/combos/defaultCombo/#common-property) are available for Rect combo, some special properties are shown below. The property with Object type will be described after the table:
+ +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| size | The MINIMUM diameter of the combo (not for fixing the size) | Number / Array | When it is an array, the first element will take effect | +| fixSize | Fix the size of the Combo | number / number[] | If it is not assigned, the rendering size of the combo depends on the sizes and distribution of its children items. If the `fixSize` is assigned but the `fixCollapseSize` is not, the size of the collapsed combo will still be `fixSize` | +| fixCollapseSize | Fix the size of the collapsed Combo | number / number[] | If it is not assigned and the `fixSize` is assigned, the size of the collapsed Combo is `fixSize`; and if `fixCollapseSize` and `fixSize` are both not assigned, the size of the collapsed Combo is `size` | | +| style | The default style of rect combo | Object | Refer to the [style](./rect#style) | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | Refer to the [labelCfg](/rect#labelcfg) | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | + +### style + +The [Combo Common Styles](/en/docs/manual/middle/elements/nodes/defaultNode/#style) are available for Rect combo. `style` is an object to configure the filling color, stroke, and other styles. The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + combos: [ + { + label: 'combo_rect', + type: 'rect', + label: 'rect', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + // type: 'rect', // The type has been assigned in the data, we do not have to define it any more + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object to configure the label of the combo. The [Combo Common Label Configurations](/en/docs/manual/middle/elements/combos/defaultCombo/#label-and-labelcfg) are available. *Supported by v4.7.17 and later versions* And rect type combo has a special value `'top-center'` for `labelCfg.position`, to place the label text on the top center of the rect. Base on the code in [style](#style) section, we add `labelCfg` to `defaultCombo`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + // ... Other properties for combos + labelCfg: { + position: 'bottom', + refX: -12, + style: { + fill: '#bae637', + fontSize: 15, + // ... The style of the label + }, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/combos/built-in/rect.zh.md b/packages/site/docs/manual/middle/elements/combos/built-in/rect.zh.md new file mode 100644 index 0000000000..d8a154d789 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/combos/built-in/rect.zh.md @@ -0,0 +1,132 @@ +--- +title: Rect +order: 2 +--- + +G6 内置了矩形 Rect Combo,其默认样式如下。标签文本位于矩形内部左上方。Demo
img + +## 使用方法 + +如 [内置 Combo](/zh/docs/manual/middle/elements/combos/defaultCombo) 一节所示,配置 Combo 的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.combo(comboFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.combo(comboFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`parentId`、`label` 应当配置到每个 Combo 数据中外,其余的 [Combo 的通用属性](#/zh/docs/manual/middle/elements/combos/defaultCombo#combo-的通用属性) 以及各个 Combo 类型的特有属性(见内置 Combo 类型)均支持三种配置方式。 + +⚠️ 注意: 使用 Combo 时,必须在示例化图时配置 `groupByTypes` 设置为 `false`,图中元素的视觉层级才能合理。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultCombo` 指定 `type` 为 `'rect'`,即可使用 `rect` Combo。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + defaultCombo: { + type: 'rect', // Combo 类型 + // ... 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同 Combo 有不同的配置,可以将配置写入到 Combo 数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + ... // 节点 + ], + edges: [ + ... // 边 + ], + combos: [ + { + id: 'combo1', + type: 'rect', // Combo 类型 + ... // 其他配置 + }, + ... // 其他 Combo + ], +} +``` + +## 配置项说明 + +Rect Combo 支持 [Combo 通用配置](/zh/docs/manual/middle/elements/combos/defaultCombo#combo-的通用属性),下表对部分属性进行解释。对于 Object 类型的配置项将在后面有详细讲解:
+ +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| size | 矩形的最小长与宽(非固定大小) | number / number[] | `size` 为 Number 时,长宽相等 | +| fixSize | 固定该 Combo 的长与宽 | number | 不指定时 Combo 大小由内部元素的分布和大小来决定。若指定了 `fixSize` 而没有指定 `fixCollapseSize`,则即使该 Combo 在收起状态下仍然保持 `fixSize` 指定的长与宽 | +| fixCollapseSize | 固定该 Combo 收起时的直径 | number | 不指定时,若未指定 `fixSize` 则由 `size` 决定收起时的长与宽,否则统一为 `fixSize` 长与宽 | | +| style | rect 图形的默认样式 | Object | 参见下文 [样式属性  style](./rect#样式属性-style) 内容 | +| label | 标签文本内容 | String | | +| labelCfg | 标签文本配置项 | Object | 参见下文 [标签文本配置 labelCfg](./rect#标签文本配置-labelcfg) | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | + +### 样式属性  style + +Object 类型。支持 [Combo 通用样式](/zh/docs/manual/middle/elements/combos/defaultCombo#样式属性-style)。通过 `style` 配置来修改 Combo 的填充色、描边等属性。下面代码演示在实例化图时全局配置方法中配置 `style`,使之达到如下图效果。
img + +```javascript +const data = { + combos: [ + { + label: 'combo_rect', + type: 'rect', + label: 'Rect', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + defaultCombo: { + // type: 'rect', // 在数据中已经指定 type,这里无需再次指定 + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置  labelCfg + +Object 类型。通过 `labelCfg` 配置标签文本。支持 [Combo 通用标签配置](/zh/docs/manual/middle/elements/combos/defaultCombo/#标签文本-label-及其配置-labelcfg)。**v4.7.17 及后续版本支持** 其中,rect 类型的 Combo 的 `labelCfg.position` 额外支持 `'top-center'`,表示将标签文本绘制在矩形 Combo 的上方中央。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultCombo` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + defaultCombo: { + // ... Combo 其他属性 + labelCfg: { + position: 'bottom', + refX: -12, + style: { + fill: '#bae637', + fontSize: 15, + // ... 其他文本样式的配置 + }, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/combos/custom-combo.en.md b/packages/site/docs/manual/middle/elements/combos/custom-combo.en.md new file mode 100644 index 0000000000..483fde28dd --- /dev/null +++ b/packages/site/docs/manual/middle/elements/combos/custom-combo.en.md @@ -0,0 +1,359 @@ +--- +title: Custom Combo +order: 2 +--- + +G6 provides two types of [Built-in Combos](/en/docs/manual/middle/elements/combos/defaultCombo): [circle](/en/docs/manual/middle/elements/combos/built-in/circle), [rect](/en/docs/manual/middle/elements/combos/built-in/rect. Besides, the custom machanism allows the users to extend the built-in Combos to design their own type of nodes by `G6.registerCombo('comboName', options, expendedComboName)`. A combo with complex graphics shapes, complex interactions, fantastic animations can be implemented easily. + +In this document, we will introduce the custom combo mechanism by two examples: + +
+1. Extend the Rect Combo; +
+2. Extend the Circle Combo. +
+ +## The API of Register Combo + +As stated in [Shape](/en/docs/manual/middle/elements/shape/shape-keyshape), there are two points should be satisfied when customize a combo: + +- Controll the life cycle of the combo; +- Analyze the input data and show it by graphics. + +The API and the methods which can be rewritten when extending a built-in combo are shown below: + +```javascript +G6.regitserCombo( + 'comboName', + { + /** + * Draw the shapes of the Combo. + * Do not need the label shape, it will be added by the extended class + * @param {Object} cfg The configurations of the combo + * @param {G.Group} group Graphics group, the container of the shapes of the combo + * @return {G.Shape} The keyShape of the combo. It can be obtained by combo.get('keyShape') + * More details about keyShape can be found in Middle-Graph Elements-Graphis Shape and keyShape + */ + drawShape(cfg, group) {}, + /** + * The extra operations after drawing the combo. There is no operation in this function by default + * @param {Object} cfg The configurations of the combo + * @param {G.Group} group Graphics group, the container of the shapes of the combo + */ + afterDraw(cfg, group) {}, + /** + * The operations after updating the combo. + * Control the update logic of the new graphic shapes expect keyShape here + * @override + * @param {Object} cfg The configurations of the combo + * @param {Combo} combo The combo item + */ + afterUpdate(cfg, combo) {}, + /** + * Response the combo states change. + * Should be rewritten when you want to response the state changes by animation. + * Responsing the state changes by styles can be configured, which is described in the document Middle-Behavior & Event-State + * @param {String} name The name of the state + * @param {Object} value The value of the state + * @param {Combo} combo The combo item + */ + setState(name, value, combo) {}, + }, + // the type name of the extended Combo, options: 'circle' or 'rect' + extendComboName, +); +``` + +## Attention + +Since the updating logic of Combo is special (upate the size and position according to the children automatically), registering a combo is kind of different from regitering a node or an edge: + +1. It is not recommended to customize a Combo without extending a built-in Combo, you should **extend the built-in 'circle' or 'rect' Combo**; +2. Do not add text shape for label in `drawShape`, it will be added and updated automatically by the base class; +3. Different from registering a node or an edge, it is not recommended to rewritten `update` and `draw`, or the updating logic will be abnormal; +4. The rewirtten `drawShape` should return the same type of keyShape as the keyShape of the extended Combo. Means that return a circle shape if you are extending the circle Combo, rect shape if you are extending the rect Combo; +5. The updating logic of new shapes expect the keyShape and the label should be defined in `afterUpdate`; +6. `setState` should be override when you want to response the state changes by animation. Responsing the state changes by simple styles can be achieved by [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state). + +## 1. Extend the Rect Combo + +Demo. + +### Illustration of Built-in Rect Combo + +As shown in the figure below, the position logic of built-in rect Combo: + +- The area boxed by the grey dashed rectangle is the area of the Combo's children to be positioned. innerWidth and innerHeight are the width and the height of the area respectively; +- The padding around the grey dashed area can be configured, and the real drawing width/height of the keyShape is equal to the innerWidth/innerHeight plus padding values; +- The shapes inside the combo uses the self coordinate system with origin (0, 0) centered at the center of the dashed area; +- The top and bottom padding, left and right padding are different, which leads to result that the (x, y) of the keyShape rect's left-top is not simply equal to (-width / 2, -height / 2), but calculated as shown in the figure; +- The default label of the rect Combo is positioned on the left-top inside the keyShape rect with refY to the top border and refX to the left border. The `position`, `refX`, and `refY` can be configured while using the Combo. + +img + +> Illustration of Built-in Rect Combo + +### Render the Combo + +Now, we are going to register a Combo as shown below (the figure below shows an empty combo): + +img + +According to the [Illustration of Built-in Rect Combo](./custom-combo#illustration-of-built-in-rect-combo), please be caution about the `x`, `y`, `width`, `height` of the shapes when extending the rect Combo. + +```javascript +G6.registerCombo( + 'cRect', + { + drawShape: function drawShape(cfg, group) { + const self = this; + // Get the padding from the configuration + cfg.padding = cfg.padding || [50, 20, 20, 20]; + // Get the shape's style, where the style.width and style.height correspond to the width and height in the figure of Illustration of Built-in Rect Combo + const style = self.getShapeStyle(cfg); + // Add a rect shape as the keyShape which is the same as the extended rect Combo + const rect = group.addShape('rect', { + attrs: { + ...style, + x: -style.width / 2 - (cfg.padding[3] - cfg.padding[1]) / 2, + y: -style.height / 2 - (cfg.padding[0] - cfg.padding[2]) / 2, + width: style.width, + height: style.height, + }, + draggable: true, + name: 'combo-keyShape', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }); + // Add the circle on the right + group.addShape('circle', { + attrs: { + ...style, + fill: '#fff', + opacity: 1, + // cfg.style.width and cfg.style.heigth correspond to the innerWidth and innerHeight in the figure of Illustration of Built-in Rect Combo + x: cfg.style.width / 2 + cfg.padding[1], + y: (cfg.padding[2] - cfg.padding[0]) / 2, + r: 5, + }, + draggable: true, + name: 'combo-circle-shape', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }); + return rect; + }, + // Define the updating logic of the right circle + afterUpdate: function afterUpdate(cfg, combo) { + const group = combo.get('group'); + // Find the circle shape in the graphics group of the Combo by name + const circle = group.find((ele) => ele.get('name') === 'combo-circle-shape'); + // Update the position of the right circle + circle.attr({ + // cfg.style.width and cfg.style.heigth correspond to the innerWidth and innerHeight in the figure of Illustration of Built-in Rect Combo + x: cfg.style.width / 2 + cfg.padding[1], + y: (cfg.padding[2] - cfg.padding[0]) / 2, + }); + }, + }, + 'rect', +); +``` + +Attention: you need to assign `name` and `draggable` for the shapes added in the custom node, where **the value of `name` must be unique in a custom node/edge/combo type**. `draggable: true` means that the shape is allowed to response the drag events. Only when `draggable: true`, the interaction behavior `'drag-node'` can be responsed on this shape. In the codes above, if you only assign `draggable: true` to the `keyShape` but not the right circle shape, the drag events will only be responsed on the `keyShape`. + +### Use the Custom Combo + +The following code uses the `'cRect'` Combo: + +```javascript +const data = { + nodes: [ + { id: 'node1', x: 250, y: 100, comboId: 'combo1' }, + { id: 'node2', x: 300, y: 100, comboId: 'combo1' }, + ], + combos: [ + { id: 'combo1', label: 'Combo 1', parentId: 'combo2' }, + { id: 'combo2', label: 'Combo 2' }, + { id: 'combo3', label: 'Combo 3' }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 800, + // Configure the combos globally + defaultCombo: { + // The type of the combos. You can also assign type in the data of combos + type: 'cRect', + // ... Other global configurations for combos + }, +}); +graph.data(data); +graph.render(); +``` + +img + +## 2. Extend the Circle Combo + +Demo. + +### Illustration of Built-in Circle Combo + +As shown in the figure below, the position logic of built-in circle Combo is much more simple thant rect Combo, where the (x, y) is the center of the circle, and the `padding` is a number: + +- The area boxed by the grey dashed circle is the area of the Combo's children to be positioned. innerR is the radius of the area; +- The padding around the grey dashed area can be configured, and the real drawing radius of the keyShape R = innerR + padding; +- The shapes inside the combo uses the self coordinate system with origin (0, 0) centered at the center of the circle; +- The padding around the circle is even; +- The default label of the circle Combo is positioned on the top outside the keyShape circle with refY to the top border. The `position`, `refX`, and `refY` can be configured while using the Combo. + +img + +> Illustration of Built-in Rect Combo + +### Render the Combo + +Now, we are going to register a Combo as shown below (the figure below shows an empty combo): + +img + +```javascript +// The symbols for the marker inside the combo +const collapseIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ]; +}; +const expandIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ['M', x - r + r, y - r + 4], + ['L', x, y + r - 4], + ]; +}; + +G6.registerCombo( + 'cCircle', + { + drawShape: function draw(cfg, group) { + const self = this; + // Get the shape style, where the style.r corresponds to the R in the Illustration of Built-in Rect Combo + const style = self.getShapeStyle(cfg); + // Add a circle shape as keyShape which is the same as the extended 'circle' type Combo + const circle = group.addShape('circle', { + attrs: { + ...style, + x: 0, + y: 0, + r: style.r, + }, + draggable: true, + name: 'combo-keyShape', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }); + // Add the marker on the bottom + const marker = group.addShape('marker', { + attrs: { + ...style, + fill: '#fff', + opacity: 1, + x: 0, + y: style.r, + r: 10, + symbol: collapseIcon, + }, + draggable: true, + name: 'combo-marker-shape', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }); + + return circle; + }, + // Define the updating logic for the marker + afterUpdate: function afterUpdate(cfg, combo) { + const self = this; + // Get the shape style, where the style.r corresponds to the R in the Illustration of Built-in Rect Combo + const style = self.getShapeStyle(cfg); + const group = combo.get('group'); + // Find the marker shape in the graphics group of the Combo + const marker = group.find((ele) => ele.get('name') === 'combo-marker-shape'); + // Update the marker shape + marker.attr({ + x: 0, + y: style.r, + // The property 'collapsed' in the combo data represents the collapsing state of the Combo + // Update the symbol according to 'collapsed' + symbol: cfg.collapsed ? expandIcon : collapseIcon, + }); + }, + }, + 'circle', +); +``` + +Attention: you need to assign `name` and `draggable` for the shapes added in the custom node, where the `name` can be not unique with any value you want. `draggable: true` means that the shape is allowed to response the drag events. Only when `draggable: true`, the interaction behavior `'drag-node'` can be responsed on this shape. In the codes above, if you only assign `draggable: true` to the `keyShape` but not the bottom marker shape, the drag events will only be responsed on the `keyShape`. + +### Use the Custom Combo + +The following code uses the `'cCircle'` Combo: + +```javascript +const data = { + nodes: [ + { id: 'node1', x: 250, y: 100, comboId: 'combo1' }, + { id: 'node2', x: 300, y: 100, comboId: 'combo1' }, + ], + combos: [ + { id: 'combo1', label: 'Combo 1', parentId: 'combo2' }, + { id: 'combo2', label: 'Combo 2' }, + { id: 'combo3', label: 'Combo 3' }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 800, + // Configure the combos globally + defaultCombo: { + // The type of the combos. You can also assign type in the data of combos + type: 'cCircle', + labelCfg: { + refY: 2, + }, + // ... Other global configurations for combos + }, + modes: { + default: [ + // The behavior to collapse/expand the Combo by double click + // It modifies the property 'collapsed' of the combo data + 'collapse-expand-combo', + ], + }, +}); +graph.data(data); +graph.render(); +``` + +img + +### Custom Behavior + +In the code above, we configured `'collapse-expand-combo'` for the graph, which means allowing user to double click a combo to make it collapsed or expanded. To expand/collapse the combo when clicking the marker instead of double clicking the combo, remove `'collapse-expand-combo'` configuration, and append the following code: + +```javascript +// collapse/expand when click the marker +graph.on('combo:click', (e) => { + if (e.target.get('name') === 'combo-marker-shape') { + // Collapse or expand the combo + graph.collapseExpandCombo(e.item); + + if (graph.get('layout')) graph.layout(); + // If there is a layout configured on the graph, relayout + else graph.refreshPositions(); // Refresh positions for items otherwise + } +}); +``` diff --git a/packages/site/docs/manual/middle/elements/combos/custom-combo.zh.md b/packages/site/docs/manual/middle/elements/combos/custom-combo.zh.md new file mode 100644 index 0000000000..f82f17727c --- /dev/null +++ b/packages/site/docs/manual/middle/elements/combos/custom-combo.zh.md @@ -0,0 +1,350 @@ +--- +title: 自定义 Combo +order: 2 +--- + +G6 提供了一系列[内置 Combo](/zh/docs/manual/middle/elements/combos/defaultCombo),包括 [circle](/zh/docs/manual/middle/elements/combos/built-in/circle)、[rect](/zh/docs/manual/middle/elements/combos/built-in/rect)。若内置 Combo 无法满足需求,用户还可以通过 `G6.registerCombo ('comboName', options, expendedComboName)` 进行**自定义扩展内置的 Combo**,方便用户开发更加定制化的 Combo,包括含有复杂图形的 Combo、复杂交互的 Combo、带有动画的 Combo 等。 + +在本章中,我们通过两个案例,讲解通过自定义扩展现有 Combo。 + +## Combo 接口 + +通过 [图形 Shape](/zh/docs/manual/middle/elements/shape/shape-keyshape) 章节的学习,我们应该已经知道了自定义 Combo 时需要满足以下两点: + +- 控制 Combo 的生命周期; +- 解析用户输入的数据,在图形上展示。 + +在自定义扩展内置 'circle' 或 'rect' Combo 时,API 中可以复写的方法如下: + +```javascript +G6.registerCombo( + 'comboName', + { + /** + * 绘制 Combo 中的图形。不需要为默认的 label 增加图形,父类方法会自动增加 label + * @param {Object} cfg Combo 的配置项 + * @param {G.Group} group 图形分组,Combo 中的图形对象的容器 + * @return {G.Shape} 返回一个绘制的图形作为 keyShape,通过 combo.get('keyShape') 可以获取。 + * 关于 keyShape 可参考文档 核心概念-节点/边/Combo-图形 Shape 与 keyShape + */ + drawShape(cfg, group) {}, + /** + * 绘制后的附加操作,默认没有任何操作 + * @param {Object} cfg Combo 的配置项 + * @param {G.Group} group 图形分组,Combo 中的图形对象的容器 + */ + afterDraw(cfg, group) {}, + /** + * 更新节点后的操作,新增的图形需要在这里控制其更新逻辑 + * @override + * @param {Object} cfg 节点的配置项 + * @param {Combo} combo 节点 + */ + afterUpdate(cfg, combo) {}, + /** + * 响应 Combo 的状态变化。 + * 在需要使用动画来响应状态变化时需要被复写,其他样式的响应参见下文提及的 [配置状态样式] 文档 + * @param {String} name 状态名称 + * @param {Object} value 状态值 + * @param {Combo} combo 节点 + */ + setState(name, value, combo) {}, + }, + // 被继承的 Combo 类型名,可选:'circle' 或 'rect' + extendedComboName, +); +``` + +## 注意事项(必读) + +因 Combo 更新逻辑的特殊性(需要根据其子元素信息自动更新自身位置和大小),自定义 Combo 时,与自定义节点/边有所不同: + +1. 不建议“从无到有”地自定义 Combo,**推荐使用继承的方式**扩展内置的 'circle' 或 'rect' Combo; +2. 在 `drawShape` 方法中不需要为 label 增加图形,父类方法将会自动增加默认的 label,可以通过配置的方式指定 label 的位置和样式; +3. 与自定义节点/边不同,这里**不建议复写 `update` 和 `draw` 方法**,否则会使 Combo 根据子元素更新的逻辑异常; +4. 复写的 `drawShape` 方法返回值与推荐继承内置的 'circle'、'rect' 的 keyShape 一致。即继承 'circle' 时,`drawShape` 方法应该返回一个 circle 图形;继承 'rect' 时,`drawShape` 方法应该返回一个 rect 图形; +5. 除 keyShape 外,自定义新增的图形需要**在 `afterUpdate` 中定义其位置更新逻辑**; +6. `setState` 只有在需要使用动画来响应状态变化时需要被复写,一般的样式响应状态变化可以通过 [配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) 实现。 + +## 1. 自定义扩展内置 Rect Combo + +Demo。 + +### 内置 Rect Combo 位置逻辑详解 + +首先,我们需要了解内置的 rect 类型的 Combo 内部的位置逻辑: + +- 下图灰色虚线框内部是子元素的分布范围,其宽高分别为 innerWidth 和 innerHeight; +- 灰色虚线框上下左右可以配置 `padding` 值,该 Combo 的 keyShape 真实绘制大小 width 与 height 是 innerWidth 和 innerHeight 加上了 padding 后的值; +- 一个 Combo 内部的图形以自身坐标系为参考,原点 (0, 0) 在灰色虚线框正中心; +- padding 值的上与下、左与右可能不相等,这就导致了该矩形的左上角坐标不是简单的 (-width / 2, -height / 2),而是通过如图标注的计算获得; +- rect 类型 Combo 的 label 默认位于矩形内部左上角,上边距为 refY,左边距为 refX。label 的位置(`position`)、`refX`、`refY` 可以在使用该类型 Combo 时配置。 + +img + +> Rect Combo 位置说明图 + +### 绘制图形 + +现在,我们自己实现一个如下图所示的 Combo 类型(下图展示空 Combo): + +img + +根据上述 [内置 Rect Combo 位置逻辑详解](./custom-combo#内置-rect-combo-位置逻辑详解),在扩展 rect 类型 Combo 时需要注意复写方法中 `x`、`y`、`width`、`height` 的设置 + +```javascript +G6.registerCombo( + 'cRect', + { + drawShape: function drawShape(cfg, group) { + const self = this; + // 获取配置中的 Combo 内边距 + cfg.padding = cfg.padding || [50, 20, 20, 20]; + // 获取样式配置,style.width 与 style.height 对应 rect Combo 位置说明图中的 width 与 height + const style = self.getShapeStyle(cfg); + // 绘制一个矩形作为 keyShape,与 'rect' Combo 的 keyShape 一致 + const rect = group.addShape('rect', { + attrs: { + ...style, + x: -style.width / 2 - (cfg.padding[3] - cfg.padding[1]) / 2, + y: -style.height / 2 - (cfg.padding[0] - cfg.padding[2]) / 2, + width: style.width, + height: style.height, + }, + draggable: true, + name: 'combo-keyShape', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }); + // 增加右侧圆 + group.addShape('circle', { + attrs: { + ...style, + fill: '#fff', + opacity: 1, + // cfg.style.width 与 cfg.style.heigth 对应 rect Combo 位置说明图中的 innerWdth 与 innerHeight + x: cfg.style.width / 2 + cfg.padding[1], + y: (cfg.padding[2] - cfg.padding[0]) / 2, + r: 5, + }, + draggable: true, + name: 'combo-circle-shape', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }); + return rect; + }, + // 定义新增的右侧圆的位置更新逻辑 + afterUpdate: function afterUpdate(cfg, combo) { + const group = combo.get('group'); + // 在该 Combo 的图形分组根据 name 找到右侧圆图形 + const circle = group.find((ele) => ele.get('name') === 'combo-circle-shape'); + // 更新右侧圆位置 + circle.attr({ + // cfg.style.width 与 cfg.style.heigth 对应 rect Combo 位置说明图中的 innerWdth 与 innerHeight + x: cfg.style.width / 2 + cfg.padding[1], + y: (cfg.padding[2] - cfg.padding[0]) / 2, + }); + }, + }, + 'rect', +); +``` + +值得注意的是,G6 3.3 需要用户为自定义节点中的图形设置 `name` 和 `draggable`。**其中,`name` 的值必须在同一元素类型内唯一**。`draggable` 为 `true` 是表示允许该图形响应鼠标的拖拽事件,只有 `draggable: true` 时,图上的交互行为 `'drag-combo'` 才能在该图形上生效。若上面代码仅在 keyShape 上设置了 `draggable: true`,而右侧圆图形上没有设置,则鼠标拖拽只能在 keyShape 上响应。 + +### 使用自定义 Combo + +现在,我们使用下面的代码使用 `'cRect'` 类型的 Combo: + +```javascript +const data = { + nodes: [ + { id: 'node1', x: 250, y: 100, comboId: 'combo1' }, + { id: 'node2', x: 300, y: 100, comboId: 'combo1' }, + ], + combos: [ + { id: 'combo1', label: 'Combo 1', parentId: 'combo2' }, + { id: 'combo2', label: 'Combo 2' }, + { id: 'combo3', label: 'Combo 3' }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 800, + // 全局 Combo 配置 + defaultCombo: { + // 指定 Combo 类型,也可以将 type 写到 combo 数据中 + type: 'cRect', + // ... 此处可配置默认 Combo 的其他样式 + }, +}); +graph.data(data); +graph.render(); +``` + +img + +## 2. 自定义扩展内置 Circle Combo + +Demo。 + +### 内置 Circle Combo 位置逻辑详解 + +如下面 Circle Combo 位置说明图所示,circle 类型的 Combo 内部的位置逻辑比 rect 类型简单,其 (x, y) 为圆心,`padding` 为一个数值: + +- 下图灰色虚线圈内部是子元素的分布范围,其半径为 innerR; +- 与 rect 不同的是,灰色虚线圈的 `padding` 是一个数值,即灰色虚线圈外围的 padding 是均匀的,该 Combo 的 keyShape 真实绘制半径 R = innerR + padding; +- 一个 Combo 内部的图形以自身坐标系为参考,原点 (0, 0) 在灰色虚线框正中心(由于 padding 是均匀的,所以原点也在 keyShape 正中心); +- circle 图形的 x 与 y 为其圆心 (0, 0); +- circle 类型 Combo 的 label 默认位于圆形外部正上方,距离圆形上边缘 refY。label 的位置(`position`)、`refX`、`refY` 可以在使用该类型 Combo 时配置。 + +img + +> Circle Combo 位置说明图 + +### 绘制图形 + +现在,我们自己实现一个如下图所示的 Combo 类型(下图展示空 Combo): + +img + +```javascript +// 定义下面需要使用的 symbol +const collapseIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ]; +}; +const expandIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ['M', x - r + r, y - r + 4], + ['L', x, y + r - 4], + ]; +}; + +G6.registerCombo( + 'cCircle', + { + drawShape: function draw(cfg, group) { + const self = this; + // 获取样式配置,style.r 是加上了 padding 的半径 + // 对应 Circle Combo 位置说明图中的 R + const style = self.getShapeStyle(cfg); + // 绘制一个 circle 作为 keyShape,与 'circle' Combo 的 keyShape 一致 + const circle = group.addShape('circle', { + attrs: { + ...style, + x: 0, + y: 0, + r: style.r, + }, + draggable: true, + name: 'combo-keyShape', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }); + // 增加下方 marker + const marker = group.addShape('marker', { + attrs: { + ...style, + fill: '#fff', + opacity: 1, + x: 0, + y: style.r, + r: 10, + symbol: collapseIcon, + }, + draggable: true, + name: 'combo-marker-shape',// 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }); + + return circle; + }, + // 定义新增的下方 marker 的位置更新逻辑 + afterUpdate: function afterUpdate(cfg, combo) { + const self = this; + // 获取样式配置,style.r 是加上了 padding 的半径 + // 对应 Circle Combo 位置说明图中的 R const style = self.getShapeStyle(cfg); + const group = combo.get('group'); + // 在该 Combo 的图形分组根据 name 找到下方 marker + const marker = group.find((ele) => ele.get('name') === 'combo-marker-shape'); + // 更新 marker + marker.attr({ + x: 0, + y: style.r, + // 数据中的 collapsed 代表该 Combo 是否是收缩状态,根据该字段更新 symbol + symbol: cfg.collapsed ? expandIcon : collapseIcon, + }); + }, + }, + 'circle', +); +``` + +值得注意的是,G6 3.3 需要用户为自定义节点中的图形设置 `name` 和 `draggable`。**其中,`name` 的值必须在同一元素类型内唯一**。`draggable` 为 `true` 是表示允许该图形响应鼠标的拖拽事件,只有 `draggable: true` 时,图上的交互行为 `'drag-combo'` 才能在该图形上生效。若上面代码仅在 keyShape 上设置了 `draggable: true`,而右侧圆图形上没有设置,则鼠标拖拽只能在 keyShape 上响应。 + +### 使用自定义 Combo + +现在,我们使用下面的代码使用 `'cCircle'` 类型的 Combo: + +```javascript +const data = { + nodes: [ + { id: 'node1', x: 250, y: 100, comboId: 'combo1' }, + { id: 'node2', x: 300, y: 100, comboId: 'combo1' }, + ], + combos: [ + { id: 'combo1', label: 'Combo 1', parentId: 'combo2' }, + { id: 'combo2', label: 'Combo 2' }, + { id: 'combo3', label: 'Combo 3' }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 800, + // 全局 Combo 配置 + defaultCombo: { + // 指定 Combo 类型,也可以将 type 写到 combo 数据中 + type: 'cCircle', + labelCfg: { + refY: 2, + }, + // ... 此处可配置默认 Combo 的其他样式 + }, + modes: { + default: [ + // 配置展开/收缩 Combo 交互,双击 Combo 可以触发 + // 将会修改响应 Combo 数据中的 collapsed 字段,从而标识该 Combo 是否处于收缩状态 + 'collapse-expand-combo', + ], + }, +}); +graph.data(data); +graph.render(); +``` + +img + +### 自定义交互 + +在上面代码中,实例化图时为图配置了 `'collapse-expand-combo'` 交互,即双击 Combo 可以展开和收起。若我们希望在单击 Combo 下方的 marker 时,展开/收起 Combo,则可以去掉 `'collapse-expand-combo'` 配置,并添加如下监听代码: + +```javascript +// collapse/expand when click the marker +graph.on('combo:click', (e) => { + if (e.target.get('name') === 'combo-marker-shape') { + // Collapse or expand the combo + graph.collapseExpandCombo(e.item); + + if (graph.get('layout')) graph.layout(); + // If there is a layout configured on the graph, relayout + else graph.refreshPositions(); // Refresh positions for items otherwise + } +}); +``` diff --git a/packages/site/docs/manual/middle/elements/combos/defaultCombo.en.md b/packages/site/docs/manual/middle/elements/combos/defaultCombo.en.md new file mode 100644 index 0000000000..e60e47f929 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/combos/defaultCombo.en.md @@ -0,0 +1,426 @@ +--- +title: Overview of Combos +order: 0 +--- + +> Node Combo is a new feature for V3.5. The [node group](/en/docs/manual/middle/elements/shape/graphics-group) will be deprecated. We recommend to use Combo for node grouping. Demo.
img + +The built-in Combos in G6 include circle and rect types.
img + +In this document, we will briefly introduce the built-in Combos in G6, the common property, and the way to configure the combo type. To know more about each type of built-in combos in G6, please refer to the corresponding documents in this directory. + +⚠️ Attention: Must set the `groupByTypes` to `false` when instantiating the graph, which will result in rendering result with reasonable visual zIndex for combos. + +## Data Structure + +To keep the stability of the structure of the source data, we do some compatible changes to introduce combos: + +1. `combos` array to contains all the combos data, and each of them has the properties: + +| Property | Type | Required | Example | Description | +| --- | --- | --- | --- | --- | +| id | string | true | 'comboA' | The uinique ID for the combo. **MUST** be a unique string | +| parentId | string | false | 'comboB' | The ID of the parent combo | +| size | false | Number / Array | 30 or [30, 20] | The MINIMUM size of the combo (not for fixing the size of combo). The default value for 'circle' type Combo is 20, [20, 5] for 'rect' type | +| fixSize | number / number[] | false | 10 or [ 10, 20 ] | Fix the size of the Combo. If it is not assigned, the rendering size of the combo depends on the sizes and distribution of its children items. If the `fixSize` is assigned but the `fixCollapseSize` is not, the size of the collapsed combo will still be `fixSize` | +| fixCollapseSize | number / number[] | false | 10 or [ 10, 20 ] | Fix the size of the collapsed Combo. If it is not assigned and the `fixSize` is assigned, the size of the collapsed Combo is `fixSize`; and if `fixCollapseSize` and `fixSize` are both not assigned, the size of the collapsed Combo is `size` | | +| padding | Number / Number[] | 10 or [ 10, 20, 10, 20 ] | The padding inside the combo | +| label | string | false | 'combo A' | The label text of the combo | +| style | Object | false | | The style configuration of the combo, details are in [Built-in Combo Configuration](/en/docs/manual/middle/elements/combos/defaultCombo#style) and documents of each type of combo | +| labelCfg | Object | false | | The label configuration of the combo, details are in [Built-in Combo Configuration](/en/docs/manual/middle/elements/combos/defaultCombo#label-and-labelcfg) and documents of each type of combo | +| collapsed | Boolean | false | true | Whether the combo is collapsed. Config it with true before render will make the combo collapsed by default | +| collapsedSubstituteIcon | Object | false | { show: true } | *Supported by v4.6.8* The image shows while the combo is collapsed | + +An example for the data item for a combo + +```javascript +{ + id: 'comboA', + label: 'A', + parentId: 'comboC' +}, +``` + +2. Introduce `comboId` in data items of nodes to indicate the affiliation. + +```javascript +{ + nodes: [ + { + id: 'node1', + comboId: 'comboA' // node1 belongs to comboA + }, + { + id: 'node2', + comboId: 'comboB' // node2 belongs to comboB + }, + { + id: 'node3' // node3 belongs to no one + }, + // ... + ], + edges: [ + // ... + ], + combos: [ + { // define comboA + id: 'comboA', + label: 'A', + parentId: 'comboC' + }, + { // define comboB + id: 'comboB', + parentId: 'comboB' + }, + { // define comboC, an empty combo + id: 'comboC' + }, + // ... + ] +} +``` + +## Types of Default Combos + +The table below shows the built-in Combos and their special properties: + +| Name | Description | Default | +| --- | --- | --- | +| circle | Circle Combo:
- `size` is a number representing the diameter
- The circle is centered at the combo position
- `color` takes effect on the stroke
- The label is placed on the top of the circle by default
- More properties are described in [circle](/en/docs/manual/middle/elements/combos/built-in/circle)
- Demo | img | +| rect | Rect Combo:
- `size` is an array, e.g. [100, 50]
- The rect in centered at the combo position
- `color` takes effect on the stroke
- The label is placed on the left top of the circle by default
- More properties are described in [rect](/en/docs/manual/middle/elements/combos/built-in/rect)
- Demo | img | + +## Common Property + +| Name | Required | Type | Example | Remark | +| --- | --- | --- | --- | --- | +| id | true | String | 'comboA' | The id of the Combo, **Must** be a unique string | +| type | false | String | 'rect' | The shape type of the Combo. It can be the type of built-in Combo, or the custom Combo. `'circle'` by default | +| parentId | string | false | 'comboB' | The ID of the parent Combo | +| size | false | Number / Array | 30 or [30, 20] | The MINIMUM size of the combo (not for fixing the size of combo). The default value for 'circle' type Combo is 20, [20, 5] for 'rect' type | +| fixSize | number / number[] | false | 10 or [ 10, 20 ] | Fix the size of the Combo. If it is not assigned, the rendering size of the combo depends on the sizes and distribution of its children items. If the `fixSize` is assigned but the `fixCollapseSize` is not, the size of the collapsed combo will still be `fixSize` | +| fixCollapseSize | number / number[] | false | 10 or [ 10, 20 ] | Fix the size of the collapsed Combo. If it is not assigned and the `fixSize` is assigned, the size of the collapsed Combo is `fixSize`; and if `fixCollapseSize` and `fixSize` are both not assigned, the size of the collapsed Combo is `size` | | +| padding | Number / Number[] | false | 10 or [ 10, 20, 10, 20 ] | The padding of the Combo. The default value for 'circle' type Combo is 25, [25, 20, 15, 20] for 'rect' | +| style | false | Object | | The Combo style | +| label | false | String | 'Combo A' | The label text of the combo | +| labelCfg | false | Object | | The configurations of the combo | + +### style + +`style` is an object to configure the filling color, stroke color, shadow, and so on. Here is the commonly used properties in `style`: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| fill | false | String | The filling color | +| stroke | false | String | The stroke color | +| lineWidth | false | Number | The line width of the stroke | +| shadowColor | false | String | The shadow color | +| shadowBlur | false | Number | The blur of the shadow | +| shadowOffsetX | false | Number | The x offset of the shadow | +| shadowOffsetY | false | Number | The y offset of the shadow | +| opacity | false | Number | The alpha or transparency of the combo | +| fillOpacity | false | Number | The filling alpha or transparency of the combo | +| cursor | false | String | The type of the mouse when hovering the combo. The options are the same as [cursor in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) | + +Configure `style` globally when instantiating the Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + // ... Other properties for combos + style: { + fill: '#steelblue', + stroke: '#eaff8f', + lineWidth: 5, + // ... Other style properties + }, + }, +}); +``` + +### label and labelCfg + +`label` is a string which indicates the content of the label.
`labelCfg` is an object to configure the label. The commonly used configurations of `labelCfg`: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| position | false | String | The relative positions to the combo. Options:  `'center'`, `'top'`, `'left'`, `'right'`, `'bottom'`. `'top'` by default | +| refX | false | Number | The label's offset along the x-axis | +| refY | false | Number | The label's offset along the y-axis | +| style | false | Object | The style property of the label | + +The commonly used configurations for the `style` in the above table are: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| fill | false | String | The color of the label | +| stroke | false | String | The stroke color of the label | +| lineWidth | false | Number | The line width of the label | +| opacity | false | Number | The opacity of the label | +| fontFamily | false | String | 文本字体 | +| fontSize | false | Number | The font size of the label | +| ... The label styles of Combo, Node and Edge are the same, summarized in [Text Shape API](/en/docs/api/shapeProperties/#text) | | | | + +The following code shows how to configure `label` and `labelCfg` globally when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + // ... Other properties for combos + labelCfg: { + position: 'top', + offset: [10, 10, 10, 10], + style: { + fill: '#666', + }, + }, + }, +}); +``` + +### collapsedSubstituteIcon + +*Supported by v4.6.8* `collapsedSubstituteIcon` is an object to configure a substitute image for the collapsed combo. Here is the commonly used properties in `collapsedSubstituteIcon`: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| show | false | Boolean | Whether show the substitute image when the combo is collapsed. false by default | +| img | false | String | The image url for the Icon, default image: download | +| width | false | Number | The width of the icon image. The width of collapsed combo will take effect by default | +| height | false | Number | The height of the icon image. The height of collapsed combo will take effect by default | + + +Configure `collapsedSubstituteIcon` globally when instantiating the Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + // ... Other properties for combos + collapsedSubstituteIcon: { + show: true, + img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IEQFS5VtXX8AAAAAAAAAAABkARQnAQ', + // ... other properties + }, + }, +}); +``` + +## Configure Combos + +There are three methods to configure combos: Configure combos globally when instantiating a Graph; Configure combos in their data; Configure combos by `graph.combo(comboFn)`. Their priorities are: + +`graph.combo(comboFn` > Configure in data > Configure globally + +That means, if there are same configurations in different ways, the way with higher priority will take effect. + +⚠️ Attention: Expect for `id`, `parentId`, and `label` which should be assigned to every single combo data, the other configurations in [The Common Property](#common-property) and in each combo type (refer to doc of each combo type) support to be assigned by the three ways. + +### Configure Globally When Instantiating Graph + +Assign `defaultCombo` to configure all the combos globally: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + type: 'circle', + // Other properties for all the combos + }, +}); +``` + +### Configure in Data + +To configure different combos with different properties, you can write the properties into their data individually: + +```javascript +const data = { + nodes: [ + ... // nodes + ], + edges: [ + ... // edges + ], + combos: [{ + id: 'combo0', + size: 100, + type: 'circle', + ... // Other properties for this combo + style: { + ... // Style properties for this combo. Different styles for different types of combos can be refered to the subdocuments + } + },{ + id: 'combo1', + size: [50, 100], + type: 'rect', + ... // Other properties for this combo + style: { + ... // Style properties for this combo. Different styles for different types of combos can be refered to the subdocuments + } + }, + // other combos + ] +} +``` + +### Configure with graph.combo(comboFn) + +By this way, we can configure different combos with different properties. + +
⚠️Attention: + +- `graph.combo(comboFn)` must be called **before calling render()**. It does not take effect otherwise; +- It has the highest priority that will override the same properties configured by other ways; +- Each combo will be updated when adding or updating items. It will cost a lot when the amount of the data is large. + +```javascript +// const data = ... +// const graph = ... +graph.combo((combo) => { + return { + id: combo.id, + type: 'rect', + style: { + fill: 'blue', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +## Combo Interaction + +To allow the users to interact with the combos, we implemented three built-in behaviors: `drag-combo`, `collapse-expand-combo`, and `drag-node` [Behavior](/en/docs/manual/middle/states/defaultBehavior)s. + +### drag-combo + +`'drag-combo'`behavior supports dragging a combo to re-arrange its position or its hierarchy. + +img + +### collapse-expand-combo + +`'collapse-expand-combo'`behavior supports collapsing or expanding the combo by double clicking. The children will be hidden when the combo is collapsed, and the edges related to the children will link to the combo. If the graph has layout configuration and the `relayout` for this behavior is `true` (`true` by default), this behavior will trigger re-layout. If you do not want re-layout the graph after collapsing or expanding a combo, assign `relayout: false` for this behavior, or use combo's click listener and [graph.collapseExpandCombo API](/en/docs/api/Graph#collapseexpandcombocombo) instead. + +img + +### drag-node + +`'drag-node'` behavior allows end users to drag the node to re-arrange the position and change the hierarchy of the node and its parent combo. + +img + +### Configure the Behaviors + +The code below shows how to configure the behaviors onto the graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + modes: { + default: ['drag-combo', 'collapse-expand-combo', 'drag-node'], + }, +}); +``` + +## Example + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'Node1', + comboId: 'rect_combo', + }, + { + id: 'node2', + label: 'Node 2', + }, + ], + combos: [ + { + id: 'circle_combo', + type: 'circle', + label: 'Circle', + }, + { + id: 'rect_combo', + type: 'rect', + label: 'Rect', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'mountNode', + width: 1500, + height: 300, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, +}); +graph.data(data); +graph.render(); +``` + +The result:
img + +### Adjust the Properties + +By writing the properties into the data, we adjust the label position, color, and styles of the combo with `'rect_combo'` as its id. Replace the following code to the code about `'rect_combo'`'s data to obtain the result. + +```javascript +{ + id: 'rect_combo', + type: 'rect', + label: 'Rect Combo', + labelCfg: { + position: 'bottom', + refX: 5, + refY: -12, + style: { + fill: '#fff' + } + }, + style: { + fill: '#fa8c16', + stroke: '#000', + lineWidth: 2 + } +} +``` + +img + +## Comparison for Combo and Hull + +combo-hull + +## Related Reading + +- [State](/en/docs/manual/middle/states/state) —— Change the styles during the interaction process; +- [Hull](/en/docs/api/graphFunc/hull). diff --git a/packages/site/docs/manual/middle/elements/combos/defaultCombo.zh.md b/packages/site/docs/manual/middle/elements/combos/defaultCombo.zh.md new file mode 100644 index 0000000000..ccd02e3715 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/combos/defaultCombo.zh.md @@ -0,0 +1,438 @@ +--- +title: Combo 总览 +order: 0 +--- + +> V3.5 后支持的全新节点分组 Combo 机制。[原节点分组](/zh/docs/manual/middle/elements/shape/graphics-group)即将废除。 + +对于熟悉图可视化类库的用户来说,节点分组是非常实用的一个功能。此前,G6 已经存在一个节点分组 Node Group 功能,但它的机制无法支持一些较复杂的功能,例如:带有节点分组的图布局、自定义 Combo、嵌套节点分组的均匀 padding、节点与分组的边、分组与分组的边、空的节点分组等。V3.5 推出了全新的节点分组 Combo 机制,能够支持所有常用功能,参考 Demo
img + +G6 的内置 Combo 包括 circle 和 rect 两种类型,分别如下图所示。
img + +本文将概述 G6 中的 Combo 的数据结构、各个内置 Combo 类型、内置 Combo 的通用属性、配置方法。各类型 Combo 详细配置项及配置方法见本目录下相应文档。 + +⚠️ 注意: 使用 Combo 时,必须在示例化图时配置 `groupByTypes` 设置为 `false`,图中元素的视觉层级才能合理。 + +## 数据结构 + +为保持 G6 源数据数据结构的稳定性,我们在原来的数据结构上做了如下修改: + +1. 新增 `combos` 数组,用于定义图上所有的 Combo 及其配置。`combos` 数组中的一个数据项有如下属性: + +| 属性名 | 类型 | 是否必须 | 示例 | 解释 | +| --- | --- | --- | --- | --- | +| id | string | true | 'comboA' | 一个 Combo 的唯一标识,**必须是 string 类型,必须唯一** | +| parentId | string | false | 'comboB' | 该 Combo 的父 Combo 的 ID | +| padding | Number / Number[] | false | 10 或 [ 10, 20, 10, 20 ] | 该 Combo 内边距 | +| size | number / number[] | false | 10 或 [ 10, 20 ] | 该 Combo 的最小尺寸(非固定尺寸),默认 'circle' 类型 Combo 的 size 为 20,'rect' 类型的为 [20, 5] | +| fixSize | number / number[] | false | 10 或 [ 10, 20 ] | 固定该 Combo 的尺寸,不指定时 Combo 大小由内部元素的分布和大小来决定。若指定了 fixSize 而没有指定 fixCollapseSize,则即使该 Combo 在收起状态下仍然保持 fixSize 指定的尺寸 | +| fixCollapseSize | number / number[] | false | 10 或 [ 10, 20 ] | 固定该 Combo 收起时的尺寸,不指定时,若未指定 fixSize 则由 size 决定收起时的尺寸,否则统一为 fixSize 尺寸 | +| label | string | false | 'combo A' | 该 Combo 的文本标签 | +| style | Object | false | | 该 Combo 的样式配置项,详见[内置 Combo 配置文档](/zh/docs/manual/middle/elements/combos/defaultCombo#样式属性-style)及各类型 Combo 的文档 | +| labelCfg | Object | false | | 该 Combo 的文本标签样式配置项,详见[内置 Combo 配置文档](/zh/docs/manual/middle/elements/combos/defaultCombo#标签文本-label-及其配置-labelcfg)及各类型 Combo 的文档 | + +`combos` 数组中一个数据项的示例: + +```javascript +{ + id: 'comboA', + label: 'A', + parentId: 'comboC' +}, +``` + +2. 在 nodes 数组中的数据项内加入 `comboId` 属性,表示该节点与某个 Combo 的从属关系。 + +```javascript +{ + nodes: [ + { + id: 'node1', + comboId: 'comboA' // node1 属于 comboA + }, + { + id: 'node2', + comboId: 'comboB' // node2 属于 comboB + }, + { + id: 'node3' // node3 不属于任何 combo + }, + // ... + ], + edges: [ + // ... + ], + combos: [ + { // 定义 comboA + id: 'comboA', + label: 'A', + parentId: 'comboC' + }, + { // 定义 comboB + id: 'comboB', + parentId: 'comboB' + }, + { // 定义 comboC,这是一个空的 combo + id: 'comboC' + }, + // ... + ] +} +``` + +## 内置 Combo 类型说明 + +下面表格中显示了内置的各类 Combo,同时对一些特殊的字段进行了说明: + +| 名称 | 描述 | 默认示例 | +| --- | --- | --- | +| circle | 圆形:
- `size` 是单个数字,表示直径
- 圆心位置对应 Combo 的位置
- `color` 字段默认在描边上生效
- 标签文本默认在 Combo 正上方
- 更多字段见 [Circle](/zh/docs/manual/middle/elements/combos/built-in/circle) Combo 教程
- Demo | img | +| rect | 矩形:
- `size` 是数组,例如:[100, 50]
- 矩形的中心位置是 Combo 的位置,而不是左上角
- `color` 字段默认在描边上生效
- 标签文本默认在 Combo 左上角
- 更多字段见 [Rect](/zh/docs/manual/middle/elements/combos/built-in/rect) Combo 教程
- Demo | img | + +## Combo 的通用属性 + +所有内置的 Combo 支持的通用属性: + +| 属性名 | 类型 | 是否必须 | 示例 | 说明 | +| --- | --- | --- | --- | --- | +| id | string | true | 'comboA' | 一个 Combo 的唯一标识,**必须是 string 类型,必须唯一** | +| type | string | false | 'rect' | 指定该 Combo 的类型,可以是内置 Combo 的类型名,也可以是自定义 Combo 的类型名。默认是 `'circle'` | +| parentId | string | false | 'comboB' | 该 Combo 的父 Combo 的 ID | +| size | false | Number / Array | 30 或 [30, 20] | Combo 的最小尺寸(非固定尺寸),默认 'circle' 类型 Combo 的 size 为 20,'rect' 类型的为 [20, 5] | +| fixSize | number / number[] | false | 10 或 [ 10, 20 ] | 固定该 Combo 的尺寸,不指定时 Combo 大小由内部元素的分布和大小来决定。若指定了 fixSize 而没有指定 fixCollapseSize,则即使该 Combo 在收起状态下仍然保持 fixSize 指定的尺寸 | +| fixCollapseSize | number / number[] | false | 10 或 [ 10, 20 ] | 固定该 Combo 收起时的尺寸,不指定时,若未指定 fixSize 则由 size 决定收起时的尺寸,否则统一为 fixSize 尺寸 | | +| padding | Number / Number[] | false | 10 或 [ 10, 20, 10, 20 ] | 该 Combo 内边距,默认 'circle' 类型 Combo 的 padding 为 25,'rect' 类型的为 [25, 20, 15, 20] | +| style | Object | false | | 该 Combo 的样式配置项 | +| label | string | false | 'Combo A' | 该 Combo 的文本标签 | +| labelCfg | Object | false | | 该 Combo 的文本标签样式配置项 | +| collapsed | Boolean | false | false | 该 Combo 是否收起。在渲染前配置 collapsed: true 在 combo 数据中,初次渲染时将默认收起 | +| collapsedSubstituteIcon | Object | false | { show: true } | *v4.6.8 起支持* 该 Combo 在收起状态下展示的图片 | + +### 样式属性 style + +Object 类型。通过 `style` 配置来修改 Combo 的填充色、边框颜色、阴影等属性。下表是 `style` 对象中常用的配置项: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| fill | false | String | Combo 填充色 | +| stroke | false | String | Combo 的描边颜色 | +| lineWidth | false | Number | 描边宽度 | +| shadowColor | false | String | 阴影颜色 | +| shadowBlur | false | Number | 阴影范围 | +| shadowOffsetX | false | Number | 阴影 x 方向偏移量 | +| shadowOffsetY | false | Number | 阴影 y 方向偏移量 | +| opacity | false | Number | 设置绘图的当前 alpha 或透明值 | +| fillOpacity | false | Number | 设置填充的 alpha 或透明值 | +| cursor | false | String | 鼠标在该 Combo 上时的鼠标样式,[CSS 的 cursor](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) 选项都支持 | + +下面代码演示在实例化图时全局配置方法中配置 `style`: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + defaultCombo: { + // ... 其他属性 + style: { + fill: '#steelblue', + stroke: '#eaff8f', + lineWidth: 5, + // ... 其他属性 + }, + }, +}); +``` + +### 标签文本 label 及其配置 labelCfg + +`label` String 类型。标签文本的文字内容。
`labelCfg` Object 类型。配置标签文本。下面是 `labelCfg` 对象中的常用配置项: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| position | false | String | 文本相对于 Combo 的位置,目前支持的位置有:  `'center'`,`'top'`,`'left'`,`'right'`,`'bottom'`。默认为 `'top'` | +| refX | false | Number | 文本的偏移,在 x 方向上的偏移量 | +| refY | false | Number | 文本的偏移,在 y 方向上的偏移量 | +| style | false | Object | 标签的样式属性。 | + +上表中的标签的样式属性 `style` 的常用配置项如下: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| fill | false | String | 文本颜色 | +| stroke | false | String | 文本描边颜色 | +| lineWidth | false | Number | 文本描边粗细 | +| opacity | false | Number | 文本透明度 | +| fontSize | false | Number | 文本字体大小 | +| fontFamily | false | String | 文字字体 | +| ... Combo 标签与节点、边标签样式属性相同,统一整理在 [Text 图形 API](/zh/docs/api/shapeProperties/#文本-text) | + +下面代码演示在实例化图时全局配置方法中配置  `label` 和  `labelCfg`。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + defaultCombo: { + // ... 其他属性 + labelCfg: { + position: 'top', + offset: [10, 10, 10, 10], + style: { + fill: '#666', + }, + }, + }, +}); +``` + +### 收起时的 Icon collapsedSubstituteIcon + +*v4.6.8 起支持* Object 类型。通过 `collapsedSubstituteIcon` 配置 Combo 在收起状态下,展示在中心的图片 Icon。下表是 `collapsedSubstituteIcon` 对象中常用的配置项: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| show | false | Boolean | 是否展示,默认不展示 | +| img | false | String | Icon 图片地址,默认为: download | +| width | false | Number | Icon 图片的宽度,不设置则将使用 Combo 收起时的宽度 | +| height | false | Number | Icon 图片高度,不设置则将使用 Combo 收起时的高度 | + + +下面代码演示在实例化图时全局配置方法中配置 `collapsedSubstituteIcon`: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + defaultCombo: { + // ... 其他属性 + collapsedSubstituteIcon: { + show: true, + img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IEQFS5VtXX8AAAAAAAAAAABkARQnAQ', + // ... 其他属性 + }, + }, +}); +``` + +## Combo 的配置方法 + +配置 Combo 的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.combo(comboFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.combo(comboFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +即有相同的配置项时,优先级高的方式将会覆盖优先级低的。 + +⚠️ 注意: 除 `id`、`parentId`、`label` 应当配置到每个 Combo 数据中外,其余的 [Combo 的通用属性](#combo-的通用属性) 以及各个 Combo 类型的特有属性(见内置 Combo 类型)均支持三种配置方式。 + +### 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultCombo` 配置 Combo ,这里的配置是全局的配置,将会在所有 Combo 上生效。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + defaultCombo: { + type: 'circle', + // 其他配置 + }, +}); +``` + +### 在数据中动态配置 + +如果需要为不同 Combo 进行不同的配置,可以将配置写入到 Combo 数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + ... // 节点 + ], + edges: [ + ... // 边 + ], + combos: [{ + id: 'combo0', + size: 100, + type: 'circle', + ... // 其他属性 + style: { + ... // 样式属性,每种 Combo 的详细样式属性参见各类型 Combo 文档 + } + },{ + id: 'combo1', + size: [50, 100], + type: 'rect', + ... // 其他属性 + style: { + ... // 样式属性,每种 Combo 的详细样式属性参见各类型 Combo 文档 + } + }, + // 其他 combo + ] +} +``` + +### 使用 graph.combos() + +该方法可以为不同 combo 进行不同的配置。 + +
**提示:** + +- 该方法必须**在 render 之前调用**,否则不起作用; +- 由于该方法优先级最高,将覆盖其他地方对 combo 的配置,这可能将造成一些其他配置不生效的疑惑; +- 该方法在增加元素、更新元素时会被调用,如果数据量大、每个 Combo 上需要更新的内容多时,可能会有性能问题。 + +```javascript +// const data = ... +// const graph = ... +graph.combo((combo) => { + return { + id: combo.id, + type: 'rect', + style: { + fill: 'blue', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +## Combo 交互 + +只是简单地将 Combo 渲染出来,并没有多大的实用价值,只有支持一系列的交互操作后,才能最大程度地体现 Combo 的价值。 + +在 G6 中,我们内置了 `drag-combo`、`collapse-expand-combo`、`drag-node` 三个 [Behavior](/zh/docs/manual/middle/states/defaultBehavior)。 + +#### drag-combo + +`drag-combo` Behavior,支持拖动 Combo ,拖动 Combo 过程中,会动态改变 Combo 中节点和边的位置,在拖拽完成以后,保持 Combo 和节点的相对位置不变。还可以通过拖拽改变 Combo 的从属关系。 + +img + +#### collapse-expand-combo + +`collapse-expand-combo` Behavior,支持双击 Combo 收起和展开 Combo ,收起 Combo 以后,隐藏 Combo 中的所有节点,外部节点和 Combo 中节点有连线的情况下,所有连接会连接到 Combo 上面。若图配置有布局且该 behavior 的 `relayout` 配置项为 `true`(默认为 `true`),则该 behavior 被触发后会触发图的重新布局。若希望避免重新布局,可以配置 `relayout` 为 `false` ,或通过监听 combo 点击事件和 [graph.collapseExpandCombo API](/zh/docs/api/Graph#collapseexpandcombocombo) 控制收缩展开逻辑。 + +img + +#### drag-node + +拖拽节点过程中,动态改变节点与父 Combo 的从属关系。 + +img + +#### 配置交互 + +通过下面代码在实例化图时将三个 behavior 配置到图上即可使用上述交互: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, + modes: { + default: ['drag-combo', 'collapse-expand-combo', 'drag-node'], + }, +}); +``` + +## 示例 + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'Node1', + comboId: 'rect_combo', + }, + { + id: 'node2', + label: 'Node 2', + }, + ], + combos: [ + { + id: 'circle_combo', + type: 'circle', + label: 'Circle', + }, + { + id: 'rect_combo', + type: 'rect', + label: 'Rect', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'mountNode', + width: 1500, + height: 300, + // 必须将 groupByTypes 设置为 false,带有 combo 的图中元素的视觉层级才能合理 + groupByTypes: false, +}); +graph.data(data); +graph.render(); +``` + +显示结果:
img + +### 调整 Combo 配置 + +下面演示通过将配置写入数据的方式,调整 `id` 为 `'rect_combo'` 的文本位置、颜色、样式。将下面代码替换上面代码中 `id` 为  `'rect_combo'` 的 combo 数据即可生效。 + +```javascript +{ + id: 'rect_combo', + type: 'rect', + label: 'Rect Combo', + labelCfg: { + position: 'bottom', + refX: 5, + refY: -12, + style: { + fill: '#fff' + } + }, + style: { + fill: '#fa8c16', + stroke: '#000', + lineWidth: 2 + } +} +``` + +img + +## 适用场景 + +1. 风控、反洗钱、保险骗保、网络诈骗、信用卡诈骗等场景下团伙分析; +2. 特征分析:同一个分组中的节点在某些特征上面比较相似; +3. 整理节点:当类似的节点放到一个分组中,只渲染分组,不渲染节点,减少干扰元素。 + +## 与轮廓包裹 Hull 的异同 + +combo-hull + +## 相关阅读 + +- [状态 State](/zh/docs/manual/middle/states/state) —— 交互过程中的样式变化; +- [轮廓包裹 Hull](/zh/docs/api/graphFunc/hull) —— 轮廓包裹分组。 \ No newline at end of file diff --git a/packages/site/docs/manual/middle/elements/edges/arrow.en.md b/packages/site/docs/manual/middle/elements/edges/arrow.en.md new file mode 100644 index 0000000000..d585ec8f6d --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/arrow.en.md @@ -0,0 +1,109 @@ +--- +title: Arrow +order: 2 +--- + +No matter built-in edges or [custom edges](/en/docs/manual/middle/elements/edges/custom-edge), arrows can be assigned to the end or begin position of an edge. There are three kinds of arrows in G6: default arrow, built-in arrow, and custom arrow. + +
img + +## Default Arrow + +img + +### Usage + +Configure the `endArrow` or `startArrow` to `true` in the `style` of an edge: + +```javascript +style: { + endArrow: true, + startArrow: true +} +``` + +## Built-in Arrow + +Supported by v3.5.8 and later versions. + +### Overview + +| Name | Parameters | Usage | Result | +| --- | --- | --- | --- | +| triangle |
The paramters are arrow's width (10 by default), length (15 by default), and offset (0 by default, corresponds to `d`), respectively.
| endArrow: {
path: G6.Arrow.triangle(10, 20, 25),
d: 25
} | img | +| vee |
The paramters are arrow's width (15 by default), length (20 by default), and offset (0 by default, corresponds to `d`), respectively.
| endArrow: {
path: G6.Arrow.vee(10, 20, 25),
d: 25
} | img | +| circle |
The paramters are arrow's radius (5 by default) and offset (0 by default, corresponds to `d`) respectively.
| endArrow: {
path: G6.Arrow.circle(10, 25),
d: 25
} | img | +| diamond |
The paramters are arrow's width (15 by default), length (15 by default), and offset (0 by default, corresponds to `d`), respectively.
| endArrow: {
path: G6.Arrow.diamond(10, 20, 25),
d: 25
} | img | +| rect |
The paramters are arrow's width (10 by default), length (10 by default), and offset (0 by default, corresponds to `d`), respectively.
| endArrow: {
path: G6.Arrow.rect(10, 20, 25),
d: 25
} | img | +| triangleRect |
The paramters are triangle's width (15 by default), triangle's length (20 by default), rect's width (15 by default), rect's length (3 by default), gap between the triangle and the rect (3 by default), and offset (0 by default, corresponds to `d`), respectively.
| endArrow: {
path: G6.Arrow.triangleRect(15, 15, 15, 3, 5, 25),
d: 25
} | img | + +### Usage + +Call `G6.Arrow.arrowName` for the `path` in `style`'s `endArrow` or `startArrow`: + +```javascript +style: { + endArrow: { + path: G6.Arrow.triangle(10, 20, 25), // Using the built-in edges for the path, parameters are the width, length, offset (0 by default, corresponds to d), respectively + d: 25 + }, + startArrow: { + path: G6.Arrow.vee(15, 20, 15), // Using the built-in edges for the path, parameters are the width, length, offset (0 by default, corresponds to d), respectively + d: 15 + }, +} +``` + +## Custom Arrow + +Please follow the [Custom Arrow](/en/docs/manual/middle/elements/edges/custom-edge) in the Advanced Doc. + +## Configure the Arrow Style + +Only built-in edges and custom edges can be configured. + +#### Configurations + +| Name | Required | Type | Description | +| --- | --- | --- | --- | +| fill | false | String | Filling color. No fill by default | +| stroke | false | String | The stroke color. Same as the edge by default | +| lineWidth | false | Number | The line width. Same as the edge by default | +| opacity | false | Number | Opacity | +| strokeOpacity | false | Number | The stroke opacity | +| shadowColor | false | String | The color of the shadow | +| shadowBlur | false | Number | The blur degree of the shadow | +| shadowOffsetX | false | Number | The x offset of the shadow | +| shadowOffsetY | false | Number | The y offset of the shadow | +| lineDash | false | Array | The style of the dash line. It is an array that describes the length of gaps and line segments. If the number of the elements in the array is odd, the elements will be dulplicated. Such as [5, 15, 25] will be regarded as [5, 15, 25, 5, 15, 25] | + +#### Usage + +```javascript +// Built-in Arrow +style: { + endArrow: { + path: G6.Arrow.triangle(10, 20, 25), + d: 25, + fill: '#f00', + stroke: '#0f0', + opacity: 0.5, + lineWidth: 3, + // ... + }, +} + + +// Custom Arrow +style: { + endArrow: { + path: 'M 0,0 L 20,10 L 20,-10 Z', + d: 5, + fill: '#f00', + stroke: '#0f0', + opacity: 0.5, + lineWidth: 3, + // ... + }, +} +``` diff --git a/packages/site/docs/manual/middle/elements/edges/arrow.zh.md b/packages/site/docs/manual/middle/elements/edges/arrow.zh.md new file mode 100644 index 0000000000..0eace17a47 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/arrow.zh.md @@ -0,0 +1,108 @@ +--- +title: 箭头 +order: 2 +--- + +无论是内置边还是[自定义边](/zh/docs/manual/middle/elements/edges/custom-edge),都可以为其配置起始端箭头与结束端箭头。G6 中的箭头分为默认箭头、内置箭头、自定义箭头。 + +
img + +## 默认箭头 + +img + +### 使用方法 + +在边的样式属性 `style` 中将 `endArrow` 或 `startArrow` 配置为 `true` 即可: + +```javascript +style: { + endArrow: true, + startArrow: true +} +``` + +## 内置箭头 + +v3.5.8 后支持。 + +### 各箭头概览 + +| 名称 | 参数 | 使用方法 | 效果 | +| --- | --- | --- | --- | +| triangle |
依次为箭头宽度(默认 10)、长度(默认 15)、偏移量(默认为 0,与 `d` 对应)
| endArrow: {
path: G6.Arrow.triangle(10, 20, 25),
d: 25
} | img | +| vee |
依次为箭头宽度(默认 15)、长度(默认 20)、偏移量(默认为 0,与 `d` 对应)
| endArrow: {
path: G6.Arrow.vee(10, 20, 25),
d: 25
} | img | +| circle |
依次为箭头半径(默认 5)、偏移量(默认为 0,与 `d` 对应)
| endArrow: {
path: G6.Arrow.circle(10, 25),
d: 25
} | img | +| diamond |
依次为箭头宽度(默认 15)、长度(默认 15)、偏移量(默认为 0,与 `d` 对应)
| endArrow: {
path: G6.Arrow.diamond(10, 20, 25),
d: 25
} | img | +| rect |
依次为箭头宽度(默认 10)、长度(默认 10)、偏移量(默认为 0,与 `d` 对应)
| endArrow: {
path: G6.Arrow.rect(10, 20, 25),
d: 25
} | img | +| triangleRect |
依次为箭头三角形宽度(默认 15)、三角形长度(默认 15)、矩形宽度(默认 15)、矩形长度(默认 3)、三角形与矩形间距(默认为 5)、偏移量(默认为 0,与 `d` 对应)
| endArrow: {
path: G6.Arrow.triangleRect(15, 15, 15, 3, 5, 25),
d: 25
} | img | + +### 使用方法 + +调用 `G6.Arrow.arrowName` 配置边的样式属性 `style` 中 `endArrow` 或 `startArrow` 的 `path`: + +```javascript +style: { + endArrow: { + path: G6.Arrow.triangle(10, 20, 25), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) + d: 25 + }, + startArrow: { + path: G6.Arrow.vee(15, 20, 15), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) + d: 15 + }, +} +``` + +## 自定义箭头 + +参见高级指引 [自定义箭头](/zh/docs/manual/middle/elements/edges/custom-edge#4-自定义箭头)。 + +## 配置箭头样式 + +只有内置箭头和自定义箭头可以配置样式。 + +#### 配置项 + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| fill | false | String | 填充颜色,默认无填充 | +| stroke | false | String | 描边颜色,默认与边颜色相同 | +| lineWidth | false | Number | 描边宽度,默认与边宽度相同 | +| opacity | false | Number | 透明度 | +| shadowColor | false | String | 阴影颜色 | +| shadowBlur | false | Number | 阴影模糊程度 | +| shadowOffsetX | false | Number | 阴影 x 方向偏移量 | +| shadowOffsetY | false | Number | 阴影 y 方向偏移量 | +| lineDash | false | Array | 描边的虚线样式,可以指定一个数组。一组描述交替绘制线段和间距(坐标空间单位)长度的数字。 如果数组元素的数量是奇数, 数组的元素会被复制并重复。例如, [5, 15, 25] 会变成 [5, 15, 25, 5, 15, 25]。 | + +#### 使用方法 + +```javascript +// 内置箭头 +style: { + endArrow: { + path: G6.Arrow.triangle(10, 20, 25), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) + d: 25, + fill: '#f00', + stroke: '#0f0', + opacity: 0.5, + lineWidth: 3, + // ... + }, +} + + +// 自定义箭头 +style: { + endArrow: { + path: 'M 0,0 L 20,10 L 20,-10 Z', + d: 5, + fill: '#f00', + stroke: '#0f0', + opacity: 0.5, + lineWidth: 3, + // ... + }, +} +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/arc.en.md b/packages/site/docs/manual/middle/elements/edges/built-in/arc.en.md new file mode 100644 index 0000000000..f7f0a01a76 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/arc.en.md @@ -0,0 +1,171 @@ +--- +title: Arc +order: 6 +--- + +A built-in edge Arc has the default style as below.
img + +## Usage + +As stated in [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge) , there are three methods to configure edges: Configure edges globally when instantiating a Graph; Configure edges in their data; Configure edges by `graph.edge(edgeFn)`. Their priorities are: + +`graph.edge(edgeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, `source`, `target`, `label` which should be assigned to every single edge data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/edges/defaultEdge#the-common-property) and in each edge type (refer to doc of each edge type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'arc'` in the `defaultEdge` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'arc', // The type of the edge + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different edges with different properties, you can write the properties into the edge data. + +```javascript +const data = { + nodes: [ + ... // nodes + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'arc', + //... // Other configurations for edges + style: { + //... // Style properties for edges + } + }, + //... // Other edges + ] +} +``` + +## Property + +Arc edge has the [Common Edge Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#the-common-property), and some commonly used properties are shown below, where `curveOffset` is the special property for arc edge , controlling the size and the bending direction of the arc. + +```javascript +color: '#87e8de', +curveOffset: 20, // The distance between the center of the two endpoints and the center of the arc +style: { + lineWidth: 2, + stroke: '#87e8de' +}, +label: 'Text of the label', +labelCfg: { + refX: 10, // x offset of the label + refY: 10, // y offset of the label + style: { + fill: '#595959' + } +} +``` + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| color | The color of the edge | String | The priority id lower than `stroke` in `style` | +| **curveOffset** | **The distance between the center of the two endpoints and the center of the arc** | **Number** | **The absolute value of `curveOffset` is the size of the arc, the sign of `curveOffset` is the bending direction of the arc. `20` by default. It is the special property for arc edge** | +| style | The default style of edge | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | + +### Special Property: curveOffset + +`curveOffset` is the special property for arc edge, which controlls the size and the bending direction of the arc. The following code shows how to configure the `curveOffset` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 200, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + type: 'arc', + label: 'arc', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + linkCenter: true, + defaultEdge: { + // type: 'arc', // The type has been assigned in the data, we do not have to define it any more + curveOffset: -80, + }, +}); +graph.data(data); +graph.render(); +``` + +⚠️Attention:
`linkCenter: true` is assigned to the graph in the code above to ensure the arc edges link to the center of their end nodes. + +### style + +`style` is an object which is the same as the [Common Edge Style Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#style). Base on the code in [curveOffset](#special-property-curveoffset) section, we add `style` to `defaultEdge`.
! img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultEdge: { + // ... Other properties for edges + style: { + stroke: '#088', + lineWidth: 3, + }, + }, +}); +// ... +``` + +### labelCfg + +`labelCfg` is an object which is the same as the [Common Edge Label Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#label-and-labelcfg). Base on the code in [Special Property: curveOffset](#special-property-curveoffset) section, we add `labelCfg` to `defaultEdge`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultEdge: { + // ... Other properties for edges + labelCfg: { + autoRotate: true, + refY: -30, + refX: 30, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/arc.zh.md b/packages/site/docs/manual/middle/elements/edges/built-in/arc.zh.md new file mode 100644 index 0000000000..4c6df46b14 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/arc.zh.md @@ -0,0 +1,171 @@ +--- +title: Arc +order: 6 +--- + +G6 内置了圆弧  arc  边,其默认样式如下。
img + +## 使用方法 + +如 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge)  一节所示,配置边的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.edge(edgeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 graph.edge(edgeFn) 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`source`、`target`、`label` 应当配置到每条边数据中外,其余的 [边的通用属性](/zh/docs/manual/middle/elements/edges/defaultEdge#边的通用属性) 以及各个边类型的特有属性(见内置边类型)均支持三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultEdge` 指定 `type` 为 `'arc'`,即可使用 arc  边。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'arc', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + ... // 节点 + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'arc', + ... // 其他配置 + style: { + ... // 样式属性,每种边的详细样式属性参见各边文档 + } + }, + ... // 其他边 + ] +} +``` + +## 配置项说明 + +arc 边支持 [边通用配置项](/zh/docs/manual/middle/elements/edges/defaultEdge/#边的通用属性),以下表格对部分常用配置项进行说明。其中 `curveOffset` 属性是 `arc` 特有的属性,它控制了圆弧的大小以及弯曲的方向。 + +```javascript +color: '#87e8de', +curveOffset: 20, // 圆弧顶端距离两线中心位置的距离 +style: { + lineWidth: 2, + stroke: '#87e8de' +}, +label: '边的标签文字', +labelCfg: { + refX: 10, // 文本在 x 方向偏移量 + refY: 10, // 文本在 y 方向偏移量 + style: { + fill: '#595959' + } +} +``` + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| color | 边的颜色 | String | 优先级低于 `style` 中的 `stroke` | +| **curveOffset** | **圆弧顶端距离两线中心位置的距离** | **Number** | **数值绝对值大小控制圆弧的大小,正负控制圆弧弯曲的方向,默认为 `20`。arc 边特有** | +| style | 边的样式 | Object | Canvas 支持的属性 | +| label | 标签文本文字 | String | | +| labelCfg | 标签文本配置项 | Object | | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | + +### 特殊属性:弧度  curveOffset + +`curveOffset` 属性是 `arc` 特有的属性,它控制了圆弧的大小以及弯曲的方向。下面代码演示在实例化图时全局配置方法中配置 `curveOffset`。
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 200, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + type: 'arc', + label: 'arc', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + linkCenter: true, + defaultEdge: { + // type: 'arc', // 在数据中已经指定 type,这里无需再次指定 + curveOffset: -80, + }, +}); +graph.data(data); +graph.render(); +``` + +⚠️ 注意:
上面代码使用了 graph 的配置项  `linkCenter: true` 以设置 arc 边连入节点的中心,保证美观性。 + +### 样式属性 style + +Object 类型。配置项与 [边通用样式属性](/zh/docs/manual/middle/elements/edges/defaultEdge/#样式属性-style) 相同。基于上面 [特殊属性:弧度  curveOffset](#特殊属性:弧度-curveoffset) 中的代码,下面代码在 `defaultEdge` 中增加了  `style`  配置项进行边的样式的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他配置 + defaultEdge: { + // ... 其他配置 + style: { + stroke: '#088', + lineWidth: 3, + }, + }, +}); +// ... +``` + +### 标签文本配置  labelCfg + +Object 类型。支持 [边通用标签配置](/zh/docs/manual/middle/elements/edges/defaultEdge/#标签文本-label-及其配置-labelcfg)。基于上面  [弧度 curveOffset](/zh/docs/manual/middle/elements/edges/built-in/arc/#特殊属性:弧度-curveoffset) 中的代码,下面代码在 `defaultEdge` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他配置 + defaultEdge: { + // ... 其他配置 + labelCfg: { + autoRotate: true, + refY: -30, + refX: 30, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/cubic.en.md b/packages/site/docs/manual/middle/elements/edges/built-in/cubic.en.md new file mode 100644 index 0000000000..619d5257d6 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/cubic.en.md @@ -0,0 +1,137 @@ +--- +title: Cubic +order: 5 +--- + +A built-in edge Cubic has the default style as below.
img + +## Usage + +As stated in [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge) , there are three methods to configure edges: Configure edges globally when instantiating a Graph; Configure edges in their data; Configure edges by `graph.edge(edgeFn)`. Their priorities are: + +`graph.edge(edgeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, `source`, `target`, `label` which should be assigned to every single edge data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/edges/defaultEdge#the-common-property) and in each edge type (refer to doc of each edge type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'cubic'` in the `defaultEdge` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'cubic', // The type of the edge + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different edges with different properties, you can write the properties into the edge data. + +```javascript +const data = { + nodes: [ + // ... // nodes + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'cubic', + //... // Other configurations for edges + style: { + //... // Style properties for edges + } + }, + //... // Other edges + ] +} +``` + +## Property + +Cubic edge has the [Common Edge Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#the-common-property), and some commonly used properties are shown below. The properties with object type will be described in detail after the table. + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| color | The color of the edge | String | The priority id lower than `stroke` in `style` | +| style | The default style of edge | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| controlPoints | The array of the control points for the cubic curve | Array | If it is not assgined, the default control points on the 1/3 and 2/3 of the curve will take effect. e.g. `[{ x: 10, y: 20 }, { x: 15, y: 30 }]` | +| curveOffset | The distances between the controlPoints to the line connecting the two endpoints. They control the degree of bending of the curve. When the type is Number, the two controlPoints are on different sides of the line and the distances are the same. The sign of it controls the bending direction. | Number / Number[] | It is a special configuration for 'cubic', 'quadratic', 'cubic-vertical', 'cubic-horizontal' type edge | +| minCurveOffset | The MINIMUM distances betweent the controlPoints to the line connecting to two endpoints. They control the degree of bending of the curve to prevent the too 'flat' curve. And it takes effect when the `curveOffset` is not assigned | Number / Number[] | It is a special configuration for 'cubic-vertical' type and 'cubic-horizontal' type edge | +| curvePosition | The relative positions of the two controlPoints on the line connecting the two endpoints. Ranges from 0 to 1 | Number / Number[] | It is a special configuration for 'cubic', 'quadratic', 'cubic-vertical', 'cubic-horizontal' type edge | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | + +### style + +`style` is an object which is the same as the [Common Edge Style Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#style). The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 200, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + type: 'cubic', + label: 'cubic', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // type: 'cubic', // The type has been assigned in the data, we do not have to define it any more + style: { + endArrow: true, + stroke: '#088', + lineWidth: 3, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object which is the same as the [Common Edge Label Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#label-and-labelcfg). Base on the code in [style](#style) section, we add `labelCfg` to `defaultEdge`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultEdge: { + // ... Other properties for edges + labelCfg: { + autoRotate: true, + refY: 10, + refX: 40, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/cubic.zh.md b/packages/site/docs/manual/middle/elements/edges/built-in/cubic.zh.md new file mode 100644 index 0000000000..2eb15b50c8 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/cubic.zh.md @@ -0,0 +1,137 @@ +--- +title: Cubic +order: 5 +--- + +G6 内置了  cubic  边,其默认样式如下。
img + +## 使用方法 + +如 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge)  一节所示,配置边的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.edge(edgeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 graph.edge(edgeFn) 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`source`、`target`、`label` 应当配置到每条边数据中外,其余的 [边的通用属性](/zh/docs/manual/middle/elements/edges/defaultEdge#边的通用属性) 以及各个边类型的特有属性(见内置边类型)均支持三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultEdge` 指定 `type` 为 `'cubic'`,即可使用 cubic  边。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'cubic', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + ... // 节点 + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'cubic', + ... // 其他配置 + style: { + ... // 样式属性,每种边的详细样式属性参见各边文档 + } + }, + ... // 其他边 + ] +} +``` + +## 配置项说明 + +cubic 边支持 [边通用配置项](/zh/docs/manual/middle/elements/edges/defaultEdge/#边的通用属性),以下表格对部分常用配置项进行说明。对于 Object 类型的配置项将在后面有详细讲解: + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| color | 边的颜色 | String | 优先级低于 `style` 中的 `stroke` | +| style | 边的样式 | Object | Canvas 支持的属性 | +| label | 标签文本文字 | String | | +| labelCfg | 标签文本配置项 | Object | | +| controlPoints | 控制点数组 | Array | 不指定时将会使用默认的控制点:曲线 1/3 和 2/3 处。示例:`[{ x: 10, y: 20 }, { x: 15, y: 30 }]` | +| curveOffset | 两个控制点距离两端点连线的距离,可理解为控制边的弯曲程度。为 Number 类型时两个控制点分别在连线两侧且与连线距离相等 | Number / Number[] | cubic、quadratic、cubic-vertical、cubic-horizontal 等贝塞尔曲线特有 | +| minCurveOffset | 两个控制点距离两端点连线的最小距离距离,可理解为控制边的弯曲程度,用于防止过于“平缓”的曲线,当 `curveOffset` 未指定时生效。为 Number 类型时两个控制点分别在连线两侧且与连线距离相等 | Number / Number[] | cubic-vertical、cubic-horizontal 边特有 | +| curvePosition | 两个控制点在两端点连线上的相对位置,范围 0 ~ 1 | Number / Number[] | cubic、quadratic、cubic-vertical、cubic-horizontal 等贝塞尔曲线特有 | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | + +### 样式属性 style + +Object 类型。配置项与 [边通用样式属性](/zh/docs/manual/middle/elements/edges/defaultEdge/#样式属性-style) 相同。下面代码演示在实例化图时全局配置方法中配置 `style`,以达到下图效果。
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 200, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + type: 'cubic', + label: 'cubic', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // type: 'cubic', // 在数据中已经指定 type,这里无需再次指定 + style: { + endArrow: true, + stroke: '#088', + lineWidth: 3, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。支持 [边通用标签配置](/zh/docs/manual/middle/elements/edges/defaultEdge/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](/zh/docs/manual/middle/elements/edges/defaultEdge/#样式属性-style) 中的代码,下面代码在 `defaultEdge` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他配置 + defaultEdge: { + // ... 其他配置 + labelCfg: { + autoRotate: true, + refY: 10, + refX: 40, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/line.en.md b/packages/site/docs/manual/middle/elements/edges/built-in/line.en.md new file mode 100644 index 0000000000..bbb2a0a2f2 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/line.en.md @@ -0,0 +1,135 @@ +--- +title: Line +order: 2 +--- + +A built-in edge Line has the default style as below.
img + +## Usage + +As stated in [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge) , there are three methods to configure edges: Configure edges globally when instantiating a Graph; Configure edges in their data; Configure edges by `graph.edge(edgeFn)`. Their priorities are: + +`graph.edge(edgeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, `source`, `target`, `label` which should be assigned to every single edge data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/edges/defaultEdge#the-common-property) and in each edge type (refer to doc of each edge type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'line'` in the `defaultEdge` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'line', // The type of the edge + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different edges with different properties, you can write the properties into the edge data. + +```javascript +const data = { + nodes: [ + ... // nodes + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'line', + //... // Other configurations for edges + style: { + //... // Style properties for edges + } + }, + //... // Other edges + ] +} +``` + +## Property + +Cubic edge has the [Common Edge Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#the-common-property), and some commonly used properties are shown below. The properties with object type will be described in detail after the table. + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| color | The color of the edge | String | The priority id lower than `stroke` in `style` | +| style | The default style of edge | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | + +### style + +`style` is an object which is the same as the [Common Edge Style Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#style). The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 100, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + type: 'line', + label: 'line', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // type: 'line', // The type has been assigned in the data, we do not have to define it any more + style: { + stroke: 'steelblue', + lineWidth: 5, + }, + labelCfg: { + position: 'end', + refY: -10, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object which is the same as the [Common Edge Label Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#label-and-labelcfg). Base on the code in [style](#style) section, we add `labelCfg` to `defaultEdge`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultEdge: { + // ... Other properties for edges + labelCfg: { + position: 'end', + refY: -10, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/line.zh.md b/packages/site/docs/manual/middle/elements/edges/built-in/line.zh.md new file mode 100644 index 0000000000..c2808dbc39 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/line.zh.md @@ -0,0 +1,135 @@ +--- +title: Line +order: 2 +--- + +G6 内置了直线 line  边,其默认样式如下。
img + +## 使用方法 + +如 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge)  一节所示,配置边的方式有两种:配置边的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.edge(edgeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 graph.edge(edgeFn) 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`source`、`target`、`label` 应当配置到每条边数据中外,其余的 [边的通用属性](/zh/docs/manual/middle/elements/edges/defaultEdge#边的通用属性) 以及各个边类型的特有属性(见内置边类型)均支持三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultEdge` 指定 `type` 为 `'line'`,即可使用 line  边。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'line', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + ... // 节点 + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'line', + ... // 其他配置 + style: { + ... // 样式属性,每种边的详细样式属性参见各边文档 + } + }, + ... // 其他边 + ] +} +``` + +## 配置项说明 + +line 边支持 [边通用配置项](/zh/docs/manual/middle/elements/edges/defaultEdge/#边的通用属性),以下表格对部分常用配置项进行说明。对于 Object 类型的配置项将在后面有详细讲解: + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| color | 直线的颜色 | String | 优先级低于 `style` 中的 `stroke` | +| style | 直线的样式 | Object | Canvas 支持的属性 | +| label | 标签文本文字 | String | | +| labelCfg | 标签文本配置项 | Object | | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | + +### 样式属性 style + +Object 类型。配置项与 [边通用样式属性](/zh/docs/manual/middle/elements/edges/defaultEdge/#样式属性-style) 相同。下面代码演示在实例化图时全局配置方法中配置 `style`,以达到下图效果。
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 100, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + type: 'line', + label: 'line', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // type: 'line', // 在数据中已经指定 type,这里无需再次指定 + style: { + stroke: 'steelblue', + lineWidth: 5, + }, + labelCfg: { + position: 'end', + refY: -10, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。支持 [边通用标签配置](/zh/docs/manual/middle/elements/edges/defaultEdge/#标签文本-label-及其配置-labelcfg)。
基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultEdge` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他配置 + defaultEdge: { + // ... 其他配置 + labelCfg: { + position: 'end', + refY: -10, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/loop.en.md b/packages/site/docs/manual/middle/elements/edges/built-in/loop.en.md new file mode 100644 index 0000000000..de9d9bc030 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/loop.en.md @@ -0,0 +1,161 @@ +--- +title: Loop +order: 7 +--- + +A built-in edge Line has the default style as below.
img + +⚠️Attention:
loop edge is appropriate for the self-loop edges whose target node and the source node are the same. In other words, a loop edge connect a node to itself. There will be a strange result if the loop edge is applied to the edge which is not a self-loop.
img + +## Usage + +As stated in [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge) , there are three methods to configure edges: Configure edges globally when instantiating a Graph; Configure edges in their data; Configure edges by `graph.edge(edgeFn)`. Their priorities are: + +`graph.edge(edgeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, `source`, `target`, `label` which should be assigned to every single edge data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/edges/defaultEdge#the-common-property) and in each edge type (refer to doc of each edge type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'cubic'` in the `defaultEdge` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'loop', // The type of the edge + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different edges with different properties, you can write the properties into the edge data. + +```javascript +const data = { + nodes: [ + ... // nodes + ], + edges: [{ + source: 'node0', + target: 'node0' + type: 'loop', + //... // Other configurations for edges + style: { + //... // Style properties for edges + } + }, + //... // Other edges + ] +} +``` + +## Property + +Loop edge has the [Common Edge Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#the-common-property), and some commonly used properties are shown below. The properties with object type will be described in detail after the table, where `loopCfg` is the special property for loop edge. + +loop 边支持以下的配置项,对于 Object 类型的配置项将在后面有详细讲解: + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| color | The color of the edge | String | The priority id lower than `stroke` in `style` | +| style | The default style of edge | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | +| **loopCfg** | **Special property for loop edge** | **Object** | | + +### style + +`style` is an object which is the same as the [Common Edge Style Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#style). The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node0', + type: 'loop', + label: 'loop', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // type: 'loop', // The type has been assigned in the data, we do not have to define it any more + style: { + endArrow: true, + stroke: '#088', + lineWidth: 3, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object which is the same as the [Common Edge Label Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#label-and-labelcfg). Base on the code in [style](#style) section, we add `labelCfg` to `defaultEdge`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultEdge: { + // ... Other properties for edges + labelCfg: { + refY: -5, + refX: 30, + }, + }, +}); +// ... +``` + +### loopCfg + +`loopCfg` is an object that configures the direction, height, and clockwise, connection point start and end position. + +- `position`: The relative position to the source/target node. Options: `top`, `top-right`, `right`,`bottom-right`, `bottom`, `bottom-left`, `left`, `top-left`. `top` by default. +- `dist`: The distance between the keyShape of the source/target node to the highest position of the loop. It is equal to the height of the source/target node by default. +- `clockwise`: Whether to draw the loop clockwisely. `true` by default +- `pointPadding`: For non-circular nodes, the offset between the connection point and the node center coordinates ('top right', 'bottom right', 'top left', 'bottom left', which are special, four angular coordinates) in the x-axis or y-axis direction, the default value is' 1/4 of the minimum value of node width and height '. *Supported by v4.7.8.* + +Base on the code in [style](#style) section, we add `loopCfg` to `defaultEdge`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultEdge: { + // ... Other properties for edges + loopCfg: { + position: 'left', + dist: 100, + clockwise: false, + pointPadding: 15, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/loop.zh.md b/packages/site/docs/manual/middle/elements/edges/built-in/loop.zh.md new file mode 100644 index 0000000000..c2dc5753aa --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/loop.zh.md @@ -0,0 +1,161 @@ +--- +title: Loop +order: 7 +--- + +G6 内置了折线 loop  边,其默认样式如下。
img + +⚠️ 注意:
loop 边适用于自环边,即起始点与结束点为相同节点的边,在不同端点的边上适用 loop 边将会出现异常效果。
img + +## 使用方法 + +如 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge)  一节所示,配置边的方式有两种:配置边的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.edge(edgeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 graph.edge(edgeFn) 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`source`、`target`、`label` 应当配置到每条边数据中外,其余的 [边的通用属性](/zh/docs/manual/middle/elements/edges/defaultEdge#边的通用属性) 以及各个边类型的特有属性(见内置边类型)均支持三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultEdge` 指定 `type` 为 `'loop'`,即可使用 `loop`  边。需要注意的是,如果图上存在非自环边,loop 将会表现异常。因此不建议在存在非自环边的图上使用此全局配置方法。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'loop', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + ... // 节点 + ], + edges: [{ + source: 'node0', + target: 'node0' + type: 'loop', + ... // 其他配置 + style: { + ... // 样式属性,每种边的详细样式属性参见各边文档 + } + }, + ... // 其他边 + ] +} +``` + +## 配置项说明 + +自环是指连接单个节点自身的边,是一种边的特殊情况。支持 [边通用配置项](/zh/docs/manual/middle/elements/edges/defaultEdge/#边的通用属性),以下表格对部分常用配置项进行说明。在通用属性基础上,支持了特殊的配置 `loopCfg`。 + +loop 边支持以下的配置项,对于 Object 类型的配置项将在后面有详细讲解: + +| 名称 | 含义 | 类型 | 备注 | +| -------------- | ------------------ | ---------- | ---------------------------- | +| color | 边的颜色 | String | 优先级低于 style 中的 stroke | +| style | 边的样式 | Object | Canvas 支持的属性 | +| style.endArrow | 边结束端是否有箭头 | Boolean | 默认为 false | +| label | 标签文本文字 | String | | +| labelCfg | 标签文本配置项 | Object | | +| **loopCfg** | **自环特殊配置** | **Object** | | + +### 样式属性 style + +Object 类型。配置项与 [边通用样式属性](/zh/docs/manual/middle/elements/edges/defaultEdge/#样式属性-style) 相同。下面代码演示在实例化图时全局配置方法中配置 `style`,以达到下图效果。
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node0', + type: 'loop', + label: 'loop', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // type: 'loop', // 在数据中已经指定 type,这里无需再次指定 + style: { + endArrow: true, + stroke: '#088', + lineWidth: 3, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。支持 [边通用标签配置](/zh/docs/manual/middle/elements/edges/defaultEdge/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultEdge` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他配置 + defaultEdge: { + // ... 其他配置 + labelCfg: { + refY: -5, + refX: 30, + }, + }, +}); +// ... +``` + +### 自环特殊配置  loopCfg + +Object 类型。通过 `loopCfg`  配置自环的方位、高度、顺逆时针、连接起始点位置。 + +- `position`: 指定自环与节点的相对位置。默认为:`top`。支持的值有:`top`, `top-right`, `right`,`bottom-right`, `bottom`, `bottom-left`, `left`, `top-left` +- `dist`: 从节点 keyShape 的边缘到自环最顶端的位置,用于指定自环的曲度,默认为节点的高度。 +- `clockwise`: 指定是否顺时针画环,默认为  `true`。 +- `pointPadding`: 对于非圆形节点设置的连接点与节点中心坐标(`top-right`,`bottom-right`,`top-left`,`bottom-left`较特殊,为四个角坐标)在 x 轴或 y 轴方向的偏移量,默认为  `节点宽高中最小值的1/4`,*v4.7.8 后支持*。 + +基于上面 [样式属性 style](#XQFb2) 中的代码,下面代码在 `defaultEdge` 中增加了  `loopCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他配置 + defaultEdge: { + // ... 其他配置 + loopCfg: { + position: 'left', + dist: 100, + clockwise: false, + pointPadding: 15, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/polyline.en.md b/packages/site/docs/manual/middle/elements/edges/built-in/polyline.en.md new file mode 100644 index 0000000000..52f1891ee1 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/polyline.en.md @@ -0,0 +1,159 @@ +--- +title: Polyline +order: 3 +--- + +A built-in edge Polyline has the default style as below.
img + +## Usage + +As stated in [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge) , there are three methods to configure edges: Configure edges globally when instantiating a Graph; Configure edges in their data; Configure edges by `graph.edge(edgeFn)`. Their priorities are: + +`graph.edge(edgeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, `source`, `target`, `label` which should be assigned to every single edge data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/edges/defaultEdge#the-common-property) and in each edge type (refer to doc of each edge type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'cubic'` in the `defaultEdge` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'polyline', // The type of the edge + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different edges with different properties, you can write the properties into the edge data. + +```javascript +const data = { + nodes: [ + // ... // nodes + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'polyline', + //... // Other configurations for edges + style: { + //... // Style properties for edges + } + }, + //... // Other edges + ] +} +``` + +## Property + +Polyline edge has the [Common Edge Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#the-common-property), and some commonly used properties are shown below. The properties with object type will be described in detail after the table + +```javascript +color: '#87e8de', +style: { + offset: 20, // The minimum distance between the bend and the source/target node + radius: 10, // The border radius of the bend. + lineWidth: 2, + stroke: '#87e8de' +}, +label: 'Label text', +labelCfg: { + refX: 10, // x offset of the label + refY: 10, // y offset of the label + style: { + fill: '#595959' + } +} +``` + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| color | The color of the edge | String | The priority id lower than `stroke` in `style` | +| style | The default style of edge | Object | Correspond to the styles in Canvas | +| **style.radius** | **The border radius of the bend** | **Number** | **It is a special property for polyline edge** | +| **style.offset** | **The minimum distance between the bend and the source/target node** | **Number** | **`5` by default. It is a special property for polyline edge** | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| controlPoints | The array of the control points for the polyline | Array | If it is not assigned, G6 will calculate it by A\* algorithm If it is assgned, the path of the polyline will be generated according to it. e.g. `[{ x: 10, y: 20 }, { x: 20, y: 25 }, ...]` | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | + +### style + +`style` is an object. The [Common Edge Style Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#style) are available for polyline edge. There are two special properties in `style` for polyline edge: + +- `radius`, he border radius of the bend; +- `offset`, The minimum distance between the bend and the source/target node, `5` by default. + +The other style properties are the same as the common style property of edge. Refer to [Built-in Edges]. The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 200, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + type: 'polyline', + label: 'polyline', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // type: 'polyline', // The type has been assigned in the data, we do not have to define it any more + style: { + radius: 10, + offset: 10, + stroke: 'steelblue', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object which is the same as the [Common Edge Label Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#label-and-labelcfg). Base on the code in [style](#style) section, we add `labelCfg` to `defaultEdge`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultEdge: { + // ... Other properties for edges + labelCfg: { + refY: -10, + refX: 60, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/polyline.zh.md b/packages/site/docs/manual/middle/elements/edges/built-in/polyline.zh.md new file mode 100644 index 0000000000..87eddc164b --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/polyline.zh.md @@ -0,0 +1,159 @@ +--- +title: Polyline +order: 3 +--- + +G6 内置了折线 polyline  边,其默认样式如下。
img + +## 使用方法 + +如 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge)  一节所示,配置边的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.edge(edgeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 graph.edge(edgeFn) 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`source`、`target`、`label` 应当配置到每条边数据中外,其余的 [边的通用属性](/zh/docs/manual/middle/elements/edges/defaultEdge#边的通用属性) 以及各个边类型的特有属性(见内置边类型)均支持三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultEdge` 指定 `type` 为 `'polyline'`,即可使用 polyline  边。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'polyline', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + ... // 节点 + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'polyline', + ... // 其他配置 + style: { + ... // 样式属性,每种边的详细样式属性参见各边文档 + } + }, + ... // 其他边 + ] +} +``` + +## 配置项说明 + +polyline 边支持 [边通用配置项](/zh/docs/manual/middle/elements/edges/defaultEdge/#边的通用属性),以下表格对部分常用配置项进行说明: + +```javascript +color: '#87e8de', +style: { + offset: 20, // 拐弯处距离节点最小距离 + radius: 10, // 拐弯处的圆角弧度,若不设置则为直角 + lineWidth: 2, + stroke: '#87e8de' +}, +label: '边的标签文字', +labelCfg: { + refX: 10, // 文本在 x 方向偏移量 + refY: 10, // 文本在 y 方向偏移量 + style: { + fill: '#595959' + } +} +``` + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| color | 边的颜色 | String | 优先级低于 style 中的 stroke | +| style | 边的样式 | Object | Canvas 支持的属性 | +| **style.radius** | **拐弯处的圆角弧度** | **Number** | **若不设置则为直角,polyline 特有** | +| **style.offset** | **拐弯处距离节点最小距离** | **Number** | **默认为 5,polyline 特有** | +| controlPoints | 控制点数组 | Array | 不指定时根据 A\* 算法自动生成折线。若指定了,则按照 `controlPoints` 指定的位置进行弯折。示例:`[{ x: 10, y: 20 }, { x: 20, y: 25 }, ...]` | +| label | 标签文本文字 | String | | +| labelCfg | 标签文本配置项 | Object | | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | + +### 样式属性 style + +Object 类型。支持 [边通用样式属性](/zh/docs/manual/middle/elements/edges/defaultEdge/#样式属性-style)。与其他类型的边不同的是,polyline 的 `style`  含有两个特殊属性: + +- `radius` ,弯折处的圆角半径,不设置则默认为直角; +- `offset` ,距离端点的最小距离,默认值为 5。 + +其它配置项与边的通用样式属性相同,见 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge)
下面代码演示在实例化图时全局配置方法中配置 `style`,以达到下图效果。
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 200, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + type: 'polyline', + label: 'polyline', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // type: 'polyline', // 在数据中已经指定 type,这里无需再次指定 + style: { + radius: 10, + offset: 10, + stroke: 'steelblue', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置  labelCfg + +Object 类型。支持 [边通用标签配置](/zh/docs/manual/middle/elements/edges/defaultEdge/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultEdge` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他配置 + defaultEdge: { + // ... 其他配置 + labelCfg: { + refY: -10, + refX: 60, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/quadratic.en.md b/packages/site/docs/manual/middle/elements/edges/built-in/quadratic.en.md new file mode 100644 index 0000000000..f4e44ea908 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/quadratic.en.md @@ -0,0 +1,135 @@ +--- +title: Quadratic +order: 4 +--- + +A built-in edge Quadratic has the default style as below.
img + +## Usage + +As stated in [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge) , there are three methods to configure edges: Configure edges globally when instantiating a Graph; Configure edges in their data; Configure edges by `graph.edge(edgeFn)`. Their priorities are: + +`graph.edge(edgeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, `source`, `target`, `label` which should be assigned to every single edge data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/edges/defaultEdge#the-common-property) and in each edge type (refer to doc of each edge type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'quadratic'` in the `defaultEdge` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'quadratic', // The type of the edge + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different edges with different properties, you can write the properties into the edge data. + +```javascript +const data = { + nodes: [ + // ... // nodes + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'quadratic', + //... // Other configurations for edges + style: { + .//... // Style properties for edges + } + }, + //... // Other edges + ] +} +``` + +## Property + +Quadratic edge has the [Common Edge Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#the-common-property), and some commonly used properties are shown below. The properties with object type will be described in detail after the table. + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| color | The color of the edge | String | The priority id lower than `stroke` in `style` | +| style | The default style of edge | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| controlPoints | The array of the control points for the quadratic curve | Array | If it is not assgined, the default control point on the center of the curve will take effect. e.g. `[{ x: 10, y: 20 }]` | +| curveOffset | The distance between the the controlPoint to the line connecting the two endpoints. It controls the degree of bending of the curve. The sign of it controls the bending direction. | Number / Number[] | It is a special configuration for 'cubic', 'horizontal', 'cubic-vertical', 'cubic-horizontal' type edge | +| curvePosition | The relative position of the controlPoint on the line connecting the two endpoints. Ranges from 0 to 1 | Number / Number[] | It is a special configuration for 'cubic', 'horizontal', 'cubic-vertical', 'cubic-horizontal' type edge | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | + +### style + +`style` is an object which is the same as the [Common Edge Style Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#style). The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 100, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + type: 'quadratic', + label: 'quadratic', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // type: 'quadratic', // The type has been assigned in the data, we do not have to define it any more + style: { + stroke: '#088', + endArrow: true, + lineWidth: 3, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object which is the same as the [Common Edge Label Properties](/en/docs/manual/middle/elements/edges/defaultEdge/#label-and-labelcfg). Base on the code in [style](#style) section, we add `labelCfg` to `defaultEdge`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultEdge: { + // ... Other properties for edges + labelCfg: { + refY: 10, + refX: 40, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/built-in/quadratic.zh.md b/packages/site/docs/manual/middle/elements/edges/built-in/quadratic.zh.md new file mode 100644 index 0000000000..8fefba9149 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/built-in/quadratic.zh.md @@ -0,0 +1,135 @@ +--- +title: Quadratic +order: 4 +--- + +G6 内置了  quadratic  边,其默认样式如下。
img + +## 使用方法 + +如 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge)  一节所示,配置边的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.edge(edgeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 graph.edge(edgeFn) 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`source`、`target`、`label` 应当配置到每条边数据中外,其余的 [边的通用属性](/zh/docs/manual/middle/elements/edges/defaultEdge#边的通用属性) 以及各个边类型的特有属性(见内置边类型)均支持三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultEdge` 指定 `type` 为 `'quadratic'`,即可使用 quadratic  边。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'quadratic', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + ... // 节点 + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'quadratic', + ... // 其他配置 + style: { + ... // 样式属性,每种边的详细样式属性参见各边文档 + } + }, + ... // 其他边 + ] +} +``` + +## 配置项说明 + +quadratic 边支持 [边通用配置项](/zh/docs/manual/middle/elements/edges/defaultEdge/#边的通用属性),以下表格对部分常用配置项进行说明。对于 Object 类型的配置项将在后面有详细讲解: + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| color | 边的颜色 | String | 优先级低于 style 中的 stroke | +| style | 边的样式 | Object | Canvas 支持的属性 | +| label | 标签文本文字 | String | | +| labelCfg | 标签文本配置项 | Object | | +| controlPoints | 控制点数组 | Array | 不指定时将会使用默认的控制点:曲线中心附近。示例:`[{ x: 10, y: 20 }]` | +| curveOffset | 控制点距离两端点连线的距离,可理解为控制边的弯曲程度 | Number / Number[] | cubic、horizontal、cubic-vertical、cubic-horizontal 等贝塞尔曲线特有 | +| curvePosition | 控制点在两端点连线上的相对位置,范围 0 ~ 1 | Number / Number[] | cubic、horizontal、cubic-vertical、cubic-horizontal 等贝塞尔曲线特有 | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | + +### 样式属性  style + +Object 类型。配置项与 [边通用样式属性](/zh/docs/manual/middle/elements/edges/defaultEdge/#样式属性-style) 相同。下面代码演示在实例化图时全局配置方法中配置 `style`。
img + +```javascript +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 100, + size: 20, + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + type: 'quadratic', + label: 'quadratic', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // type: 'quadratic', // 在数据中已经指定 type,这里无需再次指定 + style: { + stroke: '#088', + endArrow: true, + lineWidth: 3, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。支持 [边通用标签配置](/zh/docs/manual/middle/elements/edges/defaultEdge/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultEdge` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他配置 + defaultEdge: { + // ... 其他配置 + labelCfg: { + refY: 10, + refX: 40, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/edges/custom-edge.en.md b/packages/site/docs/manual/middle/elements/edges/custom-edge.en.md new file mode 100644 index 0000000000..43af782de1 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/custom-edge.en.md @@ -0,0 +1,352 @@ +--- +title: Custom Edge +order: 3 +--- + +G6 provides abundant [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge). Besides, the custom machanism allows the users to design their own type of edges. An edge with complex graphics shapes, complex interactions, fantastic animations can be implemented easily. + +You are able to custom an edge type by `G6.registerEdge(typeName: string, edgeDefinition: object, extendedEdgeType?: string)`, where: + +- `typeName`: the name of the new edge type; +- `extendedEdgeType`: The name of the existing type that will be extended, which can be a built-in edge type, or an existing custom edge type. When it is not assigned, the custom edge will not extend any existing edge type; +- `edgeDefinition`: The definition of the new edge type. The required options can be found at [Custom Mechanism API](/en/docs/api/registerItem#g6registeredgeedgename-options-extendededgename). When the `extendedEdgeType` is assigned, the functions which are not rewritten will extend from the type with name `extendedEdgeType`. + +**Noted** that if the `extendedEdgeType` is assigned, the required functions such as `draw`, `update`, and `setState` will extend from `extendedEdgeType` unless they are rewritten in `edgeDefinition`. Due to this mechanism, a question is often fed back: + +- Q: when the custom edge/node is updated, the re-draw logic is not the same as `draw` or `drawShape` function defined in `edgeDefinition`. e.g., some shapes are not updated as expected, and some text shapes show up. +- A: Since the `extendedEdgeType` is assigned, and the `update` is not implemented in `extendedEdgeType`, the `update` of the extended edge type will be called when updating the edge/node, whose logic might be different from the `draw` or `drawShape` defined by yourself. To avoid this problem, you can override the `update` by `undefined` in `edgeDefinition`. When `update` is `undefined`, the `draw` or `drawShape` will be called when updating the edge/node. + +In this document, we will introduce the custom edge by four examples:
1. Register a brand new edge;
2. Register an edge by extending a built-in edge;
2. Register an edge with an extra shape;
4. Register an edge with interactions and styles;
5. Register an edge with custom arrow. + +## 1. Register a Brand New Edge + +Now, we are goint to register a perpendicular polyline:
+ +img +img +img + +> (Left) Straight line edge. (Center) A custom polyline edge. (Right) The result after modifying the link points of the end nodes. + +### Register a Custom Edge + +```javascript +G6.registerEdge('hvh', { + draw(cfg, group) { + const startPoint = cfg.startPoint; + const endPoint = cfg.endPoint; + const shape = group.addShape('path', { + attrs: { + stroke: '#333', + path: [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y], // 1/3 + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y], // 2/3 + ['L', endPoint.x, endPoint.y], + ], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + return shape; + }, +}); +``` + +Now, we have registered a custom edge named `'hvh'` whose result is shown in the center of the figure above. The default `startPoint` and `endPoint` in the custom edge are the intersection of the edge and the end nodes. + +To achieve the result shown in the right of the figure, we modify the anchorPoints (link points) of the end nodes to change the positions of `startPoint` and `endPoint`. + +### Modify the anchorPoints in Data + +Now, we modify `anchorPoints` in the node data, and then assign `shape` to `'hvh'` in edge data as shown below. + +```javascript +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 200, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + { + id: 'node2', + x: 200, + y: 100, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + { + id: 'node3', + x: 200, + y: 300, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + ], + edges: [ + { + id: 'edge1', + target: 'node2', + source: 'node1', + type: 'hvh', + }, + { + id: 'edge2', + target: 'node3', + source: 'node1', + type: 'hvh', + }, + ], +}; +``` + +## 2. Extend the Built-in Edge + +In this section, we add animation to a built-in edge by `afterDraw`. + +img + +```javascript +G6.registerEdge( + 'line-growth', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = shape.getTotalLength(); + shape.animate( + (ratio) => { + const startLen = ratio * length; + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + { + repeat: true, + duration: 2000, + }, + ); + }, + }, + 'cubic', +); +``` + +
+ +## 3. Adding an Extra Shape + +Adding an extra shape on an arbitrary position on the path of the edge can be implemented by `afterDraw`. `shape.getPoint(ratio)` helps us to find the coordiante of an arbitrary point on the `path`. + +img + +```javascript +G6.registerEdge( + 'mid-point-edge', + { + afterDraw(cfg, group) { + // get the first shape in the graphics group of this edge, it is the path of the edge here + const shape = group.get('children')[0]; + // get the coordinate of the mid point on the path + const midPoint = shape.getPoint(0.5); + // add a rect on the mid point of the path. note that the origin of a rect shape is on its lefttop + group.addShape('rect', { + attrs: { + width: 10, + height: 10, + fill: '#f00', + // x and y should be minus width / 2 and height / 2 respectively to translate the center of the rect to the midPoint + x: midPoint.x - 5, + y: midPoint.y - 5, + }, + name: 'mid-point-edge-rect', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }); + }, + update: undefined, + }, + 'cubic', +); +``` + +
+ +## 4. Custom Edge with Interaction Styles + +In this section, we implement a type of edge with the interaction styles below: + +- Widen the edge by clicking. Restore it by clicking again; +- Turn to red by mouse hovering. Restore it by mouse leaving. + +The result:
+ +img +
+ +⚠️Attention: + when the edge is too thin to be hitted by mouse, set `lineAppendWidth` to enlarge the hitting area. + +```javascript +// Extend a new type of edge by extending line edge +G6.registerEdge( + 'custom-edge', + { + // Response the states change + setState(name, value, item) { + const group = item.getContainer(); + const shape = group.get('children')[0]; // The order is determined by the ordering of been draw + if (name === 'active') { + if (value) { + shape.attr('stroke', 'red'); + } else { + shape.attr('stroke', '#333'); + } + } + if (name === 'selected') { + if (value) { + shape.attr('lineWidth', 3); + } else { + shape.attr('lineWidth', 2); + } + } + }, + }, + 'line', +); + +// Select by clicking, cancel by clicking again +graph.on('edge:click', (ev) => { + const edge = ev.item; + graph.setItemState(edge, 'selected', !edge.hasState('selected')); // Switch the 'selected' state +}); + +graph.on('edge:mouseenter', (ev) => { + const edge = ev.item; + graph.setItemState(edge, 'active', true); +}); + +graph.on('edge:mouseleave', (ev) => { + const edge = ev.item; + graph.setItemState(edge, 'active', false); +}); +``` + +
+ +## 5. Custom Arrow + +G6 (v3.5.8 and later versions) provides [default arrow and built-in arrows](/en/docs/manual/middle/elements/edges/arrow) for edges. The default end arrows might not meet the requirements sometimes. You can register an edge with a custom arrow by the custom mechanism of G6.
+ +img + +> (Left) Built-in arrow of G6. (Right) A custom edge with custom arrow. + +⚠️ Attention: The coordinate system of custom arrow is changed by G6 3.4.1. In the figure below, the left one is the demonstration of the custom arrow before v3.4.1, and the right one illustates v3.4.1 and its later versions. The pointed direction is changed from negative direction to positive direction of x-axis. In the same time, the dirrection of the offset `d` is changed. In both versions, the origin of the self coordinate system of the custom arrow is on the end point of the corresponding edge or path, and the slope of the arrow is the same as the slope of edge or path at the end point. + +img + +> (Left) Illustration for the coordinate system of custom arrow before v3.4.1. (Right) Illustration for v3.4.1 and its later versions. + +There are three ways to configure a custom arrow to an edge in G6: + +- Configuring on the graph to global edges; +- Configuring in data for single edge; +- Configuring in a custom edge type. + +### 1. Global Configuration + +```javascript +const graph = new Graph({ + // ... Other configurations for graph + defaultEdge: { + style: { + endArrow: { + // The custom arrow is a path points at (0, 0), and its tail points to the positive direction of x-axis + path: 'M 0,0 L 20,10 L 20,-10 Z', + // the offset of the arrow, nagtive value means the arrow is moved alone the positive direction of x-axis + // d: -10 + // styles are supported after v3.4.1 + fill: '#333', + stroke: '#666', + opacity: 0.8, + // ... + }, + }, + }, +}); +``` + +### 2. Configuring in Data + +```javascript +const data = { + nodes: [ + { id: 'node1' }, + { id: 'node2' }, + // ... other nodes + ], + edges: [ + { + source: 'node1', + target: 'node2', + style: { + endArrow: { + // The custom arrow is a path points at (0, 0), and its tail points to the positive direction of x-axis + path: 'M 0,0 L 20,10 L 20,-10 Z', + // the offset of the arrow, nagtive value means the arrow is moved alone the positive direction of x-axis + // d: -10 + // styles are supported after v3.4.1 + fill: '#333', + stroke: '#666', + opacity: 0.8, + // ... + }, + }, + }, + //... other edges + ], +}; +``` + +### 3. Configuring in a Custom Edge + +```javascript +G6.registerEdge('line-arrow', { + draw(cfg, group) { + const { startPoint, endPoint } = cfg; + const keyShape = group.addShape('path', { + attrs: { + path: [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x, endPoint.y], + ], + stroke: 'steelblue', + lineWidth: 3, + startArrow: { + // The custom arrow is a path points at (0, 0), and its tail points to the positive direction of x-axis + path: 'M 0,0 L 20,10 L 20,-10 Z', + // the offset of the arrow, nagtive value means the arrow is moved alone the positive direction of x-axis + // d: -10 + }, + endArrow: { + // The custom arrow is a path points at (0, 0), and its tail points to the positive direction of x-axis + path: 'M 0,0 L 20,10 L 20,-10 Z', + // the offset of the arrow, nagtive value means the arrow is moved alone the positive direction of x-axis + // d: -10 + }, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + return keyShape; + }, +}); +``` diff --git a/packages/site/docs/manual/middle/elements/edges/custom-edge.zh.md b/packages/site/docs/manual/middle/elements/edges/custom-edge.zh.md new file mode 100644 index 0000000000..4e1d9d5840 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/custom-edge.zh.md @@ -0,0 +1,362 @@ +--- +title: 自定义边 +order: 3 +--- + +G6 除了提供丰富的 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge)  外,还提供了自定义边的机制,方便用户开发更加定制化的边,包括含有复杂图形的边、复杂交互的边、带有动画的边等。 + +用户可以通过 `G6.registerEdge(typeName: string, edgeDefinition: object, extendedEdgeType?: string)` 注册一个新的边类型,其中: + +- `typeName`:该新边类型名称; +- `extendedEdgeType`:被继承的边类型,可以是内置边类型名,也可以是其他自定义边的类型名。`extendedEdgeType` 未指定时代表不继承其他类型的边; +- `edgeDefinition`:该新边类型的定义,其中必要函数详见 [自定义机制 API](/zh/docs/api/registerItem#g6registeredgeedgename-options-extendededgename)。当有 `extendedEdgeType` 时,没被复写的函数将会继承 `extendedEdgeType` 的定义。 + +**需要注意的是**,自定义边/节点时,若给定了 `extendedEdgeType`,如 `draw`,`update`,`setState` 等必要的函数若不在 `edgeDefinition` 中进行复写,将会继承 `extendedEdgeType` 中的相关定义。常见问题: + +- Q:边/节点更新时,没有按照在 `edgeDefinition` 中自定义实现的 `draw` 或 `drawShape` 逻辑更新。例如,有些图形没有被更新,增加了没有在 `draw` 或 `drawShape` 方法中定义的图形等。 +- A:由于继承了 `extendedEdgeType`,且在 `edgeDefinition` 中没有复写 `update` 方法,导致边/节点更新时执行了 `extendedEdgeType` 中的 `update` 方法,从而与自定义的 `draw` 或 `drawShape` 有出入。可以通过复写 `update` 方法为 `undefined` 解决。当 `update` 方法为 `undefined` 时,边/节点的更新将会执行 `draw` 或 `drawShape` 进行重绘。 + +在本章中我们会通过四个案例,从简单到复杂讲解边的自定义:
1. 从无到有的定义边;
2. 扩展现有边;
3. 增加额外图形;
4. 边的交互样式;
5. 自定义带箭头的边。 + +## 1. 从无到有定义边 + +我们来实现垂直的折线:
+ +img +img +img + +> (左)直线边。(中)默认的折线边。(右)调整了节点的锚点(连入点)后的折线边。 + +### 自定义边 + +```javascript +G6.registerEdge('hvh', { + draw(cfg, group) { + const startPoint = cfg.startPoint; + const endPoint = cfg.endPoint; + const shape = group.addShape('path', { + attrs: { + stroke: '#333', + path: [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y], // 三分之一处 + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y], // 三分之二处 + ['L', endPoint.x, endPoint.y], + ], + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'path-shape', + }); + return shape; + }, +}); +``` + +- 上面自定义边中的 `startPoint` 和 `endPoint` 分别是是边两端与起始节点和结束节点的交点; +- 可以通过修改节点的锚点(边连入点)来改变 `startPoint` 和 `endPoint` 的位置。 + +### 在数据中修改 anchorPoints + +通过以下的数据,使用自定义的 hvh 边,就可以实现上图最右边的效果。 + +```javascript +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 200, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + { + id: 'node2', + x: 200, + y: 100, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + { + id: 'node3', + x: 200, + y: 300, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + ], + edges: [ + { + id: 'edge1', + target: 'node2', + source: 'node1', + type: 'hvh', + }, + { + id: 'edge2', + target: 'node3', + source: 'node1', + type: 'hvh', + }, + ], +}; +``` + +## 2. 扩展现有边 + +通过 `afterDraw` 接口给现有的曲线增加动画。 + +img + +```javascript +G6.registerEdge( + 'line-growth', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = shape.getTotalLength(); + shape.animate( + (ratio) => { + const startLen = ratio * length; + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + { + repeat: true, + duration: 2000, + }, + ); + }, + }, + 'cubic', +); +``` + +
+ +## 3. 增加额外图形 + +通过实现 `afterDraw` 增加额外图形,为找到边的主要图形 `path` 上的某个点,可以使用 `shape.getPoint(ratio)` 获得。 + +img + +```javascript +G6.registerEdge( + 'mid-point-edge', + { + afterDraw(cfg, group) { + // 获取图形组中的第一个图形,在这里就是边的路径图形 + const shape = group.get('children')[0]; + // 获取路径图形的中点坐标 + const midPoint = shape.getPoint(0.5); + // 在中点增加一个矩形,注意矩形的原点在其左上角 + group.addShape('rect', { + attrs: { + width: 10, + height: 10, + fill: '#f00', + // x 和 y 分别减去 width / 2 与 height / 2,使矩形中心在 midPoint 上 + x: midPoint.x - 5, + y: midPoint.y - 5, + }, + name: 'mid-point-edge-rect', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }); + }, + update: undefined, + }, + 'cubic', +); +``` + +
+ +## 4. 边的交互样式 + +以点击选中、鼠标 hover 到边为示例,实现如下需求: + +- 点击边时边变粗,再点击变成细; +- 鼠标移动上去变成红色,离开变成 `'#333'` 。 + +效果如下图所示。
+ +img +
+⚠️注意: + +边过细时点击很难被击中,可以设置  `lineAppendWidth`  来提升击中范围。 + +```javascript +// 基于 line 扩展出新的边 +G6.registerEdge( + 'custom-edge', + { + // 响应状态变化 + setState(name, value, item) { + const group = item.getContainer(); + const shape = group.get('children')[0]; // 顺序根据 draw 时确定 + if (name === 'active') { + if (value) { + shape.attr('stroke', 'red'); + } else { + shape.attr('stroke', '#333'); + } + } + if (name === 'selected') { + if (value) { + shape.attr('lineWidth', 3); + } else { + shape.attr('lineWidth', 2); + } + } + }, + }, + 'line', +); + +// 点击时选中,再点击时取消 +graph.on('edge:click', (ev) => { + const edge = ev.item; + graph.setItemState(edge, 'selected', !edge.hasState('selected')); // 切换选中 +}); + +graph.on('edge:mouseenter', (ev) => { + const edge = ev.item; + graph.setItemState(edge, 'active', true); +}); + +graph.on('edge:mouseleave', (ev) => { + const edge = ev.item; + graph.setItemState(edge, 'active', false); +}); +``` + +
+ +## 5. 自定义箭头 + +G6(v3.5.8 及后续版本)为内置边、自定义边提供了[默认箭头和内置箭头](/zh/docs/manual/middle/elements/edges/arrow)。很多时候,G6 提供的箭头并不能满足业务上的需求,这个时候,就需要我们自定义箭头。
+ +img + +> (左)G6 内置箭头。(右)自定义边带有自定义箭头。 + +⚠️ 注意: G6 3.4.1 后的自定义箭头坐标系有所变化。如下图所示,左图为 G6 3.4.1 之前版本的演示,右图为 G6 3.4.1 及之后版本的演示。箭头由指向 x 轴负方向更正为指向 x 轴正方向。同时,偏移量 `d` 的方向也发生响应变化。不变的是,自定义箭头本身坐标系的原点都与相应边 / path 的端点重合,且自定义箭头的斜率与相应边 / path 端点处的微分斜率相同。 + +img + +> (左)v3.4.1 之前的自定义箭头坐标系演示。(右)v3.4.1 及之后版本的自定义箭头坐标系演示。 + +G6 中有三种途径在边上配置自定义箭头: + +- 配置自定义箭头到边的全局配置中; +- 在数据中为单条边配置; +- 在自定义边中配置。 + +### 方法 1: 全局配置 + +```javascript +const graph = new Graph({ + // ... 图的其他配置项 + defaultEdge: { + style: { + endArrow: { + // 自定义箭头指向(0, 0),尾部朝向 x 轴正方向的 path + path: 'M 0,0 L 20,10 L 20,-10 Z', + // 箭头的偏移量,负值代表向 x 轴正方向移动 + // d: -10, + // v3.4.1 后支持各样式属性 + fill: '#333', + stroke: '#666', + opacity: 0.8, + // ... + }, + }, + }, +}); +``` + +### 方法 2: 在数据中配置 + +```javascript +const data = { + nodes: [ + { id: 'node1' }, + { id: 'node2' }, + // ... 其他节点 + ], + edges: [ + { + source: 'node1', + target: 'node2', + style: { + endArrow: { + // 自定义箭头指向(0, 0),尾部朝向 x 轴正方向的 path + path: 'M 0,0 L 20,10 L 20,-10 Z', + // 箭头的偏移量,负值代表向 x 轴正方向移动 + // d: -10, + // v3.4.1 后支持各样式属性 + fill: '#333', + stroke: '#666', + opacity: 0.8, + // ... + }, + }, + }, + //... 其他边 + ], +}; +``` + +### 方法 3: 自定义边中配置 + +```javascript +// 使用方法二:自定义边,并带有自定义箭头 +G6.registerEdge('line-arrow', { + draw(cfg, group) { + const { startPoint, endPoint } = cfg; + const keyShape = group.addShape('path', { + attrs: { + path: [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x, endPoint.y], + ], + stroke: 'steelblue', + lineWidth: 3, + startArrow: { + // 自定义箭头指向(0, 0),尾部朝向 x 轴正方向的 path + path: 'M 0,0 L 20,10 L 20,-10 Z', + // 箭头的偏移量,负值代表向 x 轴正方向移动 + // d: -10, + // v3.4.1 后支持各样式属性 + fill: '#333', + stroke: '#666', + opacity: 0.8, + // ... + }, + endArrow: { + // 自定义箭头指向(0, 0),尾部朝向 x 轴正方向的 path + path: 'M 0,0 L 20,10 L 20,-10 Z', + // 箭头的偏移量,负值代表向 x 轴正方向移动 + // d: -10, + // v3.4.1 后支持各样式属性 + fill: '#333', + stroke: '#666', + opacity: 0.8, + // ... + }, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'path-shape', + }); + return keyShape; + }, +}); +``` diff --git a/packages/site/docs/manual/middle/elements/edges/defaultEdge.en.md b/packages/site/docs/manual/middle/elements/edges/defaultEdge.en.md new file mode 100644 index 0000000000..2b46a1ca51 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/defaultEdge.en.md @@ -0,0 +1,299 @@ +--- +title: Overview of Edges +order: 0 +--- + +There are 9 built-in edges in G6: + +- line: straight line without control points; +- polyline: polyline with one or more control points; +- arc; +- quadratic: quadratic bezier curve; +- cubic: cubic bezier curve; +- cubic-vertical:vertical cubic bezier curve. The user can not assign the control point for this type of edge; +- cubic-horizontal: horizontal cubic bezier curve. The user can not assign the control point for this type of edge; +- loop: self-loop edge. + +
+img + +## Types of Default Nodes + +The table below shows the built-in edges and their special properties: + +| Name | Description | | +| --- | --- | --- | +| line | A straight line connected two end nodes:
- `controlPoints` does not take effect
- Refer to properties of line for more information
| img | +| polyline | A polyline with one or more control points:
- `controlPoints` is the set of all the control points of polyline. If it is not assigned, G6 will calculate it by A\* algorithm
- Refer to properties of polyline for more information
| img | +| arc | An arc connects two end nodes:
- `controlPoints` does not take effects
- control the bending and direction by `curveOffset`
- Refer to properties of arc for more informatio
| img | +| quadratic | A quadratic bezier curve with one control point:
- The curve will be bended on the center if the `controlPoints` is not defined
- Refer to properties of quadratic for more informatio
| img | +| cubic | A cubic bezier curve with two control points:
- The curve will be bended on the position of 1/3 and 2/3 if the `controlPoints` is not defined
- Refer to properties of cubic for more informatio
| img | +| cubic-vertical | The vertical cubic bezier curve. The user can not assign the control point for this type of edge | img | +| cubic-horizontal | The horizontal cubic bezier curve. The user can not assign the control point for this type of edge | | +| loop | Self-loop edge. Refer to properties of loop for more informatio | img | + +## The Common Property + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | --- | +| id | true | String | The id of the edge, **MUST** be a unique string | +| source | true | String | Number | The id of the source node | +| target | true | String | The id of the target node | +| type | false | String | The type of the edge. It can be the type of a Built-in Edge, or a custom Edge. `'line'` by default | +| sourceAnchor | false | Number | The index of link points on the source node. The link point is the intersection of the edge and related node | +| targetAnchor | false | Number | The index of link points on the target node. The link point is the intersection of the edge and related node | +| style | false | Object | The edge style | +| label | false | String | The label text of the edge | +| labelCfg | false | Object | The configurations of the label | + +### style + +`style` is an object to configure the stroke color, shadow, and so on. Here is the commonly used properties in `style`: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| stroke | false | String | The stroke color | +| lineWidth | false | Number | The line width | +| lineAppendWidth | false | Number | The width of the response area for interaction. In other words, when the edge is too thin to be hitted by mouse, enlarge the value of `lineWidth` to widen the response area | +| endArrow | false | Boolean / Object | The arrow on the end of the edge. When `startArrow` is `true`, show a default arrow on the end of the edge. User can customize an arrow by path, e.g.:
endArrow: {
path: 'M 0,0 L 20,10 L 20,-10 Z', // Customize the path for the arrow
d: -2 // offset
} | +| startArrow | false | Boolean / Object | The arrow on the start of the edge. When `startArrow` is `true`, show a default arrow on the start of the edge. User can customize an arrow by path, e.g.:
endArrow: {
path: 'M 0,0 L 20,10 L 20,-10 Z', // Customize the path for the arrow
d: -2 // offset
} | +| strokeOpacity | false | Number | The stroke opacity | +| shadowColor | false | String | The color of the shadow | +| shadowBlur | false | Number | The blur degree of the shadow | +| shadowOffsetX | false | Number | The x offset of the shadow | +| shadowOffsetY | false | Number | The y offset of the shadow | +| lineDash | false | Array | The style of the dash line. It is an array that describes the length of gaps and line segments. If the number of the elements in the array is odd, the elements will be dulplicated. Such as [5, 15, 25] will be regarded as [5, 15, 25, 5, 15, 25] | +| cursor | false | String | The type of the mouse when hovering the edge. The options are the same as [cursor in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) | + +Configure `style` globally when instantiating the Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // ... Other properties for edges + style: { + stroke: '#eaff8f', + lineWidth: 5, + // ... Other style properties + }, + }, +}); +``` + +### label and labelCfg + +`label` is a string which indicates the content of the label.
`labelCfg` is an object to configure the label. The commonly used configurations of `labelCfg`: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| refX | false | Number | x offset of the label | +| refY | false | Number | y offset of the label | +| position | false | String | The relative position to the edge. Options: `'start'`, `'middle'`, and `'end'`. `'middle'` by default | +| autoRotate | false | Boolean | Whether to activate ratating according to the edge automatically. `false` by default | +| style | false | Object | The style property of the label | + +The commonly used configurations for the `style` in the above table are: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| fill | false | String | The color of the label | +| stroke | false | String | The stroke color | +| lineWidth | false | Number | The line width of the stroke | +| opacity | false | Number | The opacity | +| fontSize | false | Number | The font size | +| fontFamily | false | String | The font family | +| ... The label styles of node and edge are the same, summarized in [Text Shape API](/en/docs/api/shapeProperties/#text) | | | | + +The following code shows how to configure `label` and `labelCfg` globally when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // ... Other properties for nodes + label: 'edge-label', + labelCfg: { + refY: -10, + refX: 60, + }, + }, +}); +``` + +## Configure Edges + +There are three methods to configure edges: Configure edges globally when instantiating a Graph; Configure edges in their data; Configure edges by `graph.edge(edgeFn)`. Their priorities are: + +`graph.edge(edgeFn)` > Configure in data > Configure globally + +That means, if there are same configurations in different ways, the way with higher priority will take effect. + +⚠️ Attention: Expect for `id`, `source`, `target`, `label` which should be assigned to every single edge data, the other configurations in [The Common Property](#the-common-property) and in each edge type (refer to doc of each edge type) support to be assigned by the three ways. + +### Configure Globally When Instantiating Graph + +Assign `defaultEdge` to configure all the nodes globally: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'line', + // Other properties for all the nodes + }, +}); +``` + +### Configure in Data + +To configure different nodes with different properties, you can write the properties into their data individually: + +```javascript +const data = { + nodes: [ + ... // nodes + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'polyline', + // ... // Other properties for this edge + style: { + // ... // Style properties for this edge + } + },{ + source: 'node1', + target: 'node2' + type: 'cubic', + // ... // Other properties for this edge + style: { + // ... // Style properties for this edge + } + }, + // ... // edges + ] +} +``` + +### Configure with graph.edge(edgeFn) + +By this way, we can configure different nodes with different properties.
+ +⚠️Attention: + +- `graph.edge(edgeFn)` must be called **before calling render()**. It does not take effect otherwise; +- It has the highest priority that will override the same properties configured by other ways; +- Each edge will be updated when adding or updating items. It will cost a lot when the amount of the data is large. + +```javascript +// const data = ... +// const graph = ... +graph.edge((edge) => { + return { + id: edge.id, + type: 'polyline', + style: { + fill: 'steelblue', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +## Example + +```javascript +const data = { + nodes: [ + { id: '1', x: 50, y: 50, size: 20 }, + { id: '2', x: 150, y: 50, size: 20 }, + { id: '3', x: 200, y: 50, size: 20 }, + { id: '4', x: 300, y: 130, size: 20 }, + { id: '5', x: 350, y: 50, size: 20 }, + { id: '6', x: 450, y: 50, size: 20 }, + { id: '7', x: 500, y: 50, size: 20 }, + { id: '8', x: 600, y: 50, size: 20 }, + { id: '9', x: 650, y: 50, size: 20 }, + { id: '10', x: 750, y: 50, size: 20 }, + { id: '11', x: 800, y: 50, size: 20 }, + { id: '12', x: 900, y: 150, size: 20 }, + { id: '13', x: 950, y: 50, size: 20 }, + { id: '14', x: 1050, y: 150, size: 20 }, + { id: '15', x: 1100, y: 50, size: 20 }, + ], + edges: [ + { source: '1', target: '2', type: 'line', label: 'line' }, + { source: '3', target: '4', type: 'polyline', label: 'polyline' }, + { source: '5', target: '6', type: 'arc', label: 'arc' }, + { source: '7', target: '8', type: 'quadratic', label: 'quadratic' }, + { source: '9', target: '10', type: 'cubic', label: 'cubic' }, + { source: '11', target: '12', type: 'cubic-vertical', label: 'cubic-vertical' }, + { source: '13', target: '14', type: 'cubic-horizontal', label: 'cubic-horizontal' }, + { source: '15', target: '15', type: 'loop', label: 'loop' }, + ], +}; + +const graph = new G6.Graph({ + container: 'mountNode', + width: 1500, + height: 300, + linkCenter: true, // edges connect the nodes' center +}); +graph.data(data); +graph.render(); +``` + +The result:
img + +### Adjust the Properties + +By writing the properties into the data, we adjust the style and the label of the edges of '9-10' and '11-12'. + +```javascript +// Move the label of this edge +{ + source: '9', + target: '10', + type: 'cubic', + label: 'cubic', + labelCfg: { + refY: -15 // refY is the offset along the clockwise down direction + } +}, +// Set the color, line dash, line width, and style of the label of this edge +{ + source: '11', + target: '12', + type: 'cubic-vertical', + color: '#722ed1', // Color + size: 5, // Line width + style: { + lineDash: [2, 2] // Dash line + }, + label: 'cubic-vertical', + labelCfg: { + position: 'center', // The position of the label= + autoRotate: true, // Whether to rotate the label according to the edge + style: { + stroke: 'white', // White stroke for the label + lineWidth: 5, // The line width of the stroke + fill: '#722ed1', // The color of the text + } + } +} +``` + +img + +## Related Reading + +- [State](/en/docs/manual/middle/states/state) —— Change the styles during the interaction process. diff --git a/packages/site/docs/manual/middle/elements/edges/defaultEdge.zh.md b/packages/site/docs/manual/middle/elements/edges/defaultEdge.zh.md new file mode 100644 index 0000000000..7e50ff780d --- /dev/null +++ b/packages/site/docs/manual/middle/elements/edges/defaultEdge.zh.md @@ -0,0 +1,300 @@ +--- +title: 边总览 +order: 0 +--- + +G6 提供了 9 种内置边: + +- line:直线,不支持控制点; +- polyline:折线,支持多个控制点; +- arc:圆弧线; +- quadratic:二阶贝塞尔曲线; +- cubic:三阶贝塞尔曲线; +- cubic-vertical:垂直方向的三阶贝塞尔曲线,不考虑用户从外部传入的控制点; +- cubic-horizontal:水平方向的三阶贝塞尔曲线,不考虑用户从外部传入的控制点; +- loop:自环。 + +这些内置边的默认样式分别如下图所示。
img + +本文将概述 G6 中的各个内置边类型、内置边的通用属性、配置方法。各类型边详细配置项及配置方法见本目录下相应文档。 + +## 内置边类型说明 + +下面表格中显示了内置的各类边,同时对一些特殊的字段进行了说明: + +| 名称 | 描述 | | +| --- | --- | --- | +| line | 连接两个节点的直线:
- `controlPoints` 不生效
- 更多配置详见 line 边的配置
| img | +| polyline | 多段线段构成的折线,连接两个端点:
- `controlPoints` 表示所有线段的拐点,不指定时根据 A\* 算法自动生成折线
- 更多配置详见 polyline 边的配置
| img | +| arc | 连接两个节点的一段圆弧:
- `controlPoints` 不生效
- 使用 curveOffset 指定弧的弯曲程度,其正负影响弧弯曲的方向
- 更多配置详见 arc 边的配置
| img | +| quadratic | 只有一个控制点的曲线:
- `controlPoints` 不指定时,会默认线的一半处弯曲
- 更多配置详见 quadratic 边的配置
| img | +| cubic | 有两个控制点的曲线:
- `controlPoints` 不指定时,会默认线的 1/3, 2/3 处弯曲
- 更多配置详见 cubic 边的配置
| img | +| cubic-vertical | 垂直方向的三阶贝塞尔曲线,不考虑用户从外部传入的控制点 | img | +| cubic-horizontal | 水平方向的三阶贝塞尔曲线,不考虑用户从外部传入的控制点 | img | +| loop | 自环。更多配置详见 loop 边的配置 | img | + +## 边的通用属性 + +所有内置的边支持的通用属性: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| id | true | String | 边唯一 ID,**必须**是唯一的 string | +| source | true | String | 起始点 id | +| target | true | String | 结束点 id | +| type | false | String | 指定边的类型,可以是内置边的类型名称,也可以是自定义边的名称。默认为 `'line'` | +| sourceAnchor | false | Number | 边的起始节点上的锚点的索引值 | +| targetAnchor | false | Number | 边的终止节点上的锚点的索引值 | +| style | false | Object | 边的样式属性 | +| label | false | String | 文本文字,如果没有则不会显示 | +| labelCfg | false | Object | 文本配置项 | + +### 样式属性  style + +Object 类型。通过 `style` 配置来修改边的颜色、线宽等属性。下表是 `style` 对象中常用的配置项: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| stroke | false | String | 边的颜色 | +| lineWidth | false | Number | 边宽度 | +| lineAppendWidth | false | Number | 边响应鼠标事件时的检测宽度,当 `lineWidth` 太小而不易选中时,可以通过该参数提升击中范围 | +| endArrow | false | Boolean / Object | 为 `true` 时在边的结束端绘制默认箭头,为 `false` 时不绘制结束端箭头;也可以使用[内置箭头配置](),例如:
endArrow: {
path: G6.Arrow.vee(10, 20, 10), // 内置箭头,参数为箭头宽度、长度、偏移量 d(默认为 0)
d: 10 // 偏移量
} ;或通过 path 自定义的箭头,例如:
endArrow: {
path: 'M 0,0 L 20,10 L 20,-10 Z', // 自定义箭头路径
d: -2 // 偏移量
} | +| startArrow | false | Boolean / Object | 为 `true` 时在边的开始端绘制默认箭头,为 `false` 时不绘制结束端箭头;也可以使用[内置箭头配置](),例如:
startArrow: {
path: G6.Arrow.vee(10, 20, 10), // 内置箭头,参数为箭头宽度、长度、偏移量 d(默认为 0)
d: 10 // 偏移量
} ;或通过 path 自定义的箭头,例如:
startArrow: {
path: 'M 0,0 L 20,10 L 20,-10 Z', // 自定义箭头路径
d: -2 // 偏移量
} | +| strokeOpacity | false | Number | 边透明度 | +| shadowColor | false | String | 阴影颜色 | +| shadowBlur | false | Number | 阴影模糊程度 | +| shadowOffsetX | false | Number | 阴影 x 方向偏移量 | +| shadowOffsetY | false | Number | 阴影 y 方向偏移量 | +| lineDash | false | Array | 设置线的虚线样式,可以指定一个数组。一组描述交替绘制线段和间距(坐标空间单位)长度的数字。 如果数组元素的数量是奇数, 数组的元素会被复制并重复。例如, [5, 15, 25] 会变成 [5, 15, 25, 5, 15, 25]。 | +| cursor | false | String | 鼠标在该边上时的鼠标样式,[CSS 的 cursor](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) 选项都支持 | + +下面代码演示在实例化图时全局配置方法中配置 `style`: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // ... 其他属性 + style: { + stroke: '#eaff8f', + lineWidth: 5, + // ... 其他样式属性 + }, + }, +}); +``` + +### 标签文本 label 及其配置  labelCfg + +`label` String 类型。标签文本的文字内容。
`labelCfg` Object 类型。配置标签文本。下面是 `labelCfg` 对象中的常用配置项: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| refX | false | Number | 标签在 x 方向的偏移量 | +| refY | false | Number | 标签在 y 方向的偏移量 | +| position | false | String | 文本相对于边的位置,目前支持的位置有:`'start'`,`'middle'`,`'end'`。默认为`'middle'`。 | +| autoRotate | false | Boolean | 标签文字是否跟随边旋转,默认 `false` | +| style | false | Object | 标签的样式属性 | + +上表中的标签的样式属性 `style` 的常用配置项如下: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| fill | false | String | 文本颜色 | +| stroke | false | String | 文本描边颜色 | +| lineWidth | false | Number | 文本描边粗细 | +| opacity | false | Number | 文本透明度 | +| fontFamily | false | String | 文本字体 | +| fontSize | false | Number | 文本字体大小 | +| ... 节点标签与边标签样式属性相同,统一整理在 [Text 图形 API](/zh/docs/api/shapeProperties/#文本-text) | | | | + +下面代码演示在实例化图时全局配置方法中配置  `label` 和  `labelCfg`。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + // ... 其他属性 + label: 'edge-label', + labelCfg: { + refY: -10, + refX: 60, + }, + }, +}); +``` + +## 边的配置方法 + +配置边的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.edge(edgeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 graph.edge(edgeFn) 配置 > 数据中动态配置 > 实例化图时全局配置 + +即有相同的配置项时,优先级高的方式将会覆盖优先级低的。 + +⚠️ 注意: 除 `id`、`source`、`target`、`label` 应当配置到每条边数据中外,其余的 [边的通用属性](#边的通用属性) 以及各个边类型的特有属性(见内置边类型)均支持三种配置方式。 + +### 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultEdge`  配置边,这里的配置是全局的配置,将会在所有边上生效。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultEdge: { + type: 'line', + // ... 其他配置 + }, +}); +``` + +### 在数据中动态配置 + +如果需要使不同边有不同的配置,可以将配置写入到边数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + ... // 节点 + ], + edges: [{ + source: 'node0', + target: 'node1' + type: 'polyline', + ... // 其他配置 + style: { + ... // 样式属性,每种边的详细样式属性参见各边文档 + } + },{ + source: 'node1', + target: 'node2' + type: 'cubic', + ... // 其他配置 + style: { + ... // 样式属性,每种边的详细样式属性参见各边文档 + } + }, + ... // 其他边 + ] +} +``` + +### 使用 graph.edge() + +该方法可以为不同边进行不同的配置。
提示: + +- 该方法必须**在 render 之前调用**,否则不起作用; +- 由于该方法优先级最高,将覆盖其他地方对边的配置,这可能将造成一些其他配置不生效的疑惑; +- 该方法在增加元素、更新元素时会被调用,如果数据量大、每条边上需要更新的内容多时,可能会有性能问题。 + +```javascript +// const data = ... +// const graph = ... +graph.edge((edge) => { + return { + id: edge.id, + type: 'polyline', + style: { + fill: 'steelblue', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +## 实例演示 + +```javascript +const data = { + nodes: [ + { id: '1', x: 50, y: 50, size: 20 }, + { id: '2', x: 150, y: 50, size: 20 }, + { id: '3', x: 200, y: 50, size: 20 }, + { id: '4', x: 300, y: 130, size: 20 }, + { id: '5', x: 350, y: 50, size: 20 }, + { id: '6', x: 450, y: 50, size: 20 }, + { id: '7', x: 500, y: 50, size: 20 }, + { id: '8', x: 600, y: 50, size: 20 }, + { id: '9', x: 650, y: 50, size: 20 }, + { id: '10', x: 750, y: 50, size: 20 }, + { id: '11', x: 800, y: 50, size: 20 }, + { id: '12', x: 900, y: 150, size: 20 }, + { id: '13', x: 950, y: 50, size: 20 }, + { id: '14', x: 1050, y: 150, size: 20 }, + { id: '15', x: 1100, y: 50, size: 20 }, + ], + edges: [ + { source: '1', target: '2', type: 'line', label: 'line' }, + { source: '3', target: '4', type: 'polyline', label: 'polyline' }, + { source: '5', target: '6', type: 'arc', label: 'arc' }, + { source: '7', target: '8', type: 'quadratic', label: 'quadratic' }, + { source: '9', target: '10', type: 'cubic', label: 'cubic' }, + { source: '11', target: '12', type: 'cubic-vertical', label: 'cubic-vertical' }, + { source: '13', target: '14', type: 'cubic-horizontal', label: 'cubic-horizontal' }, + { source: '15', target: '15', type: 'loop', label: 'loop' }, + ], +}; + +const graph = new G6.Graph({ + container: 'mountNode', + width: 1500, + height: 300, + linkCenter: true, // 使边连入节点的中心 +}); +graph.data(data); +graph.render(); +``` + +显示结果:
img + +### 调整边的样式 + +可以在边上添加文本,修改边的样式。下面演示将配置写入数据的方式配置边。使用下面代码替换上面代码中的 9-10、11-12 两条边数据,修改这两条边的样式和其文本。 + +```javascript +// 使 9-10 的 cubic 边文本下移 15 像素 +{ + source: '9', + target: '10', + type: 'cubic', + label: 'cubic', + labelCfg: { + refY: -15 // refY 默认是顺时针方向向下,所以需要设置负值 + } +}, +// 设置 11-12 的 cubic-vertical 边的颜色、虚线、粗细,并设置文本样式、随边旋转 +{ + source: '11', + target: '12', + type: 'cubic-vertical', + color: '#722ed1', // 边颜色 + size: 5, // 边粗细 + style: { + lineDash: [2, 2] // 虚线边 + }, + label: 'cubic-vertical', + labelCfg: { + position: 'center', // 其实默认就是 center,这里写出来便于理解 + autoRotate: true, // 使文本随边旋转 + style: { + stroke: 'white', // 给文本添加白边和白色背景 + lineWidth: 5, // 文本白边粗细 + fill: '#722ed1', // 文本颜色 + } + } +} +``` + +img + +## 相关阅读 + +- [状态 State](/zh/docs/manual/middle/states/state) —— 交互过程中的样式变化。 diff --git a/packages/site/docs/manual/middle/elements/methods/edgeVisible.en.md b/packages/site/docs/manual/middle/elements/methods/edgeVisible.en.md new file mode 100644 index 0000000000..b61d4c449f --- /dev/null +++ b/packages/site/docs/manual/middle/elements/methods/edgeVisible.en.md @@ -0,0 +1,67 @@ +--- +title: The Visibility +order: 5 +--- + +## Show/Hide a Node or an Edge + +Show and hide the node/edge/combo by the following six functions: + +```javascript +// Show the instance of the node nodeItem. The property `visible` of the node will be true after calling the following code +nodeItem.show(); + +// Hide the instance of the node nodeItem. The property `visible` of the node will be false after calling the following code +nodeItem.hide(); + +// Show the instance of the edge edgeItem. The property `visible` of the node will be true after calling the following code +edgeItem.show(); + +// Hide the instance of the edge edgeItem. The property `visible` of the node will be false after calling the following code +edgeItem.hide(); + +// Show the instance of the combo comboItem. The property `visible` of the node will be true after calling the following code +comboItem.show(); + +// Hide the instance of the combo comboItem. The property `visible` of the node will be false after calling the following code +comboItem.hide(); +``` + +## Example + +img + +In this example, we bind the listeners to node clicking, edge clicking, and canvas clicking. And show/hide items in the inside the listeners: + +```javascript +// Hide the node when the mouse clicks on it +graph.on('node:click', (ev) => { + const node = ev.item; + console.log('before hide(), the nodevisible = ', node.get('visible')); + node.hide(); + graph.paint(); + console.log('after hide(), the node visible = ', node.get('visible')); +}); + +// Hide the edge when the mouse clicks on it +graph.on('edge:click', (ev) => { + const edge = ev.item; + console.log('before hide(), the edge visible = ', edge.get('visible')); + edge.hide(); + graph.paint(); + console.log('after hide(), the edge visible = ', edge.get('visible')); +}); + +// Show all the nodes and edges when the mouse clicks the canvas +graph.on('canvas:click', (ev) => { + const nodes = graph.getNodes(); + const edges = graph.getEdges(); + nodes.forEach((node) => { + node.show(); + }); + edges.forEach((edge) => { + edge.show(); + }); + graph.paint(); +}); +``` diff --git a/packages/site/docs/manual/middle/elements/methods/edgeVisible.zh.md b/packages/site/docs/manual/middle/elements/methods/edgeVisible.zh.md new file mode 100644 index 0000000000..092d264fcd --- /dev/null +++ b/packages/site/docs/manual/middle/elements/methods/edgeVisible.zh.md @@ -0,0 +1,67 @@ +--- +title: 显示与隐藏 +order: 5 +--- + +## 元素的显示/隐藏 + +使用下面六个函数可以实现节点、边、Combo 的显示/隐藏: + +```javascript +// 显示节点实例 nodeItem,该节点的 visible 属性值在该方法调用后被置为 true +nodeItem.show(); + +// 隐藏节点实例 nodeItem,该节点的 visible 属性值在该方法调用后被置为 false +nodeItem.hide(); + +// 显示边实例 edgeItem,该边的 visible 属性值在该方法调用后被置为 true +edgeItem.show(); + +// 隐藏边实例 edgeItem,该边的 visible 属性值在该方法调用后被置为 false +edgeItem.hide(); + +// 显示边实例 comboItem,该 Combo 的 visible 属性值在该方法调用后被置为 true +comboItem.show(); + +// 隐藏边实例 comboItem,该 Combo 的 visible 属性值在该方法调用后被置为 false +comboItem.hide(); +``` + +## 示例 + +img + +该示例摘取了元素显示/隐藏的相关操作部分,通过鼠标监听对节点、边、画布的点击事件,显示和隐藏元素: + +```javascript +// 鼠标点击节点,隐藏该节点 +graph.on('node:click', (ev) => { + const node = ev.item; + console.log('before hide(), the nodevisible = ', node.get('visible')); + node.hide(); + graph.paint(); + console.log('after hide(), the node visible = ', node.get('visible')); +}); + +// 鼠标点击边,隐藏该边 +graph.on('edge:click', (ev) => { + const edge = ev.item; + console.log('before hide(), the edge visible = ', edge.get('visible')); + edge.hide(); + graph.paint(); + console.log('after hide(), the edge visible = ', edge.get('visible')); +}); + +// 鼠标点击画布,显示所有节点和边 +graph.on('canvas:click', (ev) => { + const nodes = graph.getNodes(); + const edges = graph.getEdges(); + nodes.forEach((node) => { + node.show(); + }); + edges.forEach((edge) => { + edge.show(); + }); + graph.paint(); +}); +``` diff --git a/packages/site/docs/manual/middle/elements/methods/elementIndex.en.md b/packages/site/docs/manual/middle/elements/methods/elementIndex.en.md new file mode 100644 index 0000000000..a6ddbedfe0 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/methods/elementIndex.en.md @@ -0,0 +1,235 @@ +--- +title: The Visual Level of Node and Edge +order: 4 +--- + +The visual levels (zIndex) of nodes and edges are refered to their [Graphics Group](/en/docs/manual/middle/elements/shape/graphics-group) (hereinafter referred to as Shape). (⚠️Attention: The Graphics Group is different from the [Node Combo](/en/docs/manual/middle/elements/combos/defaultCombo), the differences are described in [Graphics Group](/en/docs/manual/middle/elements/shape/graphics-group)). + +In [Graphics Group](/en/docs/manual/middle/elements/shape/graphics-group), we stated: All the nodes instances in a Graph is grouped by a Group named `nodeGroup`, all the edges instances are grouped by `edgeGroup`. And the visual level (zIndex) of `nodeGroup` is higher than `edgeGroup`, which means all the nodes will be drawed on the top of all the edges. + +Sometimes, we want to draw the edges on the top. For example, highlighting a node and its related edges. In this situation, you can configure `groupByTypes` of the graph to false and call `toFront()` and `toBack()` to order the nodes or edges. + +The expected effect is: the related nodes and edges are drawed on the top of others when the mouse enters a node; Restore the visual levels (zIndex) when the mouse moves out of the node. Complete Code of the Demo.
img + +There are 3 steps to implement the expected effect: + +- Step 1: Configure`groupByTypes` to `false` when instantiating a Graph; +- Step 2: Place the nodes to the top of edges; +- Step 3: Change the visual levels in the listener function of mouse entering. + +## Prerequisite Code + +The following code imports G6, defines the data, instantiates the Graph, renders the graph. We will modify this code to implement the expected effect. + +```javascript +// The source data +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 200, + size: 20, + }, + { + id: 'node2', + x: 150, + y: 150, + size: 20, + }, + { + id: 'node3', + x: 150, + y: 250, + size: 20, + }, + { + id: 'node4', + x: 150, + y: 200, + size: 20, + }, + ], + edges: [ + { + id: 'edge0', + source: 'node0', + target: 'node1', + }, + { + id: 'edge1', + source: 'node2', + target: 'node3', + }, + ], +}; + +// Instantiate the graph +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // Make the edge thicker for demonstration + defaultEdge: { + style: { + lineWidth: 2, + }, + }, +}); + +// Load the data +graph.data(data); +// Render the graph +graph.render(); +``` + +## Step 1 Configure the Graph + +`groupByTypes` is a configuration of Graph with `true` as default value. That means that all the nodes are grouped in a Group named `nodeGroup`, all the edges are groupd in `edgeGroup`, and `nodeGroup` is on the top of `edgeGroup`. Assign `false` to `groupByTypes` to cancel the `nodeGroup` and `edgeGroup`. And all the nodes and edges will be grouped in one Group. The visual level (zIndex) in determined by their generation order. + +### Configuration + +| Name | Type | Default | Description | +| ------------ | ------- | ------- | ------------------------------------------------------- | +| groupByTypes | Boolean | true | Whether nodes and edges are grouped in different Group. | + +### Usage + +Modify the code about instantiating the Graph in Prerequisite Code. Add `groupByTypes` with `false`: + +```javascript +const graph = new G6.Graph({ + // ... // Other configurations + groupByTypes: false, +}); +``` + +We obtain this result now:
img + +## Step 2 Place the Nodes on the Top + +Due to the `groupByTypes` with `false` and edges are generated after nodes, the edges are on the top of the nodes in the figure above, which is a little strange. To draw the nodes on the top, we call `toFront()` for each node after `graph.render()`. + +### Description for Functions + +```javascript +// Shift the node instance nodeItem to the front +nodeItem.toFront(); +// Shift the node instance nodeItem to the back +nodeItem.toBack(); +// Shift the edge instance edgeItem to the front +edgeItem.toFront(); +// Shift the edge instance edgeItem to the back +edgeItem.toBack(); +``` + +### Usage + +```javascript +// const graph = ... +graph.data(data); +graph.render(); +// Get all the node instances of the graph +const nodes = graph.getNodes(); +// Traverse the nodes, and shift them to the front +nodes.forEach((node) => { + node.toFront(); +}); +// Repaint the graph after shifting +graph.paint(); +``` + +
Now, all the nodes are drawed on the top of edges:
img + +## Step 3 Change the Visual Levels in the Listener Function of Mouse Entering + +When the mouse enters a node, the related nodes and edges will be shifted to the front. And they will be restored after mouse leaving. + +### Description for Functions + +Listen the mouse entering and leaving by the following four functions: + +```javascript +// Mouse enters a node +graph.on('node:mouseenter', (ev) => { + // ... +}); + +// Mouse leaves a node +graph.on('node:mouseleave', (ev) => { + // ... +}); + +// Mouse enters an edge +graph.on('edge:mouseenter', (ev) => { + // ... +}); + +// Mouse leaves an edge +graph.on('edge:mouseleave', (ev) => { + // ... +}); +``` + +### Usage + +```javascript +// Mouse enters an edge +graph.on('edge:mouseenter', (ev) => { + // Get the target of the entering event + const edge = ev.item; + // The source node of the edge + const source = edge.getSource(); + // The target node of the edge + const target = edge.getTarget(); + // Shift the edge to the front, and then shift the end nodes to the front + edge.toFront(); + source.toFront(); + target.toFront(); + // Attention: the following code must be called to repaint the graph + graph.paint(); +}); + +graph.on('edge:mouseleave', (ev) => { + // Get all the edge instances of the graph + const edges = graph.getEdges(); + // Travers the edges, shift them to the back to restore + edges.forEach((edge) => { + edge.toBack(); + }); + // Attention: the following code must be called to repaint the graph + graph.paint(); +}); + +graph.on('node:mouseenter', (ev) => { + // Get the target of the entering event + const node = ev.item; + // Get the related edges of the node + const edges = node.getEdges(); + // Travers the related edges, shift them to the front, and then shift the end nodes to the front + edges.forEach((edge) => { + edge.toFront(); + edge.getSource().toFront(); + edge.getTarget().toFront(); + }); + // Attention: the following code must be called to repaint the graph + graph.paint(); +}); + +graph.on('node:mouseleave', (ev) => { + // Get all the edge instances of the graph + const edges = graph.getEdges(); + // Travers the edges, shift them to the back to restore + edges.forEach((edge) => { + edge.toBack(); + }); + // Attention: the following code must be called to repaint the graph + graph.paint(); +}); +``` diff --git a/packages/site/docs/manual/middle/elements/methods/elementIndex.zh.md b/packages/site/docs/manual/middle/elements/methods/elementIndex.zh.md new file mode 100644 index 0000000000..93ae552694 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/methods/elementIndex.zh.md @@ -0,0 +1,233 @@ +--- +title: 节点与边的层级 +order: 4 +--- + +节点与边在视觉上的层级涉及到了它们相对应的 [图形分组 Group](/zh/docs/manual/middle/elements/shape/graphics-group)。本文提到的所有分组 Group 都为 G6 的图形分组 Group,而非 G6 的  [节点分组 Combo](/zh/docs/manual/middle/elements/combos/defaultCombo),其区别在 [图形分组 Group](/zh/docs/manual/middle/elements/shape/graphics-group)  中说明。 + +在 [图形分组 Group](/zh/docs/manual/middle/elements/shape/graphics-group) 中我们提到:在 G6 中,Graph 的一个实例中的所有节点属于同一个变量名为 `nodeGroup` 的 group,所有的边属于同一个变量名为 `edgeGroup` 的 group。节点 group 在视觉上的层级(zIndex)高于边 group,即所有节点会绘制在所有边的上层。 + +但有时,我们需要让边在视觉上在节点上层。例如,高亮节点及其相关边和邻居、高亮一条边等。可以通过配合图实例的配置项  `groupByTypes` 以及节点和边的 `toFront()` 与 `toBack()` 函数实现。为实现如下效果:鼠标进入节点时,提升相关边以及邻居节点的层级;离开节点时恢复;鼠标进入边时,提升边及其两端点的层级;离开边时恢复。Demo 完整代码
img + +要实现上图效果,需要以下步骤: + +- Step 1:实例化图时配置 `groupByTypes` 为 `false`; +- Step 2:将节点放置在边上层; +- Step 3:监听鼠标事件并改变目标元素层级。 + +## 前提代码 + +下面代码完成了引入 G6、数据设置、实例化图、渲染图的命令等。后文将修改下面这份代码中以达到上图高亮效果。 + +```javascript +// 数据源 +const data = { + nodes: [ + { + id: 'node0', + x: 100, + y: 100, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 200, + size: 20, + }, + { + id: 'node2', + x: 150, + y: 150, + size: 20, + }, + { + id: 'node3', + x: 150, + y: 250, + size: 20, + }, + { + id: 'node4', + x: 150, + y: 200, + size: 20, + }, + ], + edges: [ + { + id: 'edge0', + source: 'node0', + target: 'node1', + }, + { + id: 'edge1', + source: 'node2', + target: 'node3', + }, + ], +}; + +// 实例化图 +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + // 为方便演示,加粗边 + defaultEdge: { + style: { + lineWidth: 2, + }, + }, +}); + +// 读取数据 +graph.data(data); +// 渲染图 +graph.render(); +``` + +## Step 1 实例化图时的配置 + +`groupByTypes` 是图的一个配置项,当其为默认值 `true` 时,所有节点在一个名为 `nodeGroup` 的分组,所有边在另一个名为 `edgeGroup` 的分组,且 `nodeGroup` 在 `edgeGroup` 上层。将其设置为 `false` 后,将不存在 `nodeGroup` 和 `edgeGroup`,所有节点和边在同一个分组,它们的层级根据生成的顺序决定。 + +### 参数描述 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| groupByTypes | Boolean | true | 各种元素是否在一个分组内,决定节点和边的层级问题,默认情况下所有的节点在一个分组中,所有的边在一个分组中,当这个参数为 `false` 时,节点和边的层级根据生成的顺序确定。 | + +### 使用方式 + +更改 前提代码 中实例化图部分代码,添加 `groupByTypes` 配置项,并设置为 `false`: + +```javascript +const graph = new G6.Graph({ + // ... // 其他配置 + groupByTypes: false, +}); +``` + +此时,将会得到如下效果:
img + +## Step 2 将节点放置在边上层 + +上一步结果中节点在边的下层不符合常规,是由于 `groupByTypes` 为 `false` 时,节点和边的层级根据生成的顺序确定,而边的生成顺序在节点之后,因此所有边被绘制到了节点上方。为了使图符合常规——节点在上层,边在下层,可以在 `graph.render()` 之后将所有的节点通过 `toFront()` 函数提前。 + +### 函数描述 + +```javascript +// 将节点实例 nodeItem 提前到其父级 group 的最前面 +nodeItem.toFront(); +// 将节点实例 nodeItem 放置到其父级 group 的最后面 +nodeItem.toBack(); +// 将边实例 edgeItem 提前到其父级 group 的最前面 +edgeItem.toFront(); +// 将边实例 edgeItem 放置到其父级 group 的最后面 +edgeItem.toBack(); +``` + +### 使用方法 + +```javascript +// const graph = ... +graph.data(data); +graph.render(); +// 获取图上的所有节点实例 +const nodes = graph.getNodes(); +// 遍历节点实例,将所有节点提前。 +nodes.forEach((node) => { + node.toFront(); +}); +// 更改层级后需要重新绘制图 +graph.paint(); +``` + +
这样,所有节点被绘制在边上层:
img + +## Step 3 监听鼠标事件并改变目标元素层级 + +在效果图中,鼠标进入节点时,相关边和节点的层级被提升到最上层,鼠标离开节点使恢复。边同理。这一步将实现这一交互效果。 + +### 函数描述 + +使用下面四个函数监听鼠标的进入、离开元素的事件: + +```javascript +// 鼠标进入节点事件 +graph.on('node:mouseenter', (ev) => { + // ... +}); + +// 鼠标离开节点事件 +graph.on('node:mouseleave', (ev) => { + // ... +}); + +// 鼠标进入边事件 +graph.on('edge:mouseenter', (ev) => { + // ... +}); + +// 鼠标离开边事件 +graph.on('edge:mouseleave', (ev) => { + // ... +}); +``` + +### 使用方法 + +```javascript +// 鼠标进入节点事件 +graph.on('edge:mouseenter', (ev) => { + // 获得鼠标当前目标边 + const edge = ev.item; + // 该边的起始点 + const source = edge.getSource(); + // 该边的结束点 + const target = edge.getTarget(); + // 先将边提前,再将端点提前。这样该边两个端点还是在该边上层,较符合常规。 + edge.toFront(); + source.toFront(); + target.toFront(); + // 注意:必须调用以根据新的层级顺序重绘 + graph.paint(); +}); + +graph.on('edge:mouseleave', (ev) => { + // 获得图上所有边实例 + const edges = graph.getEdges(); + // 遍历边,将所有边的层级放置在后方,以恢复原样 + edges.forEach((edge) => { + edge.toBack(); + }); + // 注意:必须调用以根据新的层级顺序重绘 + graph.paint(); +}); + +graph.on('node:mouseenter', (ev) => { + // 获得鼠标当前目标节点 + const node = ev.item; + // 获取该节点的所有相关边 + const edges = node.getEdges(); + // 遍历相关边,将所有相关边提前,再将相关边的两个端点提前,以保证相关边的端点在边的上方常规效果 + edges.forEach((edge) => { + edge.toFront(); + edge.getSource().toFront(); + edge.getTarget().toFront(); + }); + // 注意:必须调用以根据新的层级顺序重绘 + graph.paint(); +}); + +graph.on('node:mouseleave', (ev) => { + // 获得图上所有边实例 + const edges = graph.getEdges(); + // 遍历边,将所有边的层级放置在后方,以恢复原样 + edges.forEach((edge) => { + edge.toBack(); + }); + // 注意:必须调用以根据新的层级顺序重绘 + graph.paint(); +}); +``` diff --git a/packages/site/docs/manual/middle/elements/methods/lock-node.en.md b/packages/site/docs/manual/middle/elements/methods/lock-node.en.md new file mode 100644 index 0000000000..20358ed517 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/methods/lock-node.en.md @@ -0,0 +1,215 @@ +--- +title: Lock / Unlock Node +order: 11 +--- + +The functions for locking a node `lock()`, `unlock()`, and `hasLocked()` are supported by the versions from G6 V3.1.4. The locked node will not response the drag event any more. But it still can be moved while dragging and zooming the canvas. You can register a [Custom Behavior](/en/docs/manual/middle/states/custom-behavior) to fix the node when dragging and zooming. + +## Fix the Locked Node While Dragging + +The built-in `drag-canvas` in G6 does not take the locked node into consideration. In most situations, it is a reasonable Behavior. For some special requirements that require to fix hte locked node when dragging, you can register a custom Behavior as shown bolow to achieve them. + +```javascript +import G6 from '@antv/g6'; +const Util = G6.Util; +const abs = Math.abs; +const DRAG_OFFSET = 10; +const body = document.body; +const ALLOW_EVENTS = [16, 17, 18]; + +G6.registerBehavior('drag-canvas-exclude-lockedNode', { + getDefaultCfg() { + return { + direction: 'both', + }; + }, + getEvents() { + return { + 'canvas:mousedown': 'onMouseDown', + 'canvas:mousemove': 'onMouseMove', + 'canvas:mouseup': 'onMouseUp', + 'canvas:click': 'onMouseUp', + 'canvas:mouseleave': 'onOutOfRange', + keyup: 'onKeyUp', + keydown: 'onKeyDown', + }; + }, + updateViewport(e) { + const origin = this.origin; + const clientX = +e.clientX; + const clientY = +e.clientY; + if (isNaN(clientX) || isNaN(clientY)) { + return; + } + let dx = clientX - origin.x; + let dy = clientY - origin.y; + if (this.get('direction') === 'x') { + dy = 0; + } else if (this.get('direction') === 'y') { + dx = 0; + } + this.origin = { + x: clientX, + y: clientY, + }; + // The difference to built-in drag-canvas: + const lockedNodes = this.graph.findAll('node', (node) => !node.hasLocked()); + lockedNodes.forEach((node) => { + node.get('group').translate(dx, dy); + }); + this.graph.paint(); + }, + onMouseDown(e) { + if (this.keydown) { + return; + } + + this.origin = { x: e.clientX, y: e.clientY }; + this.dragging = false; + }, + onMouseMove(e) { + if (this.keydown) { + return; + } + + e = Util.cloneEvent(e); + const graph = this.graph; + if (!this.origin) { + return; + } + if (this.origin && !this.dragging) { + if (abs(this.origin.x - e.clientX) + abs(this.origin.y - e.clientY) < DRAG_OFFSET) { + return; + } + if (this.shouldBegin(e, this)) { + e.type = 'dragstart'; + graph.emit('canvas:dragstart', e); + this.dragging = true; + } + } + if (this.dragging) { + e.type = 'drag'; + graph.emit('canvas:drag', e); + } + if (this.shouldUpdate(e, this)) { + this.updateViewport(e); + } + }, + onMouseUp(e) { + if (this.keydown) { + return; + } + + if (!this.dragging) { + this.origin = null; + return; + } + e = Util.cloneEvent(e); + const graph = this.graph; + if (this.shouldEnd(e, this)) { + this.updateViewport(e); + } + e.type = 'dragend'; + graph.emit('canvas:dragend', e); + this.endDrag(); + }, + endDrag() { + if (this.dragging) { + this.origin = null; + this.dragging = false; + // Check whether it exists mouseup event outside. Unbind it if it exists. + const fn = this.fn; + if (fn) { + body.removeEventListener('mouseup', fn, false); + this.fn = null; + } + } + }, + // If user move the mouse out of the canvas when dragging, the drag event might not be ended by releasing the mouse. Thus, listen to the mouseup event ouside the canvas to end it. + onOutOfRange(e) { + if (this.dragging) { + const self = this; + const canvasElement = self.graph.get('canvas').get('el'); + const fn = (ev) => { + if (ev.target !== canvasElement) { + self.onMouseUp(e); + } + }; + this.fn = fn; + body.addEventListener('mouseup', fn, false); + } + }, + onKeyDown(e) { + const code = e.keyCode || e.which; + if (!code) { + return; + } + if (ALLOW_EVENTS.indexOf(code) > -1) { + this.keydown = true; + } else { + this.keydown = false; + } + }, + onKeyUp() { + this.keydown = false; + }, +}); +``` + +## Fix the Locked Node While Zooming + +Built-in Behavior `zoom-canvas` zooms all the nodes including locked nodes. Register a custom Behavior to fix the locked nodes. + +```javascript +const DELTA = 0.05; + +G6.registerBehavior('zoom-canvas-exclude-lockedNode', { + getDefaultCfg() { + return { + sensitivity: 2, + minZoom: 0.1, + maxZoom: 10, + }; + }, + getEvents() { + return { + wheel: 'onWheel', + }; + }, + onWheel(e) { + e.preventDefault(); + if (!this.shouldUpdate(e, this)) { + return; + } + const graph = this.graph; + const canvas = graph.get('canvas'); + const point = canvas.getPointByClient(e.clientX, e.clientY); + const sensitivity = this.get('sensitivity'); + let ratio = graph.getZoom(); + // To be Compatible with IE, Firefox, and Chrome + if (e.wheelDelta < 0) { + ratio = 1 - DELTA * sensitivity; + } else { + ratio = 1 + DELTA * sensitivity; + } + const zoom = ratio * graph.getZoom(); + if (zoom > this.get('maxZoom') || zoom < this.get('minZoom')) { + return; + } + graph.zoom(ratio, { x: point.x, y: point.y }); + const lockedNodes = this.graph.findAll('node', (node) => !node.hasLocked()); + lockedNodes.forEach((node) => { + const matrix = Util.clone(node.get('group').getMatrix()); + const center = node.getModel(); + matrix = Util.transform(matrix, [ + ['t', -center.x, -center.y], + ['s', ratio, ratio], + ['t', center.x, center.y], + ]); + node.get('group').setMatrix(matrix); + }); + graph.paint(); + graph.emit('wheelzoom', e); + }, +}); +``` diff --git a/packages/site/docs/manual/middle/elements/methods/lock-node.zh.md b/packages/site/docs/manual/middle/elements/methods/lock-node.zh.md new file mode 100644 index 0000000000..3f190d3c9a --- /dev/null +++ b/packages/site/docs/manual/middle/elements/methods/lock-node.zh.md @@ -0,0 +1,215 @@ +--- +title: 锁定/解锁节点 +order: 11 +--- + +G6 3.1.4 版本中新增了 `lock()`、`unlock()` 和 `hasLocked()` 三个 API,方便用户锁定某个节点。默认情况下,当锁定某个节点后,拖动节点时锁定的节点不会有任何反应,但拖动画布和缩放画布时,仍然会对锁定的节点有影响,如果不想让锁定的节点收到拖动画布和缩放画布的影响,可以通过[自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior) 的方式来实现。 + +## 拖动画布时候不处理锁定的节点 + +G6 内置的 `drag-canvas` 不区分节点是否锁定,全部一视同仁。绝大数情况下,这种行为是完全没问题的,但某些业务可能会要求锁定的节点,拖动画布时也不能移动,对于这种情况,可以通过重新定义拖动画布的 Behavior 来实现。 + +```javascript +import G6 from '@antv/g6'; +const Util = G6.Util; +const abs = Math.abs; +const DRAG_OFFSET = 10; +const body = document.body; +const ALLOW_EVENTS = [16, 17, 18]; + +G6.registerBehavior('drag-canvas-exclude-lockedNode', { + getDefaultCfg() { + return { + direction: 'both', + }; + }, + getEvents() { + return { + 'canvas:mousedown': 'onMouseDown', + 'canvas:mousemove': 'onMouseMove', + 'canvas:mouseup': 'onMouseUp', + 'canvas:click': 'onMouseUp', + 'canvas:mouseleave': 'onOutOfRange', + keyup: 'onKeyUp', + keydown: 'onKeyDown', + }; + }, + updateViewport(e) { + const origin = this.origin; + const clientX = +e.clientX; + const clientY = +e.clientY; + if (isNaN(clientX) || isNaN(clientY)) { + return; + } + let dx = clientX - origin.x; + let dy = clientY - origin.y; + if (this.get('direction') === 'x') { + dy = 0; + } else if (this.get('direction') === 'y') { + dx = 0; + } + this.origin = { + x: clientX, + y: clientY, + }; + // 和内置 drag-canvas 不同的地方是在这里 + const lockedNodes = this.graph.findAll('node', (node) => !node.hasLocked()); + lockedNodes.forEach((node) => { + node.get('group').translate(dx, dy); + }); + this.graph.paint(); + }, + onMouseDown(e) { + if (this.keydown) { + return; + } + + this.origin = { x: e.clientX, y: e.clientY }; + this.dragging = false; + }, + onMouseMove(e) { + if (this.keydown) { + return; + } + + e = Util.cloneEvent(e); + const graph = this.graph; + if (!this.origin) { + return; + } + if (this.origin && !this.dragging) { + if (abs(this.origin.x - e.clientX) + abs(this.origin.y - e.clientY) < DRAG_OFFSET) { + return; + } + if (this.shouldBegin(e, this)) { + e.type = 'dragstart'; + graph.emit('canvas:dragstart', e); + this.dragging = true; + } + } + if (this.dragging) { + e.type = 'drag'; + graph.emit('canvas:drag', e); + } + if (this.shouldUpdate(e, this)) { + this.updateViewport(e); + } + }, + onMouseUp(e) { + if (this.keydown) { + return; + } + + if (!this.dragging) { + this.origin = null; + return; + } + e = Util.cloneEvent(e); + const graph = this.graph; + if (this.shouldEnd(e, this)) { + this.updateViewport(e); + } + e.type = 'dragend'; + graph.emit('canvas:dragend', e); + this.endDrag(); + }, + endDrag() { + if (this.dragging) { + this.origin = null; + this.dragging = false; + // 终止时需要判断此时是否在监听画布外的 mouseup 事件,若有则解绑 + const fn = this.fn; + if (fn) { + body.removeEventListener('mouseup', fn, false); + this.fn = null; + } + } + }, + // 若在拖拽时,鼠标移出画布区域,此时放开鼠标无法终止 drag 行为。在画布外监听 mouseup 事件,放开则终止 + onOutOfRange(e) { + if (this.dragging) { + const self = this; + const canvasElement = self.graph.get('canvas').get('el'); + const fn = (ev) => { + if (ev.target !== canvasElement) { + self.onMouseUp(e); + } + }; + this.fn = fn; + body.addEventListener('mouseup', fn, false); + } + }, + onKeyDown(e) { + const code = e.keyCode || e.which; + if (!code) { + return; + } + if (ALLOW_EVENTS.indexOf(code) > -1) { + this.keydown = true; + } else { + this.keydown = false; + } + }, + onKeyUp() { + this.keydown = false; + }, +}); +``` + +## 缩放画布时不处理锁定的节点 + +默认情况下,G6 内置的 `zoom-canvas` 在缩放画布时候也会对锁定的节点缩放,如果缩放过程中不需要操作锁定的节点,则可以通过下面的方式来实现。 + +```javascript +const DELTA = 0.05; + +G6.registerBehavior('zoom-canvas-exclude-lockedNode', { + getDefaultCfg() { + return { + sensitivity: 2, + minZoom: 0.1, + maxZoom: 10, + }; + }, + getEvents() { + return { + wheel: 'onWheel', + }; + }, + onWheel(e) { + e.preventDefault(); + if (!this.shouldUpdate(e, this)) { + return; + } + const graph = this.graph; + const canvas = graph.get('canvas'); + const point = canvas.getPointByClient(e.clientX, e.clientY); + const sensitivity = this.get('sensitivity'); + let ratio = graph.getZoom(); + // 兼容 IE、Firefox 及 Chrome + if (e.wheelDelta < 0) { + ratio = 1 - DELTA * sensitivity; + } else { + ratio = 1 + DELTA * sensitivity; + } + const zoom = ratio * graph.getZoom(); + if (zoom > this.get('maxZoom') || zoom < this.get('minZoom')) { + return; + } + graph.zoom(ratio, { x: point.x, y: point.y }); + const lockedNodes = this.graph.findAll('node', (node) => !node.hasLocked()); + lockedNodes.forEach((node) => { + const matrix = Util.clone(node.get('group').getMatrix()); + const center = node.getModel(); + matrix = Util.transform(matrix, [ + ['t', -center.x, -center.y], + ['s', ratio, ratio], + ['t', center.x, center.y], + ]); + node.get('group').setMatrix(matrix); + }); + graph.paint(); + graph.emit('wheelzoom', e); + }, +}); +``` diff --git a/packages/site/docs/manual/middle/elements/methods/multi-line.en.md b/packages/site/docs/manual/middle/elements/methods/multi-line.en.md new file mode 100644 index 0000000000..086000bf1e --- /dev/null +++ b/packages/site/docs/manual/middle/elements/methods/multi-line.en.md @@ -0,0 +1,141 @@ +--- +title: Multiple Edges between Two Nodes +order: 8 +--- + +## Problem + +For such a data below, how to display multiple edges between two nodes by G6? + +```javascript +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 150, + label: 'node1', + }, + { + id: 'node2', + x: 300, + y: 150, + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + { + source: 'node2', + target: 'node1', + }, + ], +}; +``` + +The following code handles the graph easily, where we use quadratic bezier curve instead of line to draw the edges: + +```javascript +const graph = new G6.Graph({ + container: GRAPH_CONTAINER, + width: 500, + height: 500, + defaultNode: { + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + labelCfg: { + style: { + fontSize: 12, + }, + }, + }, + defaultEdge: { + type: 'quadratic', // assign the edges to be quadratic bezier curves + style: { + stroke: '#e2e2e2', + }, + }, +}); + +graph.data(data); +graph.render(); +``` + +The result: + +img + +But what if we want to show 3 or more edges? + +We use the data below for example: + +```javascript +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 150, + label: 'node1', + }, + { + id: 'node2', + x: 300, + y: 150, + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + { + source: 'node2', + target: 'node1', + }, + { + source: 'node2', + target: 'node1', + }, + ], +}; +``` + +We found that the code above can not handle this situation any more. The result: + +img + +## Solution + +### Solution 1 + +Refer to the [Demo](/en/examples/item/multiEdge#multiEdges) and use the util function `processParallelEdges`. + +### Solution 2 + +To solve this problem, we utlize the [Custom Edge](/en/docs/manual/middle/elements/edges/custom-edge) of G6. + +There are two tips should be taken into consideration before customize an edge: + +- **We need a flag to identify whether there are more than one edges with same direction between two nodes**; +- **We need a value to control the curvature of each edge to prevent overlapping**. + +Therefore, we add a property `edgeType` for each edge in its data to identify different types of edges. + +The complete the code for the demo is shown below: + + + +Now, the problem is solved. diff --git a/packages/site/docs/manual/middle/elements/methods/multi-line.zh.md b/packages/site/docs/manual/middle/elements/methods/multi-line.zh.md new file mode 100644 index 0000000000..b2d1a8968a --- /dev/null +++ b/packages/site/docs/manual/middle/elements/methods/multi-line.zh.md @@ -0,0 +1,143 @@ +--- +title: 两节点间存在多条边 +order: 8 +--- + +## 问题 + +有如下的一份数据,如何使用 G6 让两个节点之间显示多条边? + +```javascript +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 150, + label: 'node1', + }, + { + id: 'node2', + x: 300, + y: 150, + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + { + source: 'node2', + target: 'node1', + }, + ], +}; +``` + +由于默认的边形状是直线,使用直线绘制同样两个端点之间的多条边时,会出现重叠的情况。因此我们使用以下代码将边绘制为二阶贝塞尔曲线 `'quadratic'`。 + +```javascript +const graph = new G6.Graph({ + container: GRAPH_CONTAINER, + width: 500, + height: 500, + defaultNode: { + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + labelCfg: { + style: { + fontSize: 12, + }, + }, + }, + defaultEdge: { + type: 'quadratic', // 指定边的形状为二阶贝塞尔曲线 + style: { + stroke: '#e2e2e2', + }, + }, +}); + +graph.data(data); +graph.render(); +``` + +结果如下: + +img + +如果两个节点之间需要显示 3 条、4 条甚至更多条边,该怎么做呢? + +我们把数据改成下面这样试试: + +```javascript +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 150, + label: 'node1', + }, + { + id: 'node2', + x: 300, + y: 150, + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + { + source: 'node2', + target: 'node1', + }, + { + source: 'node2', + target: 'node1', + }, + ], +}; +``` + +结果发现并不对。 + +img + +## 解决方案 + +### 方案 1 + +参考 [Demo](/zh/examples/item/multiEdge#multiEdges),使用 util 方法 `processParallelEdges`。 + +### 方案 2 + +这个时候,需要借助 G6 的 [自定义边](/zh/docs/manual/middle/elements/edges/custom-edge) 功能。 + +有了这个黑科技,什么样的需求,那还不是分分钟的事。 + +在使用「自定义边」前,需要明确两件事: + +- **需要有个标识来区分两个节点之间是否超过一条同方向边**; +- **需要有一个值控制边的弯曲度,以防边重叠**。 + +因此,我们在每条边的数据中添加一个 `edgeType` 属性以区分不同的边。有了这个约定以后,就可以开始动手撸码了。 + +完善的自定义边的代码如下。 + + + +至此,我们实现了让两个节点之间展示多条边的功能。 diff --git a/packages/site/docs/manual/middle/elements/methods/updateElement.en.md b/packages/site/docs/manual/middle/elements/methods/updateElement.en.md new file mode 100644 index 0000000000..fde656cc6f --- /dev/null +++ b/packages/site/docs/manual/middle/elements/methods/updateElement.en.md @@ -0,0 +1,66 @@ +--- +title: Update Item Style +order: 1 +--- + +There are three ways to modify the styles for items in G6. + +#### Configure When Instantiating Graph + +When instantiating a Graph, assign `style` in `defaultNode` or `defaultEdge` to configure the styles for global nodes and global edges respectively. + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 800, + defaultNode: { + type: 'circle', + style: { + fill: '#fff', + fontSize: 14, + }, + }, + defaultEdge: { + type: 'line-with-arrow', + style: { + fill: '#fff', + fontSize: 14, + }, + }, +}); +``` + +#### Configure style in Data + +By this way, you can configure the different nodes and edges in different styles. + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + style: { + fill: '#fff', + fontSize: 12, + }, + }, + ], +}; +``` + +#### update / updateItem + +This is a way for updating the [keyShape](/en/docs/manual/middle/elements/shape/shape-keyshape) of a node or an edge. + +```javascript +graph.updateItem(node, { + // The node style + style: { + stroke: 'blue', + }, +}); +``` + +For more information about the styles, refer to [Node Style Properties](/en/docs/manual/middle/elements/nodes/defaultNode/#style). diff --git a/packages/site/docs/manual/middle/elements/methods/updateElement.zh.md b/packages/site/docs/manual/middle/elements/methods/updateElement.zh.md new file mode 100644 index 0000000000..886de269b7 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/methods/updateElement.zh.md @@ -0,0 +1,66 @@ +--- +title: 更新节点或边的样式 +order: 1 +--- + +G6 提供了三种修改节点样式的方法。 + +#### 实例化 Graph + +实例化 Graph 时,可以通过在 `defaultNode` 或 `defaultEdge` 中指定  `style` 分别配置全局节点和全局边的样式属性。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 1000, + height: 800, + defaultNode: { + type: 'circle', + style: { + fill: '#fff', + fontSize: 14, + }, + }, + defaultEdge: { + type: 'line-with-arrow', + style: { + fill: '#fff', + fontSize: 14, + }, + }, +}); +``` + +#### 数据中指定 style + +这种方式可以在数据中为不同的节点和边指定不同的样式。 + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + style: { + fill: '#fff', + fontSize: 12, + }, + }, + ], +}; +``` + +#### 使用 update / updateItem + +使用 `update` / `updateItem` 更新节点或边。此方法用于动态更新节点或边的 [keyShape](/zh/docs/manual/middle/elements/shape/shape-keyshape)。 + +```javascript +graph.updateItem(node, { + // 节点的样式 + style: { + stroke: 'blue', + }, +}); +``` + +想要知道节点都支持哪些属性样式,请参考 [节点样式属性](/zh/docs/manual/middle/elements/nodes/defaultNode/#样式属性-style)。 diff --git a/packages/site/docs/manual/middle/elements/nodes/anchorpoint.en.md b/packages/site/docs/manual/middle/elements/nodes/anchorpoint.en.md new file mode 100644 index 0000000000..c3b3f0d596 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/anchorpoint.en.md @@ -0,0 +1,65 @@ +--- +title: AnchorPoint +order: 3 +--- + +The anchorPoint of a node is the link point where the related edges link to. In other words, it is the intersection of a node and its related edges. anchorPoints is a 2d array, each element represents the position of one anchor point. The positions of the anchor points in a [Shape](/en/docs/manual/middle/elements/shape/shape-keyshape) are shown below, the range of each x and y is [0, 1]:
img + +You can select the link points for an edge by `sourceAnchor` and `targetAnchor` if there are anchorPoints in the source and target node. Where This `sourceAnchor` and `targetAnchor` indicate the index of the array of anchorPoints. mechanism beautifies the graphs when there are multiple edges between two nodes. + +The data below shows how to configure the anchorPoints on a node and link points for an edge: + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + x: 100, + y: 200, + // There are two anchorPoints for this node + anchorPoints: [ + [0, 1], + [0.5, 1], + ], + type: 'rect', + }, + { + id: 'node2', + label: 'node2', + x: 300, + y: 400, + // There are two anchorPoints for this node + anchorPoints: [ + [0.5, 0], + [1, 0.5], + ], + type: 'rect', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + // The source link point of the edge is the 0-th anchorPoint of the source node + sourceAnchor: 0, + // The target link point of the edge is the 0-th anchorPoint of the target node + targetAnchor: 0, + style: { + endArrow: true, + }, + }, + { + source: 'node2', + target: 'node1', + // The source link point of the edge is the 1-st anchorPoint of the source node + sourceAnchor: 1, + // The target link point of the edge is the 1-st anchorPoint of the target node + targetAnchor: 1, + style: { + endArrow: true, + }, + }, + ], +}; +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/anchorpoint.zh.md b/packages/site/docs/manual/middle/elements/nodes/anchorpoint.zh.md new file mode 100644 index 0000000000..730d3a4866 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/anchorpoint.zh.md @@ -0,0 +1,67 @@ +--- +title: 节点的连接点 anchorPoint +order: 3 +--- + +节点的连接点 anchorPoint 指的是边连入节点的相对位置,即节点与其相关边的交点位置。anchorPoints 是一个二维数组,每一项表示一个连接点的位置,在一个[图形 Shape](/zh/docs/manual/middle/elements/shape/shape-keyshape) 中,连接点的位置如下图所示,x 和 y 方向上范围都是 [0, 1]:
img + +节点中有了 anchorPoints 之后,相关边可以分别选择连入起始点、结束点的哪一个 anchorPoint。当需要在节点之间连多条线时,这种机制能够使边的连入更美观。 + +边可以通过指定 `sourceAnchor` 和 `targetAnchor`  分别选择起始点、结束点的 anchorPoint。`sourceAnchor` 和 `targetAnchor` 取的值是相对应节点上 anchorPoints 数组的索引值。 + +下面数据演示了如何在节点上配置连接点、在边上指定连接点: + +```javascript +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + x: 100, + y: 200, + // 该节点可选的连接点集合,该点有两个可选的连接点 + anchorPoints: [ + [0, 1], + [0.5, 1], + ], + type: 'rect', + }, + { + id: 'node2', + label: 'node2', + x: 300, + y: 400, + // 该节点可选的连接点集合,该点有两个可选的连接点 + anchorPoints: [ + [0.5, 0], + [1, 0.5], + ], + type: 'rect', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + // 该边连入 source 点的第 0 个 anchorPoint, + sourceAnchor: 0, + // 该边连入 target 点的第 0 个 anchorPoint, + targetAnchor: 0, + style: { + endArrow: true, + }, + }, + { + source: 'node2', + target: 'node1', + // 该边连入 source 点的第 1 个 anchorPoint, + sourceAnchor: 1, + // 该边连入 source 点的第 1 个 anchorPoint, + targetAnchor: 1, + style: { + endArrow: true, + }, + }, + ], +}; +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/circle.en.md b/packages/site/docs/manual/middle/elements/nodes/built-in/circle.en.md new file mode 100644 index 0000000000..bbc7569ec3 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/circle.en.md @@ -0,0 +1,194 @@ +--- +title: Circle +order: 1 +--- + +A built-in node Circle has the default style as below, the label is drawed on the center of it.
img + +## Usage + +As stated in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) , there are three methods to configure nodes: Configure nodes globally when instantiating a Graph; Configure nodes in their data; Configure nodes by `graph.node(nodeFn)`. Their priorities are: + +`graph.node(nodeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, and `label` which should be assigned to every single node data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/nodes/defaultNode#common-property) and in each node type (refer to doc of each node type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'circle'` in the `defaultNode` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'circle', // The type of the node + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different nodes with different properties, you can write the properties into the node data. + +```javascript +const data = { + nodes: [ + { + id: 'node0', + type: 'circle', // The tyep of the node + //... // Other configurations + }, + //... // Other nodes + ], + edges: [ + //... // edges + ], +}; +``` + +## Property + +The [Node Common Properties](/en/docs/manual/middle/elements/nodes/defaultNode/#common-property) are available for Circle node, some special properties are shown below. The property with Object type will be described after the table:
+ +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| size | The diameter of the node | Number / Array | When it is an array, the first element will take effect | +| style | The default style of circle node | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | +| linkPoints | The link points **in visual** | Object | They are invisible by default. It is usually used with the [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint). The differences are described in [linkPoints](#linkpoints) | +| icon | The configurations of the icon on the circle node | Object | It is invisible by default | + +### style + +The [Node Common Styles](/en/docs/manual/middle/elements/nodes/defaultNode/#style) are available for Circle node. `style` is an object to configure the filling color, stroke, and other styles. The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'circle', + label: 'circle', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'circle', // The type has been assigned in the data, we do not have to define it any more + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object to configure the label of the node. The [Node Common Label Configurations](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) are available. Base on the code in [style](#style) section, we add `labelCfg` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other properties for nodes + labelCfg: { + position: 'bottom', + offset: 10, + style: { + // ... The style of the label + }, + }, + }, +}); +// ... +``` + +### linkPoints + +`linkPoints` is an object to configure the small circles on the 「top, bottom, left, and right」. + +⚠️Attention: It is different from `anchorPoints`: `anchorPoints` is an 「**array**」 that indicates the actual relative positions used to specify the join position of the relevant edge of the node (refer to [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint)); `linkPoints` is an object that indicates whether 「**render**」the four small circles, which do not connect the relevant edges. These two properties are often used together. + +| Name | Description | Type | Remark | +| --------- | --------------------------------------- | ------- | ---------------------- | +| top | Whether to show the top small circle | Boolean | `false` by default | +| bottom | Whether to show the bototm small circle | Boolean | `false` by default | +| left | Whether to show the left small circle | Boolean | `false` by default | +| right | Whether to show the right small circle | Boolean | `false` by default | +| size | The size of the small circles | Number | `3` by default | +| fill | The filling color of the small circles | String | `'#72CC4A'` by default | +| stroke | The stroke color of the small circles | String | `'#72CC4A'` by default | +| lineWidth | The line width of the small circles | Number | `1` by default | + +Base on the code in [style](#style) section, we add `linkPoints` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other configurations for nodes + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + fill: '#fff', + size: 5, + }, + }, +}); +// ... +``` + +### icon + +`icon` is an object to configure the icon on the node. + +| Name | Description | Type | Remark | +| ------ | ------------------------- | ------- | ------------------ | +| show | Whether to show the icon | Boolean | `false` by default | +| width | The width of the icon | Number | `16` by default | +| height | The height of the icon | Number | `16` by default | +| img | The image url or base64 of the icon | String | Configuring it means the icon is an image | +| text | iconfont for the icon | String | Configuring it means the icon is an iconfont | + +Base on the code in [style](#style) section, we add `icon` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for the graph + defaultNode: { + // ... Other configurations for nodes + icon: { + show: true, + width: 25, + height: 25, + // img: '...', The image url of the icon + // text: '...', Use an iconfont for the icon + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/circle.zh.md b/packages/site/docs/manual/middle/elements/nodes/built-in/circle.zh.md new file mode 100644 index 0000000000..c6dae56c95 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/circle.zh.md @@ -0,0 +1,194 @@ +--- +title: Circle +order: 1 +--- + +G6 内置了圆  Circle 节点,其默认样式如下。标签文本位于圆形中央。
img + +## 使用方法 + +如 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 一节所示,配置节点的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.node(nodeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.node(nodeFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`label` 应当配置到每个节点数据中外,其余的 [节点的通用属性](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性) 以及各个节点类型的特有属性(见内置节点类型)均支持这三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultNode` 指定 `type` 为 `'circle'`,即可使用 `circle` 节点。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'circle', // 节点类型 + // ... 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + { + id: 'node0', + type: 'circle', // 节点类型 + ... // 其他配置 + }, + ... // 其他节点 + ], + edges: [ + ... // 边 + ] +} +``` + +## 配置项说明 + +circle 节点支持 [节点通用配置](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性),下表对部分属性进行解释。对于 Object 类型的配置项将在后面有详细讲解:
+ +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| size | 圆的直径 | Number / Array | `size` 为数组时,取第一个值 | +| style | circle 默认样式 | Object | Canvas 支持的属性 | +| label | 标签文本内容 | String | | +| labelCfg | 标签文本配置项 | Object | | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | +| linkPoints | **视觉上的**四个锚点 | Object | 默认不显示,应与 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint) 配合使用。二者区别请看 [linkPoints](#linkpoints) | +| icon | 圆上 icon 配置 | Object | 默认不显示 icon | + +### 样式属性 style + +Object 类型。支持 [节点通用样式](/zh/docs/manual/middle/elements/nodes/defaultNode#样式属性-style)。通过 `style` 配置来修改节点的填充色、描边等属性。下面代码演示在实例化图时全局配置方法中配置 `style`,使之达到如下图效果。
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'circle', + label: 'circle', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'circle', // 在数据中已经指定 type,这里无需再次指定 + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。通过 `labelCfg` 配置标签文本。支持 [节点通用标签配置](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + labelCfg: { + position: 'bottom', + offset: 10, + style: { + // ... 文本样式的配置 + }, + }, + }, +}); +// ... +``` + +### linkPoints + +Object 类型。可以指定节点周围「上、下、左、右」四个方向上的四个小圆点。 + +⚠️ 注意: 区分于 `anchorPoints`: `anchorPoints` 是真正用于指定该节点相关边的连入位置的「**数组**」,见 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint);而 `linkPoints` 仅是指定是否「**绘制**」出四个圆点,不起实际的连接相关边的作用。二者常常配合使用。 + +| 名称 | 含义 | 类型 | 备注 | +| --------- | ------------------ | ------- | ------------------ | +| top | 是否显示上部的圆点 | Boolean | 默认为 `false` | +| bottom | 是否显示底部的圆点 | Boolean | 默认为 `false` | +| left | 是否显示左侧的圆点 | Boolean | 默认为 `false` | +| right | 是否显示右侧的圆点 | Boolean | 默认为 `false` | +| size | 圆点的大小 | Number | 默认为 `3` | +| fill | 圆点的填充色 | String | 默认为 `'#72CC4A'` | +| stroke | 圆点的描边颜色 | String | 默认为 `'#72CC4A'` | +| lineWidth | 圆点描边的宽度 | Number | 默认为 `1` | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `linkPoints`  配置项进行连入点的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 其他属性 + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + fill: '#fff', + size: 5, + }, + }, +}); +// ... +``` + +### 图标  icon + +Object 类型。通过配置 `icon`,可以在节点上显示小图标。 + +| 名称 | 含义 | 类型 | 备注 | +| ------ | ------------- | ------- | ---------------------- | +| show | 是否显示 icon | Boolean | 默认为 `false`,不显示 | +| width | icon 的宽度 | Number | 默认为 `16` | +| height | icon 的高度 | Number | 默认为 `16` | +| img | icon 的地址或 base64 | String | 若配置则表示使用图片作为 icon | +| text | icon 的 iconfont | String | 若配置则表示使用 iconfont 作为 icon | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了 `icon`  配置项进行图标的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 其他属性 + icon: { + show: true, + // img: '...', 可更换为其他图片地址 + // text: '...', 使用 iconfont + width: 25, + height: 25, + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/diamond.en.md b/packages/site/docs/manual/middle/elements/nodes/built-in/diamond.en.md new file mode 100644 index 0000000000..678b9d06b9 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/diamond.en.md @@ -0,0 +1,197 @@ +--- +title: Diamond +order: 4 +--- + +## Diamond + +A built-in node Diamond has the default style as below, the label is drawed on the center of it.
img + +## Usage + +As stated in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) , there are three methods to configure nodes: Configure nodes globally when instantiating a Graph; Configure nodes in their data; Configure nodes by `graph.node(nodeFn)`. Their priorities are: + +`graph.node(nodeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, and `label` which should be assigned to every single node data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/nodes/defaultNode#common-property) and in each node type (refer to doc of each node type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'diamond'` in the `defaultNode` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'diamond', // The type of the node + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different nodes with different properties, you can write the properties into the node data. + +```javascript +const data = { + nodes: [ + { + id: 'node0', + type: 'diamond', // The tyep of the node + //... // Other configurations + }, + ... // Other nodes + ], + edges: [ + ... // edges + ] +} +``` + +## Property + +The [Node Common Properties](/en/docs/manual/middle/elements/nodes/defaultNode/#common-property) are available for Diamond node, some special properties are shown below. The property with Object type will be described after the table: + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| size | The width and the height of the diamond | Number / Array | When it is a number, the width and the height are the same | +| style | The default style of diamond node | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | +| linkPoints | The link points **in visual** | Object | They are invisible by default. It is usually used with the [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint). The differences are described in [linkPoints](#linkpoints) | +| icon | The configurations of the icon on the diamond node | Object | It is invisible by default | + +### style + +The [Node Common Styles](/en/docs/manual/middle/elements/nodes/defaultNode/#style) are available for Circle node.`style` is an object to configure the filling color, stroke, and other styles. The following code shows how to configure the `style` globally when instantiating a Graph.

img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'diamond', + label: 'diamond', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type'diamond', // The type has been assigned in the data, we do not have to define it any more + size: [200, 80], + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object to configure the label of the node. The [Node Common Label Configurations](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) are available. Base on the code in [style](#style) section, we add `labelCfg` to `defaultNode`.。
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other properties for node + labelCfg: { + style: { + fill: '#9254de', + fontSize: 18, + }, + position: 'bottom', + }, + }, +}); +// ... +``` + +### linkPoints + +`linkPoints` is an object to configure the small circles on the 「top, bottom, left, and right」. + +⚠️Attention: It is different from `anchorPoints`: `anchorPoints` is an 「**array**」 that indicates the actual relative positions used to specify the join position of the relevant edge of the node (refer to [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint)); `linkPoints` is an object that indicates whether 「**render**」the four small circles, which do not connect the relevant edges. These two properties are often used together. + +| Name | Description | Type | Remark | +| --------- | --------------------------------------- | ------- | ---------------------- | +| top | Whether to show the top small circle | Boolean | `false` by default | +| bottom | Whether to show the bototm small circle | Boolean | `false` by default | +| left | Whether to show the left small circle | Boolean | `false` by default | +| right | Whether to show the right small circle | Boolean | `false` by default | +| size | The size of the small circles | Number | `3` by default | +| fill | The filling color of the small circles | String | `'#72CC4A'` by default | +| stroke | The stroke color of the small circles | String | `'#72CC4A'` by default | +| lineWidth | The line width of the small circles | Number | `1` by default | + +Base on the code in [style](#style) section, we add `linkPoints` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other configurations for nodes + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + size: 5, + fill: '#fff', + }, + }, +}); +// ... +``` + +### icon + +`icon` is an object to configure the icon on the node. + +| Name | Description | Type | Remark | +| ------ | ------------------------- | ------- | ------------------ | +| show | Whether to show the icon | Boolean | `false` by default | +| width | The width of the icon | Number | `16` by default | +| height | The height of the icon | Number | `16` by default | +| img | The image url or base64 of the icon | String | Configuring it means the icon is an image | +| text | iconfont for the icon | String | Configuring it means the icon is an iconfont | | + +Base on the code in [style](#style) section, we add `icon` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for the graph + defaultNode: { + // ... Other configurations for nodes + icon: { + show: true, + width: 25, + height: 25, + // img: '...', The image url of the icon + // text: '...', Use an iconfont for the icon + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/diamond.zh.md b/packages/site/docs/manual/middle/elements/nodes/built-in/diamond.zh.md new file mode 100644 index 0000000000..04549cfa4c --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/diamond.zh.md @@ -0,0 +1,196 @@ +--- +title: Diamond +order: 4 +--- + +## Diamond + +G6 内置了菱形  Diamond  节点,其默认样式如下。标签文本位于菱形中央。
img + +## 使用方法 + +如 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 一节所示,配置节点的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.node(nodeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.node(nodeFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`label` 应当配置到每个节点数据中外,其余的 [节点的通用属性](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性) 以及各个节点类型的特有属性(见内置节点类型)均支持这三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultNode` 指定 `type` 为 `'diamond'`,即可使用 `diamond` 节点。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'diamond', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [{ + id: 'node0', + type: 'diamond', + ... // 其他配置 + }, + ... // 其他节点 + ], + edges: [ + ... // 边 + ] +} +``` + +## 配置项说明 + +Diamond 节点支持 [节点通用配置](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性),下表对部分属性进行解释。对于 Object 类型的配置项将在后面有详细讲解: + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| size | 菱形的宽高 | Number / Array | size 为一个数值时,宽高相同 | +| style | diamond 默认样式 | Object | Canvas 支持的属性 | +| label | 标签文本内容 | String | | +| labelCfg | 标签文本配置项 | Object | | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | +| linkPoints | **视觉上的**四个锚点 | Object | 默认不显示,应与 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint) 配合使用。二者区别请看 [linkPoints](#linkpoints) | +| icon | 菱形上 icon 配置 | Object | 默认不显示 icon | + +### 样式属性 style + +Object 类型。支持 [节点通用样式](/zh/docs/manual/middle/elements/nodes/defaultNode#样式属性-style)。通过 `style` 配置来修改节点的填充色、描边等属性。下面代码演示在实例化图时全局配置方法中配置 `style`,使之达到如下图效果。
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'diamond', + label: 'diamond', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'diamond', // 数据中已指定 type,这里无需再次指定 + size: [200, 80], + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。通过 `labelCfg` 配置标签文本。支持 [节点通用标签配置](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + labelCfg: { + style: { + fill: '#9254de', + fontSize: 18, + }, + position: 'bottom', + }, + }, +}); +// ... +``` + +### linkPoints + +Object 类型。通过配置 `linkPoints` ,可以指定菱形周围「上、下、左、右」四个小圆点。 + +⚠️ 注意: 区分于 `anchorPoints`: `anchorPoints` 是真正用于指定该节点相关边的连入位置的「**数组**」,见 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint);而 `linkPoints` 仅是指定是否「**绘制**」出四个圆点,不起实际的连接相关边的作用。二者常常配合使用。 + +| 名称 | 含义 | 类型 | 备注 | +| --------- | ------------------ | ------- | ------------------ | +| top | 是否显示上部的圆点 | Boolean | 默认为 `false` | +| bottom | 是否显示底部的圆点 | Boolean | 默认为 `false` | +| left | 是否显示左侧的圆点 | Boolean | 默认为 `false` | +| right | 是否显示右侧的圆点 | Boolean | 默认为 `false` | +| size | 圆点的大小 | Number | 默认为 `3` | +| fill | 圆点的填充色 | String | 默认为 `'#72CC4A'` | +| stroke | 圆点的边框颜色 | String | 默认为 `'#72CC4A'` | +| lineWidth | 圆点边框的宽度 | Number | 默认为 `1` | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `linkPoints`  配置项进行连入点的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + size: 5, + fill: '#fff', + }, + }, +}); +// ... +``` + +### 图标  icon + +Object 类型。通过配置 `icon`,可以在圆上显示小图标。 + +| 名称 | 含义 | 类型 | 备注 | +| ------ | ------------- | ------- | ---------------------- | +| show | 是否显示 icon | Boolean | 默认为 `false`,不显示 | +| width | icon 的宽度 | Number | 默认为 `16` | +| height | icon 的高度 | Number | 默认为 `16` | +| img | icon 的地址或 base64 | String | 若配置则表示使用图片作为 icon | +| text | icon 的 iconfont | String | 若配置则表示使用 iconfont 作为 icon | + +下面代码演示在实例化图时全局配置方法中配置 `icon`。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + icon: { + show: true, + width: 25, + height: 25, + // img: '...', 可更换为其他图片地址 + // text: '...', 使用 iconfont + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/donut.en.md b/packages/site/docs/manual/middle/elements/nodes/built-in/donut.en.md new file mode 100644 index 0000000000..231eca5f2e --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/donut.en.md @@ -0,0 +1,234 @@ +--- +title: Donut +order: 9 +--- + +A built-in node Donut has the default style as below, the label is drawed on the center of it.
img + +## Usage + +As stated in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode), there are three methods to configure nodes: Configure nodes globally when instantiating a Graph; Configure nodes in their data; Configure nodes by `graph.node(nodeFn)`. Their priorities are: + +`graph.node(nodeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, and `label` which should be assigned to every single node data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/nodes/defaultNode#common-property) and in each node type (refer to doc of each node type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'donut'` in the `defaultNode` object when instantiating a Graph. In the same time, you should assign object `donutAttrs` in `defaultNode` to config the attributes and values for donut chart. There should be two and more valid attributes in `donutAttrs`, which means each attribute should have a string key and number value. The donut chart will not show up otherwise. The donut chart is paint with default color palette, if you want to custom the colors, assign `donutColorMap` which has same key as `donutAttrs`: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'donut', // The type of the + donutAttrs: { // The attributes show on the donut chart. Each attribute has format [key: string]: number + prop1: 10, + prop2: 20, + prop3: 25, + prop5: 10, + prop6: 20, + }, + // donutColorMap: { // The color palette for the donut chart. The keys are the same as donutAttrs. If donutColorMap is not assigned, a default color palette will be applied + // prop1: '#8eaade', + // prop2: '#55a9f2', + // prop3: '#0d47b5' + // } + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different nodes with different properties, you can write the properties into the node data. + +```javascript +const data = { + nodes: [ + { + id: 'node0', + type: 'donut', // The tyep of the node + donutAttrs: { // The attributes show on the donut chart. Each attribute has format [key: string]: number + prop1: 10, + prop2: 20, + prop3: 25, + prop5: 10, + prop6: 20, + }, + // donutColorMap: { // The color palette for the donut chart. The keys are the same as donutAttrs. If donutColorMap is not assigned, a default color palette will be applied + // prop1: '#8eaade', + // prop2: '#55a9f2', + // prop3: '#0d47b5' + // },node + //... // Other configurations + }, + //... // Other nodes + ], + edges: [ + //... // edges + ], +}; +``` + +## Property + +The [Node Common Properties](/en/docs/manual/middle/elements/nodes/defaultNode/#common-property) are available for Donut node, some special properties are shown below. The property with Object type will be described after the table:
+ +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| size | The diameter of the node | Number / Array | When it is an array, the first element will take effect | +| style | The default style of background circle | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| donutAttrs | The attributes for the donut chart | { [key: string]: number } | `donutAttrs` is a special property for donut node. There should be two and more valid attributes, the donut chart won't show up otherwise | +| donutColorMap | The color palette for the donut chart | { [key: string]: string } | `donutColorMap` is a special property for donut node. It has the same kes as `donutAttrs`. A default color palette will be applied if it is not assigned | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | +| linkPoints | The link points **in visual** | Object | They are invisible by default. It is usually used with the [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint). The differences are described in [linkPoints](#linkpoints) | +| icon | The configurations of the icon on the donut node | Object | It is invisible by default | + +### style + +The [Node Common Styles](/en/docs/manual/middle/elements/nodes/defaultNode/#style) are available for Donut node. `style` is an object to configure the filling color, stroke, and other styles. The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'donut', + label: 'donut', + donutAttrs: { + prop1: 10, + prop2: 20, + prop3: 25, + prop5: 10, + prop6: 20, + }, + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'donut', // The type has been assigned in the data, we do not have to define it any more + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + donutColorMap: { // the color palette + prop1: '#8eaade', + prop2: '#55a9f2', + prop3: '#0d47b5', + prop5: '#7b8085', + prop6: '#003870' + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object to configure the label of the node. The [Node Common Label Configurations](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) are available. Base on the code in [style](#style) section, we add `labelCfg` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other properties for nodes + labelCfg: { + position: 'bottom', + offset: 10, + style: { + // ... The style of the label + }, + }, + }, +}); +// ... +``` + +### linkPoints + +`linkPoints` is an object to configure the small circles on the 「top, bottom, left, and right」. + +⚠️Attention: It is different from `anchorPoints`: `anchorPoints` is an 「**array**」 that indicates the actual relative positions used to specify the join position of the relevant edge of the node (refer to [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint)); `linkPoints` is an object that indicates whether 「**render**」the four small circles, which do not connect the relevant edges. These two properties are often used together. + +| Name | Description | Type | Remark | +| --------- | --------------------------------------- | ------- | ---------------------- | +| top | Whether to show the top small circle | Boolean | `false` by default | +| bottom | Whether to show the bototm small circle | Boolean | `false` by default | +| left | Whether to show the left small circle | Boolean | `false` by default | +| right | Whether to show the right small circle | Boolean | `false` by default | +| size | The size of the small circles | Number | `3` by default | +| fill | The filling color of the small circles | String | `'#72CC4A'` by default | +| stroke | The stroke color of the small circles | String | `'#72CC4A'` by default | +| lineWidth | The line width of the small circles | Number | `1` by default | + +Base on the code in [style](#style) section, we add `linkPoints` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other configurations for nodes + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + fill: '#fff', + size: 5, + }, + }, +}); +// ... +``` + +### icon + +`icon` is an object to configure the icon on the node. + +| Name | Description | Type | Remark | +| ------ | ------------------------- | ------- | ------------------ | +| show | Whether to show the icon | Boolean | `false` by default | +| width | The width of the icon | Number | `16` by default | +| height | The height of the icon | Number | `16` by default | +| img | The image url or base64 of the icon | String | Configuring it means the icon is an image | +| text | iconfont for the icon | String | Configuring it means the icon is an iconfont | | + +Base on the code in [style](#style) section, we add `icon` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for the graph + defaultNode: { + // ... Other configurations for nodes + icon: { + show: true, + width: 25, + height: 25, + // img: '...', The image url of the icon + // text: '...', Use an iconfont for the icon + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/donut.zh.md b/packages/site/docs/manual/middle/elements/nodes/built-in/donut.zh.md new file mode 100644 index 0000000000..6559990db5 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/donut.zh.md @@ -0,0 +1,235 @@ +--- +title: Donut +order: 9 +--- + +G6 内置了甜甜圈  Donut 节点,其默认样式如下。标签文本位于圆形中央,圆形周围根据给定字段的占比绘制甜甜圈统计图。
img + +## 使用方法 + +如 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 一节所示,配置节点的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.node(nodeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.node(nodeFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`label` 应当配置到每个节点数据中外,其余的 [节点的通用属性](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性) 以及各个节点类型的特有属性(见内置节点类型)均支持这三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultNode` 指定 `type` 为 `'donut'`,并为 donut 节点配置 `donutAttrs` 指定参与统计的字段(需要包含两个及以上合法字段,合法是指每个字段格式为 `[key: string]: number`),即可使用 `donut` 节点。若 `donutAttrs` 未指定,或 `donutAttrs` 中的合法字段数少于 2,则该节点将被显示为 circle 节点。甜甜圈图的颜色将会使用默认色板,若需自定义,则传入 `donutColorMap` 字段。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'donut', // 节点类型 + donutAttrs: { // 甜甜圈字段,每个字段必须为 [key: string]: number + prop1: 10, + prop2: 20, + prop3: 25, + prop5: 10, + prop6: 20, + }, + // donutColorMap: { // 甜甜圈颜色映射,字段名与 donutAttrs 中的字段名对应。不指定则使用默认色板 + // prop1: '#8eaade', + // prop2: '#55a9f2', + // prop3: '#0d47b5' + // }, + // ... 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [ + { + id: 'node0', + type: 'donut', // 节点类型 + donutAttrs: { // 甜甜圈字段,每个字段必须为 [key: string]: number + prop1: 10, + prop2: 20, + prop3: 25, + prop5: 10, + prop6: 20, + }, + // donutColorMap: { // 甜甜圈颜色映射,字段名与 donutAttrs 中的字段名对应 + // prop1: '#8eaade', + // prop2: '#55a9f2', + // prop3: '#0d47b5' + // }, + ... // 其他配置 + }, + ... // 其他节点 + ], + edges: [ + ... // 边 + ] +} +``` + +## 配置项说明 + +donut 节点支持 [节点通用配置](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性),下表对部分属性进行解释。对于 Object 类型的配置项将在后面有详细讲解:
+ +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| size | 圆的直径 | Number / Array | `size` 为数组时,取第一个值 | +| style | 背景圆的默认样式,与 circle 节点类似 | Object | Canvas 支持的属性 | +| label | 标签文本内容 | String | | +| labelCfg | 标签文本配置项 | Object | | +| donutAttrs | 甜甜圈统计字段 | { [key: string]: number } | donut 节点特有。必须含有两个及以上的合法字段,才能够显示出甜甜圈图 | +| donutColorMap | 甜甜圈色板 | { [key: string]: string } | donut 节点特有。不指定将使用默认色板。key 与 donutAttrs 中的字段名对应 | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | +| linkPoints | **视觉上的**四个锚点 | Object | 默认不显示,应与 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint) 配合使用。二者区别请看 [linkPoints](#linkpoints) | +| icon | 圆上 icon 配置 | Object | 默认不显示 icon | + +### 样式属性 style + +Object 类型。支持 [节点通用样式](/zh/docs/manual/middle/elements/nodes/defaultNode#样式属性-style)。通过 `style` 配置来修改节点的填充色、描边等属性。下面代码演示在实例化图时全局配置方法中配置 `style`,使之达到如下图效果。
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'donut', + label: 'donut', + donutAttrs: { + prop1: 10, + prop2: 20, + prop3: 25, + prop5: 10, + prop6: 20, + }, + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'donut', // 在数据中已经指定 type,这里无需再次指定 + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + donutColorMap: { // 指定色板 + prop1: '#8eaade', + prop2: '#55a9f2', + prop3: '#0d47b5', + prop5: '#7b8085', + prop6: '#003870' + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。通过 `labelCfg` 配置标签文本。支持 [节点通用标签配置](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + labelCfg: { + position: 'bottom', + offset: 10, + style: { + // ... 文本样式的配置 + }, + }, + }, +}); +// ... +``` + +### linkPoints + +Object 类型。可以指定节点周围「上、下、左、右」四个方向上的四个小圆点。 + +⚠️ 注意: 区分于 `anchorPoints`: `anchorPoints` 是真正用于指定该节点相关边的连入位置的「**数组**」,见 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint);而 `linkPoints` 仅是指定是否「**绘制**」出四个圆点,不起实际的连接相关边的作用。二者常常配合使用。 + +| 名称 | 含义 | 类型 | 备注 | +| --------- | ------------------ | ------- | ------------------ | +| top | 是否显示上部的圆点 | Boolean | 默认为 `false` | +| bottom | 是否显示底部的圆点 | Boolean | 默认为 `false` | +| left | 是否显示左侧的圆点 | Boolean | 默认为 `false` | +| right | 是否显示右侧的圆点 | Boolean | 默认为 `false` | +| size | 圆点的大小 | Number | 默认为 `3` | +| fill | 圆点的填充色 | String | 默认为 `'#72CC4A'` | +| stroke | 圆点的描边颜色 | String | 默认为 `'#72CC4A'` | +| lineWidth | 圆点描边的宽度 | Number | 默认为 `1` | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `linkPoints`  配置项进行连入点的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 其他属性 + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + fill: '#fff', + size: 5, + }, + }, +}); +// ... +``` + +### 图标  icon + +Object 类型。通过配置 `icon`,可以在节点上显示小图标。 + +| 名称 | 含义 | 类型 | 备注 | +| ------ | ------------- | ------- | ---------------------- | +| show | 是否显示 icon | Boolean | 默认为 `false`,不显示 | +| width | icon 的宽度 | Number | 默认为 `16` | +| height | icon 的高度 | Number | 默认为 `16` | +| img | icon 的地址或 base64 | String | 若配置则表示使用图片作为 icon | +| text | icon 的 iconfont | String | 若配置则表示使用 iconfont 作为 icon | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了 `icon`  配置项进行图标的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 其他属性 + icon: { + show: true, + //img: '...', 可更换为其他图片地址 + width: 25, + height: 25, + // img: '...', 可更换为其他图片地址 + // text: '...', 使用 iconfont + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/ellipse.en.md b/packages/site/docs/manual/middle/elements/nodes/built-in/ellipse.en.md new file mode 100644 index 0000000000..3e6720aed0 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/ellipse.en.md @@ -0,0 +1,198 @@ +--- +title: Ellipse +order: 3 +--- + +A built-in node Ellipse has the default style as below, the label is drawed on the center of it. + +img + +## Usage + +As stated in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) , there are three methods to configure nodes: Configure nodes globally when instantiating a Graph; Configure nodes in their data; Configure nodes by `graph.node(nodeFn)`. Their priorities are: + +`graph.node(nodeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, and `label` which should be assigned to every single node data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/nodes/defaultNode#common-property) and in each node type (refer to doc of each node type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'ellipse'` in the `defaultNode` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'ellipse', // The type of the node + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different nodes with different properties, you can write the properties into the node data. + +```javascript +const data = { + nodes: [ + { + id: 'node0', + type: 'ellipse', // The tyep of the node + //... // Other configurations + }, + //... // Other nodes + ], + edges: [ + //... // edges + ], +}; +``` + +## Property + +The [Node Common Properties](/en/docs/manual/middle/elements/nodes/defaultNode/#common-property) are available for Ellipse node, some special properties are shown below. The property with Object type will be described after the table:
+ +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| size | The size of the ellipse | Number / Array | When it is a number, the ellipse looks like a circle. When it is an array, the `size[0]` is the major diameter, the `size[1]` is the minor diameter | +| style | The default style of ellipse node | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | +| linkPoints | The link points **in visual** | Object | They are invisible by default. It is usually used with the [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint). The differences are described in [linkPoints](#linkpoints) | +| icon | The configurations of the icon on the ellipse node | Object | It is invisible by default | + +### style + +The [Node Common Styles](/en/docs/manual/middle/elements/nodes/defaultNode/#style) are available for Circle node.`style` is an object to configure the filling color, stroke, and other styles. The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'ellipse', + label: 'ellipse', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'ellipse', // The type has been assigned in the data, we do not have to define it any more + size: [130, 80], + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object to configure the label of the node. The [Node Common Label Configurations](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) are available. Base on the code in [style](#style) section, we add `labelCfg` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other properties for node + labelCfg: { + offset: 20, + style: { + fill: '#9254de', + fontSize: 18, + // ... The style of the label + }, + }, + }, +}); +// ... +``` + +### linkPoints + +`linkPoints` is an object to configure the small circles on the 「top, bottom, left, and right」. + +⚠️Attention: It is different from `anchorPoints`: `anchorPoints` is an 「**array**」 that indicates the actual relative positions used to specify the join position of the relevant edge of the node (refer to [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint)); `linkPoints` is an object that indicates whether 「**render**」the four small circles, which do not connect the relevant edges. These two properties are often used together. + +| Name | Description | Type | Remark | +| --------- | --------------------------------------- | ------- | ---------------------- | +| top | Whether to show the top small circle | Boolean | `false` by default | +| bottom | Whether to show the bototm small circle | Boolean | `false` by default | +| left | Whether to show the left small circle | Boolean | `false` by default | +| right | Whether to show the right small circle | Boolean | `false` by default | +| size | The size of the small circles | Number | `3` by default | +| fill | The filling color of the small circles | String | `'#72CC4A'` by default | +| stroke | The stroke color of the small circles | String | `'#72CC4A'` by default | +| lineWidth | The line width of the small circles | Number | `1` by default | + +Base on the code in [style](#style) section, we add `linkPoints` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other configurations for nodes + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + size: 5, + fill: '#fff', + }, + }, +}); +// ... +``` + +### icon + +`icon` is an object to configure the icon on the node. + +| Name | Description | Type | Remark | +| ------ | ------------------------- | ------- | ------------------ | +| show | Whether to show the icon | Boolean | `false` by default | +| width | The width of the icon | Number | `16` by default | +| height | The height of the icon | Number | `16` by default | +| img | The image url or base64 of the icon | String | Configuring it means the icon is an image | +| text | iconfont for the icon | String | Configuring it means the icon is an iconfont | + +Base on the code in [style](#style) section, we add `icon` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for the graph + defaultNode: { + // ... Other configurations for nodes + icon: { + show: true, + width: 30, + height: 30, + // img: '...', The image url of the icon + // text: '...', Use an iconfont for the icon + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/ellipse.zh.md b/packages/site/docs/manual/middle/elements/nodes/built-in/ellipse.zh.md new file mode 100644 index 0000000000..e0b5fe6f7c --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/ellipse.zh.md @@ -0,0 +1,196 @@ +--- +title: Ellipse +order: 3 +--- + +G6 内置了  ellipse 节点,其默认样式如下。标签文本位于椭圆中央。 + +img + +## 使用方法 + +如 [内置节点](/en/docs/manual/middle/elements/nodes/defaultNode) 一节所示,配置节点的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.node(nodeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.node(nodeFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`label` 应当配置到每个节点数据中外,其余的 [节点的通用属性](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性) 以及各个节点类型的特有属性(见内置节点类型)均支持这三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultNode` 指定 `type` 为 `'ellipse'`,即可使用 `ellipse` 节点。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'ellipse', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [{ + id: 'node0', + type: 'ellipse', + ... // 其他配置 + }, + ... // 其他节点 + ], + edges: [ + ... // 边 + ] +} +``` + +## 配置项说明 + +ellipse  节点支持 [节点通用配置](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性),下表对部分属性进行解释。对于 Object 类型的配置项将在后面有详细讲解: + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| size | 椭圆的大小 | Number / Array | size 为 Number 时,效果为一个圆形。为 Array 时,size[0] 为椭圆长轴长度,size[1] 为椭圆短轴长度 | +| style | 椭圆的默认样式 | Object | Canvas 支持的属性 | +| label | 标签文本内容 | String | | +| labelCfg | 标签文本配置项 | Object | | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | +| linkPoints | **视觉上的**四个锚点 | Object | 默认不显示,应与 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint) 配合使用。二者区别请看 [linkPoints](#linkpoints) | +| icon | 椭圆上 icon 配置 | Object | 默认不显示 icon | + +### 样式属性 style + +Object 类型。支持 [节点通用样式](/zh/docs/manual/middle/elements/nodes/defaultNode#样式属性-style)。通过 `style` 配置来修改节点的填充色、描边等属性。下面代码演示在实例化图时全局配置方法中配置 `style`,使之达到如下图效果。
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'ellipse', + label: 'ellipse', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'ellipse', // 在数据中已经指定 type,这里可以不用再此指定 + size: [130, 80], + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置  labelCfg + +Object 类型。通过 `labelCfg` 配置标签文本。支持 [节点通用标签配置](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + labelCfg: { + offset: 20, + style: { + fill: '#9254de', + fontSize: 18, + }, + }, + }, +}); +// ... +``` + +### linkPoints + +Object 类型。通过配置 `linkPoints` ,可以指定节点上「上、下、左、右」四个小圆点。 + +⚠️ 注意: 区分于 `anchorPoints`: `anchorPoints` 是真正用于指定该节点相关边的连入位置的「**数组**」,见 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint);而 `linkPoints` 仅是指定是否「**绘制**」出四个圆点,不起实际的连接相关边的作用。二者常常配合使用。 + +| 名称 | 含义 | 类型 | 备注 | +| --------- | ------------------ | ------- | ------------------ | +| top | 是否显示上部的圆点 | Boolean | 默认为 `false` | +| bottom | 是否显示底部的圆点 | Boolean | 默认为 `false` | +| left | 是否显示左侧的圆点 | Boolean | 默认为 `false` | +| right | 是否显示右侧的圆点 | Boolean | 默认为 `false` | +| size | 圆点的大小 | Number | 默认为 `3` | +| fill | 圆点的填充色 | String | 默认为 `'#72CC4A'` | +| stroke | 圆点的边框颜色 | String | 默认为 `'#72CC4A'` | +| lineWidth | 圆点边框的宽度 | Number | 默认为 `1` | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `linkPoints`  配置项进行连入点的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + size: 5, + fill: '#fff', + }, + }, +}); +// ... +``` + +### 图标  icon + +Object 类型。通过配置 `icon`,可以在圆上显示小图标。 + +| 名称 | 含义 | 类型 | 备注 | +| ------ | ------------- | ------- | ---------------------- | +| show | 是否显示 icon | Boolean | 默认为 `false`,不显示 | +| width | icon 的宽度 | Number | 默认为 `16` | +| height | icon 的高度 | Number | 默认为 `16` | +| img | icon 的地址或 base64 | String | 若配置则表示使用图片作为 icon | +| text | icon 的 iconfont | String | 若配置则表示使用 iconfont 作为 icon | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了 `icon`  配置项进行图标的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + icon: { + show: true, + width: 25, + height: 25, + // img: '...', 可更换为其他图片地址 + // text: '...', 使用 iconfont + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/image.en.md b/packages/site/docs/manual/middle/elements/nodes/built-in/image.en.md new file mode 100644 index 0000000000..a35fed2c46 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/image.en.md @@ -0,0 +1,194 @@ +--- +title: Image +order: 7 +--- + +## Image + +A built-in node Circle has the default style as below, the label is drawed on the bottom of it.
img + +## Usage + +As stated in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) , there are three methods to configure nodes: Configure nodes globally when instantiating a Graph; Configure nodes in their data; Configure nodes by `graph.node(nodeFn)`. Their priorities are: + +`graph.node(nodeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, and `label` which should be assigned to every single node data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/nodes/defaultNode#common-property) and in each node type (refer to doc of each node type) support to be assigned by the three ways. + +### 1 实例化图时全局配置 + +Assign `type` to `'image'` in the `defaultNode` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'image', + label: 'AntV Team', + // Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different nodes with different properties, you can write the properties into the node data. + +```javascript +const data = { + nodes: [{ + id: 'node0', + img: 'https://yyb.gtimg.com/aiplat/page/product/visionimgidy/img/demo6-16a47e5d31.jpg?max_age=31536000', + type: 'image', + size: 200, + label: 'AntV Team', + labelCfg: { + position: 'bottom' + }, + // The configurations for clipping the image + clipCfg: { + show: false, + type: 'circle', + r: 15 + } + }, + ... // Other nodes + ], + edges: [ + ... // edges + ] +} +``` + +## Property + +The [Node Common Properties](/en/docs/manual/middle/elements/nodes/defaultNode/#common-property) are available for Image node, some special properties are shown below. The property with Object type will be described after the table: + +```javascript +img: 'https://yyb.gtimg.com/aiplat/page/product/visionimgidy/img/demo6-16a47e5d31.jpg?max_age=31536000', +size: 200, + labelCfg: { + position: 'bottom' + }, + // The configurations for clipping the image + clipCfg: { + show: false, + type: 'circle', + // circle + r: 15, + // ellipse + rx: 10, + ry: 15, + // rect + width: 15, + height: 15, + // Coordinates + x: 0, + y: 0 + } +``` + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | --- | +| **img** | **The URL addgress** | **String** | **special property for image node** | +| size | The size of the node | Number | Array | When it is a number, the width and the height are the same | +| label | The text of the label | String | | +| labelCfg | The configurations for the label | Object | The [Node Common Label Configurations](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) are available. | +| **clipCfg** | **The configurations for clipping** | **Object** | **Do not clip by default. It is a special property for image node** | + +### clipCfg + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| type | The type of shape of clipping | String | Options: `'circle'`, `'rect'`, `'ellipse'` | +| x | The x coordinate of the clipping shape | Number | 0 by default. Only takes effect when the `type` is `'circle'`, `'rect'`, or `'ellipse'` | +| y | The y coordinate of the clipping shape | Number | 0 by default. Only takes effect when the `type` is `'circle'`, `'rect'`, or `'ellipse' | +| show | Whether to clip the image | Boolean | Do not clip by default. | +| r | The radius of circle clipping | Number | Takes effect when the `type` is `'circle'` | +| width | The width of the clipping | Number | Takes effect when the `type` is `'rect'` | +| height | The height of the clipping | Number | Takes effect when the `type` is `'rect'` | +| rx | The major radius of the ellipse clipping | Number | Takes effect when the `type` is `'ellipse'` | +| ry | The minor radius of the ellipse clipping | Number | Takes effect when the `type` is `'ellipse'` | + +There are default values for all the types of clipping. The following code shows how to configure the `clipCfg` when instantiating a Graph: + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'image', + label: 'image', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'image', // The type has been assigned in the data, we do not have to define it any more + clipCfg: { + show: true, + type: 'circle', + }, + }, +}); +graph.data(data); +graph.render(); +``` + +#### Clippling Type + +##### Cicle Clipping + +`circle` When the `type` in `clipCfg` is `'circle'`: + +```javascript +clipCfg: { + show: true, + type: 'circle', + r: 100 +} +``` + +img + +##### Rect Clipping + +`rect` + +When the `type` in `clipCfg` is `'rect'`: + +```javascript +clipCfg: { + show: true, + type: 'rect', + x: -50, + y: -50, + width: 100, + height: 100 +} +``` + +img + +##### Ellipse Clipping + +`ellipse` + +When the `type` in `clipCfg` is `'ellipse'`: + +```javascript +clipCfg: { + show: true, + type: 'ellipse', + rx: 100, + ry: 60 +} +``` + +img diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/image.zh.md b/packages/site/docs/manual/middle/elements/nodes/built-in/image.zh.md new file mode 100644 index 0000000000..ca5fe1048b --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/image.zh.md @@ -0,0 +1,196 @@ +--- +title: Image +order: 7 +--- + +## Image + +G6 内置了 image 节点,其默认样式如下。标签文本位于图片下方。
img + +## 使用方法 + +如 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 一节所示,配置节点的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.node(nodeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.node(nodeFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`label` 应当配置到每个节点数据中外,其余的 [节点的通用属性](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性) 以及各个节点类型的特有属性(见内置节点类型)均支持这三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultNode` 指定 `type` 为 `'image'`,即可使用 `image` 节点。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'image', + label: 'AntV Team', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [{ + id: 'node0', + img: 'https://yyb.gtimg.com/aiplat/page/product/visionimgidy/img/demo6-16a47e5d31.jpg?max_age=31536000', + type: 'image', + size: 200, + label: 'AntV Team', + labelCfg: { + position: 'bottom' + }, + // 裁剪图片配置 + clipCfg: { + show: false, + type: 'circle', + r: 15 + } + }, + ... // 其他节点 + ], + edges: [ + ... // 边 + ] +} +``` + +## 配置项说明 + +image 节点支持 [节点通用配置](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性),下表对部分属性进行解释: + +```javascript +img: 'https://yyb.gtimg.com/aiplat/page/product/visionimgidy/img/demo6-16a47e5d31.jpg?max_age=31536000', +size: 200, + labelCfg: { + position: 'bottom' + }, + // 裁剪图片配置 + clipCfg: { + show: false, + type: 'circle', + // circle + r: 15, + // ellipse + rx: 10, + ry: 15, + // rect + width: 15, + height: 15, + // 坐标 + x: 0, + y: 0 + } +``` + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | --- | +| **img** | **图片 URL 地址** | **String** | **image 节点特有** | +| size | 图片大小 | Number | Array | `size` 为单个值时,表示宽高相等 | +| label | 标签文本内容 | String | | +| labelCfg | 标签文本配置项 | Object | 支持 [节点通用标签配置](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg)。 | +| **clipCfg** | **裁剪图片的配置项** | **Object** | **默认不裁剪,image 节点特有** | + +### 剪裁 + +`clipCfg` + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| type | 裁剪的图片形状 | String | 支持 `'circle'`、`'rect'`、`'ellipse'` | +| x | 裁剪图形的 x 坐标 | Number | 默认为 0,类型为 `'circle'`、`'rect'`、`'ellipse'` 时生效 | +| y | 裁剪图形的 y 坐标 | Number | 默认为 0,类型为 `'circle'`、`'rect'`、`'ellipse'` 时生效 | +| show | 是否启用裁剪功能 | Boolean | 默认不裁剪,值为 `false` | +| r | 剪裁圆形的半径 | Number | 剪裁 type 为  `'circle'` 时生效 | +| width | 剪裁矩形的宽度 | Number | 剪裁 type 为 `'rect'` 时生效 | +| height | 剪裁矩形的长度 | Number | 剪裁 type 为 `'rect'` 时生效 | +| rx | 剪裁椭圆的长轴半径 | Number | 剪裁 type 为 `'ellipse'` 时生效 | +| ry | 剪裁椭圆的短轴半径 | Number | 剪裁 type 为 `'ellipse'` 时生效 | + +所有的裁剪类型都提供了默认值。下面代码演示在实例化图时全局配置 `clipCfg` 的最简形式: + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'image', + label: 'image', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'image', // 在数据中已经指定 type,这里无需再次指定 + clipCfg: { + show: true, + type: 'circle', + }, + }, +}); +graph.data(data); +graph.render(); +``` + +#### 裁剪类型 + +##### 圆形剪裁 + +`circle` 当剪裁配置 `clipCfg` 中的裁剪类型 `type` 为 `'circle'` 时,如下配置可以得到下图效果: + +```javascript +clipCfg: { + show: true, + type: 'circle', + r: 100 +} +``` + +img + +##### 矩形剪裁 + +`rect` + +当剪裁配置 `clipCfg` 中的裁剪类型 `type` 为 `'rect'` 时,如下配置可以得到下图效果: + +```javascript +clipCfg: { + show: true, + type: 'rect', + x: -50, + y: -50, + width: 100, + height: 100 +} +``` + +img + +##### 椭圆剪裁 + +`ellipse` + +当剪裁配置 `clipCfg` 中的裁剪类型 `type` 为 `'ellipse'` 时,如下配置可以得到下图效果: + +```javascript +clipCfg: { + show: true, + type: 'ellipse', + rx: 100, + ry: 60 +} +``` + +img diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/modelRect.en.md b/packages/site/docs/manual/middle/elements/nodes/built-in/modelRect.en.md new file mode 100644 index 0000000000..d457fb66db --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/modelRect.en.md @@ -0,0 +1,295 @@ +--- +title: ModelRect +order: 8 +--- + +A built-in node modelRect has the default style as below, the label is drawed on the center of it.
+ +img + +img + +
**Tips:** There will be no description when there is no `description` in the data. + +## Usage + +As stated in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) , there are three methods to configure nodes: Configure nodes globally when instantiating a Graph; Configure nodes in their data; Configure nodes by `graph.node(nodeFn)`. Their priorities are: + +`graph.node(nodeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, and `label` which should be assigned to every single node data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/nodes/defaultNode#common-property) and in each node type (refer to doc of each node type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'modelRect'` in the `defaultNode` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'modelRect', + // Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different nodes with different properties, you can write the properties into the node data. + +```javascript +const data = { + nodes: [{ + id: 'node0', + type: 'modelRect', + ... // Other configurations + }, + ... // Other nodes + ], + edges: [ + ... // edges + ] +} +``` + +## Property + +The [Node Common Properties](/en/docs/manual/middle/elements/nodes/defaultNode/#common-property) are available for ModelRect node, some special properties are shown below. The property with Object type will be described after the table: + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | --- | +| size | The size of the modelRect node | Number | Array | | +| style | The default style of modelRect node | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | +| linkPoints | The link points **in visual** | Object | They are invisible by default. It is usually used with the [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint). The differences are described in [linkPoints](#linkpoints) | +| **preRect** | **Left rect of the node** | **Object** | **Special property for modelRect** | +| **logoIcon** | **The left logo icon** | **Object** | **Special property for modelRect** | +| **stateIcon** | **The right state icon** | **Object** | **Special property for modelRect** | +| **description** | **The description text below the label** | **String** | **Special property for modelRect** | +| **descriptionCfg**
_It is supported after V3.3_ | **The configuration for description text** | **Object** | **Special property for modelRect** | + +```javascript + // The configuration of the logo icon in the node + logoIcon: { + // Whether to show the icon. false means hide the icon + show: true, + x: 0, + y: 0, + // the image url of icon + img: 'https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg', + width: 16, + height: 16, + // Adjust the left/right offset of the icon + offset: 0 + }, + // The configuration of the state icon in the node + stateIcon: { + // Whether to show the icon. false means hide the icon + show: true, + x: 0, + y: 0, + // the image url of icon + img: 'https://gw.alipayobjects.com/zos/basement_prod/300a2523-67e0-4cbf-9d4a-67c077b40395.svg', + width: 16, + height: 16, + // Adjust the left/right offset of the icon + offset: -5 + } +``` + +### style + +The [Node Common Styles](/en/docs/manual/middle/elements/nodes/defaultNode/#style) are available for Circle node.`style` is an object to configure the filling color, stroke, and other styles. The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'modelRect', + label: 'modelRect', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'modelRect', // The type has been assigned in the data, we do not have to define it any more + size: [200, 80], + style: { + fill: '#f0f5ff', + stroke: '#adc6ff', + lineWidth: 2, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object to configure the label of the node. The [Node Common Label Configurations](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) are available. Base on the code in [style](#style) section, we add `labelCfg` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other properties for node + labelCfg: { + style: { + fill: '#9254de', + fontSize: 18, + }, + }, + }, +}); +// ... +``` + +### descriptionCfg + +⚠️**Attension:** _It will be supported after V3.3._ + +`descriptionCfg` is an object to configure the label of the node. The [Node Common Label Configurations](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) are available. Besides, descriptionCfg has special attribute: + +| Name | Description | Type | Remark | +| ---------- | -------------------------------------------------- | ------ | -------------- | +| paddingTop | The padding from the description to the label text | Number | `0` by default | + +Base on the code in [style](#style) section, we add `descriptionCfg` to `defaultNode` + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other properties for node + descriptionCfg: { + style: { + fill: '#f00', + }, + }, + }, +}); +// ... +``` + +### linkPoints + +`linkPoints` is an object to configure the small circles on the 「top, bottom, left, and right」. + +⚠️Attention: It is different from `anchorPoints`: `anchorPoints` is an 「**array**」 that indicates the actual relative positions used to specify the join position of the relevant edge of the node (refer to [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint)); `linkPoints` is an object that indicates whether 「**render**」the four small circles, which do not connect the relevant edges. These two properties are often used together. + +| Name | Description | Type | Remark | +| --------- | --------------------------------------- | ------- | ---------------------- | +| top | Whether to show the top small circle | Boolean | `false` by default | +| bottom | Whether to show the bototm small circle | Boolean | `false` by default | +| left | Whether to show the left small circle | Boolean | `false` by default | +| right | Whether to show the right small circle | Boolean | `false` by default | +| size | The size of the small circles | Number | `3` by default | +| fill | The filling color of the small circles | String | `'#72CC4A'` by default | +| stroke | The stroke color of the small circles | String | `'#72CC4A'` by default | +| lineWidth | The line width of the small circles | Number | `1` by default | + +Base on the code in [style](#style) section, we add `linkPoints` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other configurations for nodes + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + size: 5, + fill: '#fff', + }, + }, +}); +// ... +``` + +### preRect + +`preRect` configures the left rect of the rectModel node. + +| Name | Description | Type | Remark | +| ------ | ---------------------------------- | ------- | ---------------------- | +| show | Whether to show the left rect | Boolean | `true` by default | +| width | The width of the left rect | Number | `4` by default | +| fill | The filling color of the left rect | String | `'#40a9ff'` by default | +| radius | The border radius of the left rect | Number | `2` by default | + +Base on the code in [style](#style) section, we add `icon` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for the graph + defaultNode: { + // ... Other configurations for nodes + preRect: { + // false means hiding it + show: true, + fill: '#f759ab', + width: 8, + }, + }, +}); +// ... +``` + +### logoIcon / stateIcon + +`logoIcon` and `stateIcon` configure the left and right logo of the modelRect node. The configurations of them are the same. + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| show | Whether to show the icon | Boolean | `true` by default | +| img | The url of the icon image | String |
- The default image for `logoIcon` is img
- The default image for `stateIcon` is img
| +| width | The width of the icon | Number | `16` by default | +| height | The height of the icon | Number | `16` by default | +| offset | Adjust the left/right offset of the icon | Number |
- The dfualt `offset` of the left `logoIcon` is `0`
- The dfualt `offset` of the right `stateIcon` is `-5`
| + +Base on the code in [style](#style) section, we add `logoIcon` and `stateIcon` to `defaultNode` to hide the left icon and change the image for right icon.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for the graph + defaultNode: { + // ... Other configurations for nodes + logoIcon: { + show: false, + }, + stateIcon: { + show: true, + img: + 'https://gw.alipayobjects.com/zos/basement_prod/c781088a-c635-452a-940c-0173663456d4.svg', + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/modelRect.zh.md b/packages/site/docs/manual/middle/elements/nodes/built-in/modelRect.zh.md new file mode 100644 index 0000000000..cf25eaee7e --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/modelRect.zh.md @@ -0,0 +1,295 @@ +--- +title: ModelRect +order: 8 +--- + +G6 内置了方形卡片 modelRect  节点,其默认样式如下。标签文本位于卡片下方。
+ +img + +img + +
**提示:**数据中无 `description` 字段时,则不显示描述信息。 + +## 使用方法 + +如 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 一节所示,配置节点的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.node(nodeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.node(nodeFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`label` 应当配置到每个节点数据中外,其余的 [节点的通用属性](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性) 以及各个节点类型的特有属性(见内置节点类型)均支持这三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultNode` 指定 `type` 为 `'modelRect'`,即可使用 `modelRect` 节点。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'modelRect', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [{ + id: 'node0', + type: 'modelRect', + ... // 其他配置 + }, + ... // 其他节点 + ], + edges: [ + ... // 边 + ] +} +``` + +## 配置项说明 + +modelRect 节点支持 [节点通用配置](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性),下表对部分属性进行解释。对于 Object 类型的配置项将在后面有详细讲解: + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | --- | +| size | 卡片的大小 | Number | Array | | +| style | 卡片的默认样式 | Object | Canvas 支持的属性 | +| label | 标签文本内容 | String | | +| labelCfg | 文本配置项 | Object | | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | +| linkPoints | **视觉上的**四个锚点 | Object | 默认不显示,应与 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint) 配合使用。二者区别请看 [linkPoints](#linkpoints) | +| **preRect** | **左侧的小矩形** | **Object** | **modelRect 节点特有** | +| **logoIcon** | **左侧的 logo 图标** | **Object** | **modelRect 节点特有** | +| **stateIcon** | **右侧的状态图标** | **Object** | **modelRect 节点特有** | +| **description** | **节点主要文本下方的描述文本** | **String** | **modelRect 节点特有** | +| **descriptionCfg**
_在 V3.3 版本后支持_ | **描述文本的配置项** | **Object** | **modelRect 节点特有** | + +```javascript + // 节点中 icon 配置 + logoIcon: { + // 是否显示 icon,值为 false 则不渲染 icon + show: true, + x: 0, + y: 0, + // icon 的地址,字符串类型 + img: 'https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg', + width: 16, + height: 16, + // 用于调整图标的左右位置 + offset: 0 + }, + // 节点中表示状态的 icon 配置 + stateIcon: { + // 是否显示 icon,值为 false 则不渲染 icon + show: true, + x: 0, + y: 0, + // icon 的地址,字符串类型 + img: 'https://gw.alipayobjects.com/zos/basement_prod/300a2523-67e0-4cbf-9d4a-67c077b40395.svg', + width: 16, + height: 16, + // 用于调整图标的左右位置 + offset: -5 + } +``` + +### 样式属性  style + +Object 类型。支持 [节点通用样式](/zh/docs/manual/middle/elements/nodes/defaultNode#样式属性-style)。通过 `style` 配置来修改节点的填充色、描边等属性。下面代码演示在实例化图时全局配置方法中配置 `style`,使之达到如下图效果。
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'modelRect', + label: 'modelRect', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'modelRect', // 在数据中已经指定 type,这里无需再次指定 + size: [200, 80], + style: { + fill: '#f0f5ff', + stroke: '#adc6ff', + lineWidth: 2, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。通过 `labelCfg` 配置标签文本。支持 [节点通用标签配置](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + labelCfg: { + style: { + fill: '#9254de', + fontSize: 18, + }, + }, + }, +}); +// ... +``` + +### 描述文本配置  descriptionCfg + +⚠️ 注意: 在 V3.3 版本后支持。 + +Object 类型。通过 `descriptionCfg` 配置标签文本。支持 [节点通用标签配置](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg)。此外,还有一个特殊属性: + +| 名称 | 含义 | 类型 | 备注 | +| ---------- | -------------------------- | ------ | ---------- | +| paddingTop | 距离上方标签文本的垂直距离 | Number | 默认为 `0` | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `descriptionCfg`  配置项进行描述文本的配置。 + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + descriptionCfg: { + style: { + fill: '#f00', + }, + }, + }, +}); +// ... +``` + +### linkPoints + +Object 类型。通过配置 `linkPoints` ,可以指定 modelRect 周围「上、下、左、右」四个小圆点。 + +⚠️ 注意: 区分于 `anchorPoints`: `anchorPoints` 是真正用于指定该节点相关边的连入位置的「**数组**」,见 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint);而 `linkPoints` 仅是指定是否「**绘制**」出四个圆点,不起实际的连接相关边的作用。二者常常配合使用。 + +| 名称 | 含义 | 类型 | 备注 | +| --------- | ------------------ | ------- | ------------------ | +| top | 是否显示上部的圆点 | Boolean | 默认为 `false` | +| bottom | 是否显示底部的圆点 | Boolean | 默认为 `false` | +| left | 是否显示左侧的圆点 | Boolean | 默认为 `false` | +| right | 是否显示右侧的圆点 | Boolean | 默认为 `false` | +| size | 圆点的大小 | Number | 默认为 `3` | +| fill | 圆点的填充色 | String | 默认为 `'#72CC4A'` | +| stroke | 圆点的边框颜色 | String | 默认为 `'#72CC4A'` | +| lineWidth | 圆点边框的宽度 | Number | 默认为 1 | + +基于上面 [样式属性 style](#819eF) 中的代码,下面代码在 `defaultNode` 中增加了  `linkPoints`  配置项进行连入点的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + size: 5, + fill: '#fff', + }, + }, +}); +// ... +``` + +### 左侧矩形 preRect + +通过 `preRect` 可以配置左侧的小矩形形状。 + +| 名称 | 含义 | 类型 | 备注 | +| ------ | -------------------- | ------- | ------------------- | +| show | 是否显示左侧小矩形 | Boolean | 默认为 `true` | +| width | 左侧小矩形的宽度 | Number | 默认为 4 | +| fill | 左侧小矩形的填充色 | String | 默认为  `'#40a9ff'` | +| radius | 左侧小矩形的圆角弧度 | Number | 默认为 2 | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `preRect`  配置项进行左侧小矩形的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + preRect: { + // 设置为 false,则不显示 + show: true, + fill: '#f759ab', + width: 8, + }, + }, +}); +// ... +``` + +### 图标 logoIcon / stateIcon + +通过 `logoIcon` 和 `stateIcon` 可以配置左侧的 logo 小图标和右边的状态小图标,这两个的配置项完全相同。 + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | +| show | 是否显示图标 | Boolean | 默认为 `true` | +| img | 图标图片 | String |
- 左侧图标 `logoIcon` 的图片默认为  img
- 右侧图标 `stateIcon` 的图片默认为 img
| +| width | 图标的宽度 | Number | 默认为 16 | +| height | 图标的高度 | Number | 默认为 16 | +| offset | 图标的左右偏移量 | Number |
- 左侧图标 `logoIcon` 的 `offset` 默认为 0
- 右侧图标 `stateIcon` 的 `offset`  默认为 -5
| + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了 `logoIcon` 和 `stateIcon` 配置项进行左右图标的配置,使之达到如下图效果,左侧图标不显示,右侧图标更换图片。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + logoIcon: { + show: false, + }, + stateIcon: { + show: true, + img: + 'https://gw.alipayobjects.com/zos/basement_prod/c781088a-c635-452a-940c-0173663456d4.svg', + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/rect.en.md b/packages/site/docs/manual/middle/elements/nodes/built-in/rect.en.md new file mode 100644 index 0000000000..4d3ca89443 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/rect.en.md @@ -0,0 +1,171 @@ +--- +title: Rect +order: 2 +--- + +A built-in node Rect has the default style as below, the label is drawed on the center of it.
img + +## Usage + +As stated in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) , there are three methods to configure nodes: Configure nodes globally when instantiating a Graph; Configure nodes in their data; Configure nodes by `graph.node(nodeFn)`. Their priorities are: + +`graph.node(nodeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, and `label` which should be assigned to every single node data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/nodes/defaultNode#common-property) and in each node type (refer to doc of each node type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'rect'` in the `defaultNode` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'rect', + // Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different nodes with different properties, you can write the properties into the node data. + +```javascript +const data = { + nodes: [ + { + id: 'node0', + type: 'rect', + //... // Other configurations + }, + //... // Other nodes + ], + edges: [ + //... // edges + ], +}; +``` + +## Property + +The [Node Common Properties](/en/docs/manual/middle/elements/nodes/defaultNode/#common-property) are available for Rect node, some special properties are shown below. The property with Object type will be described after the table: + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| size | The size of the ellipse | Number / Array | When it is a number, the width and height are the same. | +| style | The default style of rect node | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | +| linkPoints | The link points **in visual** | Object | They are invisible by default. It is usually used with the [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint). The differences are described in [linkPoints](#linkpoints) | + +### style + +The [Node Common Styles](/en/docs/manual/middle/elements/nodes/defaultNode/#style) are available for Circle node.`style` is an object to configure the filling color, stroke, and other styles. + +| Name | Description | Type | Remark | +| ----------- | ---------------------------- | ------ | ------------------------------------------ | +| radius | The border radius | Number | Rectangle with no border radius by default | +| stroke | The color of the stroke | String | | +| lineWidth | The line width of the stroke | Number | `1` by default | +| fill | The filling color | String | | +| fillOpacity | The opacity | Number | `1` by default | + +The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'rect', + label: 'rect', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 300, + defaultNode: { + // type: 'rect', // The type has been assigned in the data, we do not have to define it any more + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + radius: 10, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object to configure the label of the node. The [Node Common Label Configurations](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) are available. Base on the code in [style](#style) section, we add `labelCfg` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other properties for node + labelCfg: { + style: { + fill: '#9254de', + fontSize: 18, + }, + position: 'bottom', + }, + }, +}); +// ... +``` + +### linkPoints + +`linkPoints` is an object to configure the small circles on the 「top, bottom, left, and right」. + +⚠️Attention: It is different from `anchorPoints`: `anchorPoints` is an 「**array**」 that indicates the actual relative positions used to specify the join position of the relevant edge of the node (refer to [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint)); `linkPoints` is an object that indicates whether 「**render**」the four small circles, which do not connect the relevant edges. These two properties are often used together. + +| Name | Description | Type | Remark | +| --------- | --------------------------------------- | ------- | ---------------------- | +| top | Whether to show the top small circle | Boolean | `false` by default | +| bottom | Whether to show the bototm small circle | Boolean | `false` by default | +| left | Whether to show the left small circle | Boolean | `false` by default | +| right | Whether to show the right small circle | Boolean | `false` by default | +| size | The size of the small circles | Number | `3` by default | +| fill | The filling color of the small circles | String | `'#72CC4A'` by default | +| stroke | The stroke color of the small circles | String | `'#72CC4A'` by default | +| lineWidth | The line width of the small circles | Number | `1` by default | + +Base on the code in [style](#style) section, we add `linkPoints` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // Other configurations for nodes + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + size: 5, + fill: '#fff', + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/rect.zh.md b/packages/site/docs/manual/middle/elements/nodes/built-in/rect.zh.md new file mode 100644 index 0000000000..24cfcc63c2 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/rect.zh.md @@ -0,0 +1,170 @@ +--- +title: Rect +order: 2 +--- + +G6 内置了 rect 节点,其默认样式如下。标签文本位于矩形中央。
img + +## 使用方法 + +如 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 一节所示,配置节点的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.node(nodeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.node(nodeFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`label` 应当配置到每个节点数据中外,其余的 [节点的通用属性](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性) 以及各个节点类型的特有属性(见内置节点类型)均支持这三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultNode` 指定 `type` 为 `'rect'`,即可使用 `rect` 节点。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'rect', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [{ + id: 'node0', + type: 'rect', + ... // 其他配置 + }, + ... // 其他节点 + ], + edges: [ + ... // 边 + ] +}; +``` + +## 配置项说明 + +rect 节点支持 [节点通用配置](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性),下表对部分属性进行解释。对于 Object 类型的配置项将在后面有详细讲解: + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | --- | +| size | rect 的宽高 | Number | Array | `size` 为数组时:第一个值表示宽度,第二个表示高度;
`size` 为一个数值时:表示宽高相等 | +| style | rect 默认样式 | Object | Canvas 支持的属性 | +| label | 标签文本内容 | String | | +| labelCfg | 标签配置项 | Object | | +| stateStyles | 各状态下的样式 | Object | 只对 `keyShape` 起作用 | +| linkPoints | **视觉上的**四个锚点 | Object | 默认不显示,应与 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint) 配合使用。二者区别请看 [linkPoints](#linkpoints) | + +### 样式属性 style + +Object 类型。支持 [节点通用样式](/zh/docs/manual/middle/elements/nodes/defaultNode#样式属性-style)。通过 `style` 配置来修改 `rect` 的填充色、边框颜色、阴影等属性。 + +| 名称 | 含义 | 类型 | 备注 | +| ----------- | -------- | ------ | -------------- | +| radius | 圆角半径 | Number | 默认为直角矩形 | +| stroke | 描边颜色 | String | | +| lineWidth | 描边粗细 | Number | 默认为 `1` | +| fill | 填充色 | String | | +| fillOpacity | 透明度 | Number | 默认为 `1` | + +下面代码演示在实例化图时全局配置方法中配置 `style`,使之达到如下图效果。
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'rect', + label: 'rect', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 300, + defaultNode: { + // type: 'rect', // 在数据中已经指定了 type,这里无需再次指定 + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + radius: 10, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。通过 `labelCfg` 配置标签文本。支持 [节点通用标签配置](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他配置 + defaultNode: { + // ... 其他配置 + labelCfg: { + style: { + fill: '#9254de', + fontSize: 18, + }, + position: 'bottom', + }, + }, +}); +// ... +``` + +### linkPoints + +Object 类型。通过配置 `linkPoints` ,可以指定矩形周围「上、下、左、右」四个小圆点。 + +⚠️ 注意: 区分于 `anchorPoints`: `anchorPoints` 是真正用于指定该节点相关边的连入位置的「**数组**」,见 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint);而 `linkPoints` 仅是指定是否「**绘制**」出四个圆点,不起实际的连接相关边的作用。二者常常配合使用。 + +| 名称 | 含义 | 类型 | 备注 | +| --------- | ------------------ | ------- | ------------------ | +| top | 是否显示上部的圆点 | Boolean | 默认为 `false` | +| bottom | 是否显示底部的圆点 | Boolean | 默认为 `false` | +| left | 是否显示左侧的圆点 | Boolean | 默认为 `false` | +| right | 是否显示右侧的圆点 | Boolean | 默认为 `false` | +| size | 圆点的大小 | Number | 默认为 `3` | +| fill | 圆点的填充色 | String | 默认为 `'#72CC4A'` | +| stroke | 圆点的边框颜色 | String | 默认为 `'#72CC4A'` | +| lineWidth | 圆点边框的宽度 | Number | 默认为 `1` | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `linkPoints`  配置项进行连入点的配置。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他配置 + defaultNode: { + // 其他配置 + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + size: 5, + fill: '#fff', + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/star.en.md b/packages/site/docs/manual/middle/elements/nodes/built-in/star.en.md new file mode 100644 index 0000000000..2c9950e84a --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/star.en.md @@ -0,0 +1,197 @@ +--- +title: Star +order: 6 +--- + +A built-in node Star has the default style as below, the label is drawed on the center of it.。
img + +## Usage + +As stated in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) , there are three methods to configure nodes: Configure nodes globally when instantiating a Graph; Configure nodes in their data; Configure nodes by `graph.node(nodeFn)`. Their priorities are: + +`graph.node(nodeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, and `label` which should be assigned to every single node data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/nodes/defaultNode#common-property) and in each node type (refer to doc of each node type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'star'` in the `defaultNode` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'star', + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different nodes with different properties, you can write the properties into the node data. + +```javascript +const data = { + nodes: [ + { + id: 'node0', + type: 'star', + //... // Other configurations + }, + //... // Other nodes + ], + edges: [ + //... // edges + ], +}; +``` + +## Property + +The [Node Common Properties](/en/docs/manual/middle/elements/nodes/defaultNode/#common-property) are available for Star node, some special properties are shown below. The property with Object type will be described after the table: + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | --- | +| size | The size of the star | number | Array | `size` is the widht and the height of the minimum bounding box of the star | +| **innerR** | **The inner radius of the star** | **Number** | **Equals to `size` \* 3 / 8 by default** | +| style | The default style of star node | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | +| linkPoints | The link points **in visual** | Object | They are invisible by default. It is usually used with the [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint). The differences are described in [linkPoints](#linkpoints) | +| icon | The configurations of the icon on the star node | Object | It is invisible by default | + +### style + +The [Node Common Styles](/en/docs/manual/middle/elements/nodes/defaultNode/#style) are available for Circle node.`style` is an object to configure the filling color, stroke, and other styles. The following code shows how to configure the `style` globally when instantiating a Graph.
+ +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'star', + label: 'star', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'star', // The type has been assigned in the data, we do not have to define it any more + size: 80, + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object to configure the label of the node. The [Node Common Label Configurations](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) are available. Base on the code in [style](#style) section, we add `labelCfg` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other properties for node + labelCfg: { + style: { + fill: '#9254de', + fontSize: 18, + }, + }, + }, +}); +// ... +``` + +### linkPoints + +`linkPoints` is an object to configure the small circles on the 「top, left bottom, right bottom, left, and right」. + +⚠️Attention: It is different from `anchorPoints`: `anchorPoints` is an 「**array**」 that indicates the actual relative positions used to specify the join position of the relevant edge of the node (refer to [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint)); `linkPoints` is an object that indicates whether 「**render**」the four small circles, which do not connect the relevant edges. These two properties are often used together. + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| top | Whether to show the top small circle | Boolean | `false` by default | +| **leftBottom** | **Whether to show the left bottom small circle** | **Boolean** | **`false` by default. It is a special property for star node** | +| **rightBottom** | **Whether to show the right bottom small circle** | **Boolean** | **`false` by default. It is a special property for star node** | +| left | Whether to show the left small circle | Boolean | `false` by default | +| right | Whether to show the right small circle | Boolean | `false` by default | +| size | The size of the small circles | Number | `3` by default | +| fill | The filling color of the small circles | String | `'#72CC4A'` by default | +| stroke | The stroke color of the small circles | String | `'#72CC4A'` by default | +| lineWidth | The line width of the small circles | Number | `1` by default | + +Base on the code in [style](#style) section, we add `linkPoints` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other configurations for nodes + linkPoints: { + top: true, + left: true, + right: true, + leftBottom: true, + rightBottom: true, + size: 5, + fill: '#fff', + }, + }, +}); +// ... +``` + +### icon + +`icon` is an object to configure the icon on the node. + +| Name | Description | Type | Remark | +| ------ | ------------------------- | ------- | ------------------ | +| show | Whether to show the icon | Boolean | `false` by default | +| width | The width of the icon | Number | `16` by default | +| height | The height of the icon | Number | `16` by default | +| img | The image url or base64 of the icon | String | Configuring it means the icon is an image | +| text | iconfont for the icon | String | Configuring it means the icon is an iconfont | + +Base on the code in [style](#style) section, we add `icon` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for the graph + defaultNode: { + // ... Other configurations for nodes + icon: { + show: true, + width: 25, + height: 25, + // img: '...', The image url of the icon + // text: '...', Use an iconfont for the icon + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/star.zh.md b/packages/site/docs/manual/middle/elements/nodes/built-in/star.zh.md new file mode 100644 index 0000000000..e1ac3e13e6 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/star.zh.md @@ -0,0 +1,196 @@ +--- +title: Star +order: 6 +--- + +G6 内置了星形  star 节点,其默认样式如下。标签文本位于星形中央。
img + +## 使用方法 + +如 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 一节所示,配置节点的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.node(nodeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.node(nodeFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`label` 应当配置到每个节点数据中外,其余的 [节点的通用属性](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性) 以及各个节点类型的特有属性(见内置节点类型)均支持这三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultNode` 指定 `type` 为 `'star'`,即可使用 `star` 节点。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'star', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [{ + id: 'node0', + type: 'star', + ... // 其他配置 + }, + ... // 其他节点 + ], + edges: [ + ... // 边 + ] +} +``` + +## 配置项说明 + +star 节点支持 [节点通用配置](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性),下表对部分属性进行解释: + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | --- | +| size | 五角星的大小 | number | Array | size 表示外环的大小 | +| **innerR** | **五角星内环大小** | **Number** | **默认为 size \* 3 / 8** | +| style | 五角星的默认样式 | Object | Canvas 支持的属性 | +| label | 标签文本内容 | String | | +| labelCfg | 标签文本配置项 | Object | | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | +| linkPoints | **视觉上的**五个锚点 | Object | 默认不显示,应与 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint) 配合使用。二者区别请看 [linkPoints](#linkpoints) | +| icon | 五角星上 icon 配置 | Object | 默认不显示 icon | + +### 样式属性 style + +Object 类型。支持 [节点通用样式](/zh/docs/manual/middle/elements/nodes/defaultNode#样式属性-style)。通过 `style` 配置来修改节点的填充色、描边等属性。下面代码演示在实例化图时全局配置方法中配置 `style`,使之达到如下图效果。
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'star', + label: 'star', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'star', // 在数据中已经指定 type,这里无需再次指定 + size: 80, + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置  labelCfg + +Object 类型。通过 `labelCfg` 配置标签文本。支持 [节点通用标签配置](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + labelCfg: { + style: { + fill: '#9254de', + fontSize: 18, + }, + }, + }, +}); +// ... +``` + +### linkPoints + +Object 类型。通过配置 `linkPoints` ,可以指定圆周围「上、左下、➡ 右下、左、右」五个小圆点。 + +⚠️ 注意: 区分于 `anchorPoints`: `anchorPoints` 是真正用于指定该节点相关边的连入位置的「**数组**」,见 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint);而 `linkPoints` 仅是指定是否「**绘制**」出四个圆点,不起实际的连接相关边的作用。二者常常配合使用。 + +| 名称 | 含义 | 类型 | 备注 | +| --------------- | ------------------------ | ----------- | ----------------------------- | +| top | 是否显示上部的圆点 | Boolean | 默认为 `false` | +| **leftBottom** | **是否显示左底部的圆点** | **Boolean** | **默认为 `false`,star 特有** | +| **rightBottom** | **是否显示右底部的圆点** | **Boolean** | **默认为 `false`,star 特有** | +| left | 是否显示左侧的圆点 | Boolean | 默认为 `false` | +| right | 是否显示右侧的圆点 | Boolean | 默认为 `false` | +| size | 圆点的大小 | Number | 默认为 3 | +| fill | 圆点的填充色 | String | 默认为 `'#72CC4A'` | +| stroke | 圆点的边框颜色 | String | 默认为 `'#72CC4A'` | +| lineWidth | 圆点边框的宽度 | Number | 默认为 `1` | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `linkPoints`  配置项进行连入点的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + linkPoints: { + top: true, + left: true, + right: true, + leftBottom: true, + rightBottom: true, + size: 5, + fill: '#fff', + }, + }, +}); +// ... +``` + +### 图标  icon + +Object 类型。通过配置 `icon`,可以在圆上显示小图标。 + +| 名称 | 含义 | 类型 | 备注 | +| ------ | ------------- | ------- | -------------------- | +| show | 是否显示 icon | Boolean | 默认为 false,不显示 | +| width | icon 的宽度 | Number | 默认为 16 | +| height | icon 的高度 | Number | 默认为 16 | +| img | icon 的地址或 base64 | String | 若配置则表示使用图片作为 icon | +| text | icon 的 iconfont | String | 若配置则表示使用 iconfont 作为 icon | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了 `icon`  配置项进行图标的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + icon: { + show: true, + width: 25, + height: 25, + // img: '...', 可更换为其他图片地址 + // text: '...', 使用 iconfont + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/triangle.en.md b/packages/site/docs/manual/middle/elements/nodes/built-in/triangle.en.md new file mode 100644 index 0000000000..e86d79728f --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/triangle.en.md @@ -0,0 +1,223 @@ +--- +title: Triangle +order: 5 +--- + +## Triangle + +A built-in node Triangle has the default style as below, the label is drawed on the center of it.
img + +## Usage + +As stated in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) , there are three methods to configure nodes: Configure nodes globally when instantiating a Graph; Configure nodes in their data; Configure nodes by `graph.node(nodeFn)`. Their priorities are: + +`graph.node(nodeFn)` > Configure in data > Configure globally + +⚠️ Attention: Expect for `id`, and `label` which should be assigned to every single node data, the other configurations in [The Common Property](/en/docs/manual/middle/elements/nodes/defaultNode#common-property) and in each node type (refer to doc of each node type) support to be assigned by the three ways. + +### 1 Global Configure When Instantiating a Graph + +Assign `type` to `'triangle'` in the `defaultNode` object when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'triangle', // The type of the node + // ... Other configuraltions + }, +}); +``` + +### 2 Configure in the Data + +To configure different nodes with different properties, you can write the properties into the node data. + +```javascript +const data = { + nodes: [ + { + id: 'node0', + type: 'triangle', // The tyep of the node + //... // Other configurations + }, + //... // Other nodes + ], + edges: [ + //... // edges + ], +}; +``` + +## Property + +The [Node Common Properties](/en/docs/manual/middle/elements/nodes/defaultNode/#common-property) are available for Triangle node, some special properties are shown below. The spetial property type will be described after the table: + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | --- | +| size | The length of side of the equilateral triangle | Number | Array | When it is an array, the first value will take effect | +| **direction** | **The direction of the triangle** | **String** | **Options: `'up'`, `'down'`, `'left'`, `'right'`. `'up'` by default** | +| style | The default style of triangle node | Object | Correspond to the styles in Canvas | +| label | The text of the label | String | | +| labelCfg | The configurations of the label | Object | | +| stateStyles | The styles in different states | Object | Refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) | +| linkPoints | The link points **in visual** | Object | They are invisible by default. It is usually used with the [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint). The differences are described in [linkPoints](#linkpoints) | +| icon | The configurations of the icon on the triangle node | Object | It is invisible by default | + +### direction + +It is a string with options: ` '``up' `, `'down'`, `'left'`, and `'right'`. `'up'` by default. The following code shows how to configure the `direction` globally when instantiating a Graph. + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'triangle', + direction: 'down', + }, +}); +``` + +img +img +img +img + +> The results with `'up'`, `'down'`, `'left'`, `'right'` as `direction`. + +### style + +The [Node Common Styles](/en/docs/manual/middle/elements/nodes/defaultNode/#style) are available for Circle node.`style` is an object to configure the filling color, stroke, and other styles. The following code shows how to configure the `style` globally when instantiating a Graph.
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'triangle', + label: 'triangle', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'triangle', // The type has been assigned in the data, we do not have to define it any more + direction: 'up', + size: 100, + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### labelCfg + +`labelCfg` is an object to configure the label of the node. The [Node Common Label Configurations](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) are available. Base on the code in [style](#style) section, we add `labelCfg` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other properties for node + labelCfg: { + position: 'center', + style: { + fill: '#9254de', + fontSize: 18, + }, + }, + }, +}); +// ... +``` + +### linkPoints + +`linkPoints` is an object to configure the small circles on the 「top, left, and right」. + +⚠️Attention: It is different from `anchorPoints`: `anchorPoints` is an 「**array**」 that indicates the actual relative positions used to specify the join position of the relevant edge of the node (refer to [anchorPoints](/en/docs/manual/middle/elements/nodes/anchorpoint)); `linkPoints` is an object that indicates whether 「**render**」the four small circles, which do not connect the relevant edges. These two properties are often used together. + +| Name | Description | Type | Remark | +| --------- | -------------------------------------- | ------- | ---------------------- | +| top | Whether to show the top small circle | Boolean | `false` by default | +| left | Whether to show the left small circle | Boolean | `false` by default | +| right | Whether to show the right small circle | Boolean | `false` by default | +| size | The size of the small circles | Number | `3` by default | +| fill | The filling color of the small circles | String | `'#72CC4A'` by default | +| stroke | The stroke color of the small circles | String | `'#72CC4A'` by default | +| lineWidth | The line width of the small circles | Number | `1` by default | + +Base on the code in [style](#style) section, we add `linkPoints` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for graph + defaultNode: { + // ... Other configurations for nodes + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + fill: '#fff', + size: 5, + }, + }, +}); +// ... +``` + +### icon + +`icon` is an object to configure the icon on the node. + +| Name | Description | Type | Remark | +| --- | --- | --- | --- | +| show | Whether to show the icon | Boolean | `false` by default | +| width | The width of the icon | Number | `16` by default | +| height | The height of the icon | Number | `16` by default | +| img | The image url or base64 of the icon | String | Configuring it means the icon is an image | +| text | iconfont for the icon | String | Configuring it means the icon is an iconfont | +| **offset** | **The offset of the icon** | **Number** | **`0` by default. It is a special property for triangle node** | + +Base on the code in [style](#style) section, we add `icon` to `defaultNode`.
img + +```javascript +const data = { + // ... data +}; +const graph = new G6.Graph({ + // ... Other configurations for the graph + defaultNode: { + // ... Other configurations for nodes + icon: { + show: true, + width: 30, + height: 30, + offset: 20, + // img: '...', The image url of the icon + // text: '...', Use an iconfont for the icon + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/built-in/triangle.zh.md b/packages/site/docs/manual/middle/elements/nodes/built-in/triangle.zh.md new file mode 100644 index 0000000000..9d96d1691c --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/built-in/triangle.zh.md @@ -0,0 +1,220 @@ +--- +title: Triangle +order: 5 +--- + +## Triangle + +G6 内置了三角形  Triangle 节点,其默认样式如下。标签文本位于三角形下方。
img + +## 使用方法 + +如 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 一节所示,配置节点的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.node(nodeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.node(nodeFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +⚠️ 注意: 除 `id`、`label` 应当配置到每个节点数据中外,其余的 [节点的通用属性](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性) 以及各个节点类型的特有属性(见内置节点类型)均支持这三种配置方式。 + +### 1 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultNode` 指定 `type` 为 `'triangle'`,即可使用 `triangle` 节点。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'triangle', + // 其他配置 + }, +}); +``` + +### 2 在数据中动态配置 + +如果需要使不同节点有不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [{ + id: 'node0', + type: 'triangle', + ... // 其他配置 + }, + ... // 其他节点 + ], + edges: [ + ... // 边 + ] +} +``` + +## 配置项说明 + +triangle 节点支持 [节点通用配置](/zh/docs/manual/middle/elements/nodes/defaultNode#节点的通用属性),下表对部分属性进行解释: + +| 名称 | 含义 | 类型 | 备注 | +| --- | --- | --- | --- | --- | +| size | 三角形的边长 | Number | Array | size 为数组时取第一个值 | +| **direction** | **三角形的方向** | **String** | **可取值:`'up'`,`'down'`,`'left'`,`'right'`。默认为 `'up'`** | +| style | 三角形默认样式 | Object | Canvas 支持的属性 | +| label | 标签文本内容 | String | | +| labelCfg | 标签文本配置项 | Object | | +| stateStyles | 各状态下的样式 | Object | 详见[配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) | +| linkPoints | **视觉上的**三个锚点 | Object | 默认不显示,应与 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint) 配合使用。二者区别请看 [linkPoints](#linkpoints) | +| icon | 三角形上 icon 配置 | Object | 默认不显示 icon | + +### 三角形方向 direction + +String 类型。可取值有:` '``up' `、`'down'`、`'left'`、`'right'`。默认为  ` '``up' `。通过设置 `direction`,可以修改三角形的方向。下面代码演示在实例化图时全局配置方法中配置 `direction`。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'triangle', + direction: 'down', + }, +}); +``` + +img +img +img +img + +> 上图分别是将 `direction` 配置为 `'up'`,`'down'`,`'left'`,`'right'` 的结果 + +### 样式属性 style + +Object 类型。支持 [节点通用样式](/zh/docs/manual/middle/elements/nodes/defaultNode#样式属性-style)。通过 `style` 配置来修改节点的填充色、描边等属性。下面代码演示在实例化图时全局配置方法中配置 `style`,使之达到如下图效果。
img + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'triangle', + label: 'triangle', + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // type: 'triangle', // 在数据中已经指定 type,这里无需再次指定 + direction: 'up', + size: 100, + style: { + fill: '#bae637', + stroke: '#eaff8f', + lineWidth: 5, + }, + }, +}); +graph.data(data); +graph.render(); +``` + +### 标签文本配置 labelCfg + +Object 类型。通过 `labelCfg` 配置标签文本。支持 [节点通用标签配置](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg)。基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `labelCfg`  配置项进行文本的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 节点其他属性 + labelCfg: { + position: 'center', + style: { + fill: '#9254de', + fontSize: 18, + }, + }, + }, +}); +// ... +``` + +### linkPoints Object 类型。通过配置 `linkPoints` ,可以指定节点上「上、左、右」三个小圆点。 + +⚠️ 注意: 区分于 `anchorPoints`: `anchorPoints` 是真正用于指定该节点相关边的连入位置的「**数组**」,见 [anchorPoints](/zh/docs/manual/middle/elements/nodes/anchorpoint);而 `linkPoints` 仅是指定是否「**绘制**」出四个圆点,不起实际的连接相关边的作用。二者常常配合使用。 + +| 名称 | 含义 | 类型 | 备注 | +| --------- | ------------------ | ------- | ------------------ | +| top | 是否显示上部的圆点 | Boolean | 默认为 `false` | +| left | 是否显示左侧的圆点 | Boolean | 默认为 `false` | +| right | 是否显示右侧的圆点 | Boolean | 默认为 `false` | +| size | 圆点的大小 | Number | 默认为 `3` | +| fill | 圆点的填充色 | String | 默认为 `'#72CC4A'` | +| stroke | 圆点的边框颜色 | String | 默认为 `'#72CC4A'` | +| lineWidth | 圆点边框的宽度 | Number | 默认为 `1` | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了  `linkPoints`  配置项进行连入点的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 其他属性 + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + fill: '#fff', + size: 5, + }, + }, +}); +// ... +``` + +### 图标  icon + +Object 类型。通过配置 `icon`,可以在圆上显示小图标。 + +| 名称 | 含义 | 类型 | 备注 | +| ---------- | ----------------- | ---------- | --------------------------------------------- | +| show | 是否显示 icon | Boolean | 默认为 false,不显示 | +| width | icon 的宽度 | Number | 默认为 16 | +| height | icon 的高度 | Number | 默认为 16 | +| img | icon 的地址或 base64 | String | 若配置则表示使用 iconfont 作为 icon | +| text | icon 的 iconfont | String | 若配置则表示使用 iconfont 作为 icon | +| **offset** | **icon 的偏移量** | **Number** | **默认为 0,triangle 节点的 icon 特有的配置** | + +基于上面 [样式属性 style](#样式属性-style) 中的代码,下面代码在 `defaultNode` 中增加了 `icon`  配置项进行图标的配置,使之达到如下图效果。
img + +```javascript +const data = { + // ... data 内容 +}; +const graph = new G6.Graph({ + // ... 图的其他属性 + defaultNode: { + // ... 其他属性 + icon: { + show: true, + width: 30, + height: 30, + offset: 20, + // img: '...', 可更换为其他图片地址 + // text: '...', 使用 iconfont + }, + }, +}); +// ... +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/custom-node.en.md b/packages/site/docs/manual/middle/elements/nodes/custom-node.en.md new file mode 100644 index 0000000000..2bdd8840b5 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/custom-node.en.md @@ -0,0 +1,572 @@ +--- +title: Custom Node +order: 2 +--- + +G6 provides abundant [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode), including [circle](/en/docs/manual/middle/elements/nodes/built-in/circle), [rect](/en/docs/manual/middle/elements/nodes/built-in/rect, [ellipse](/en/docs/manual/middle/elements/nodes/built-in/ellipse), [diamond](/en/docs/manual/middle/elements/nodes/built-in/diamond), [triangle](/en/docs/manual/middle/elements/nodes/built-in/triangle), [star](/en/docs/manual/middle/elements/nodes/built-in/star), [image](/en/docs/manual/middle/elements/nodes/built-in/image), [modelRect](/en/docs/manual/middle/elements/nodes/built-in/modelRect). Besides, the custom machanism allows the users to design their own type of nodes by `G6.registerNode(typeName: string, nodeDefinition: object, extendedNodeType?: string)`. A node with complex graphics shapes, complex interactions, fantastic animations can be implemented easily. For the parameters: + +- `typeName`: the name of the new node type; +- `extendedNodeType`: The name of the existing type that will be extended, which can be a built-in node type, or an existing custom node type. When it is not assigned, the custom node will not extend any existing node type; +- `nodeDefinition`: The definition of the new node type. The required options can be found at [Custom Mechanism API](/en/docs/api/registerItem#g6registernodenodename-options-extendednodename). When the `extendedNodeType` is assigned, the functions which are not rewritten will extend from the type with name `extendedNodeType`. + +**Noted** that if the `extendedNodeType` is assigned, the required functions such as `draw`, `update`, and `setState` will extend from `extendedNodeType` unless they are rewritten in `nodeDefinition`. Due to this mechanism, a question is often fed back: + +- Q: when the custom node/edge is updated, the re-draw logic is not the same as `draw` or `drawShape` function defined in `nodeDefinition`. e.g., some shapes are not updated as expected, and some text shapes show up. +- A: Since the `extendedNodeType` is assigned, and the `update` is not implemented in `extendedNodeType`, the `update` of the extended node type will be called when updating the node/edge, whose logic might be different from the `draw` or `drawShape` defined by yourself. To avoid this problem, you can override the `update` by `undefined` in `nodeDefinition`. When `update` is `undefined`, the `draw` or `drawShape` will be called when updating the node/edge. + +In this document, we will introduce the custom node mechanism by five examples:
1. Register a brand new node: Draw the graphics; Optimize the performance.
2. Register a node by extending a built-in node: Add extra graphics shape; Add animation.
3. Adjust the anchorPoints(link points);
4. Register a node with state styles: Response the states change by styles and animations 5. Custom Node with DOM + +As stated in [Shape](/en/docs/manual/middle/elements/shape/shape-keyshape), there are two points should be satisfied when customize a node: + +- Controll the life cycle of the node; +- Analyze the input data and show it by graphics. + +The API of cumstom node: + +```javascript +G6.registerNode( + 'nodeName', + { + options: { + style: {}, + stateStyles: { + hover: {}, + selected: {}, + }, + }, + /** + * Draw the node with label + * @param {Object} cfg The configurations of the node + * @param {G.Group} group Graphics group, the container of the shapes of the node + * @return {G.Shape} The keyShape of the node. It can be obtained by node.get('keyShape') + */ + draw(cfg, group) {}, + /** + * The extra operations after drawing the node. There is no operation in this function by default + * @param {Object} cfg The configurations of the node + * @param {G.Group} group Graphics group, the container of the shapes of the node + */ + afterDraw(cfg, group) {}, + /** + * Update the node and its label + * @override + * @param {Object} cfg The configurations of the node + * @param {Node} node The node item + */ + update(cfg, node) {}, + /** + * The operations after updating the node. It is combined with afterDraw generally + * @override + * @param {Object} cfg The configurations of the node + * @param {Node} node The node item + */ + afterUpdate(cfg, node) {}, + /** + * Should be rewritten when you want to response the state changes by animation. + * Responsing the state changes by styles can be configured, which is described in the document Middle-Behavior & Event-State + * @param {String} name The name of the state + * @param {Object} value The value of the state + * @param {Node} node The node item + */ + setState(name, value, node) {}, + /** + * Get the anchorPoints(link points for related edges) + * @param {Object} cfg The configurations of the node + * @return {Array|null} The array of anchorPoints(link points for related edges). Null means there are no anchorPoints + */ + getAnchorPoints(cfg) {}, + }, + extendedNodeType, +); +``` + +   ⚠️Attention: + +- `draw`: it is required if the custom node does not extend any parent; +- Coordinate system: The coordinate system of the shapes inside the custom node is a **sub coordinate system relating to itself**, which means the `(0, 0)` is the center of the node. And the coordinates of the node is related to the whole canvas, which is controled by the group contains it and users have no need to use it when customing a node type. When adding a `rect` shape into a custom node, be caution that its x and y should be minused half of its width and height. See the detail in [Register a Bran-new Node](#1-register-a-bran-new-edge); +- `update`: + - When the `update` function is not undefined: If user has defined the third parameter `extendedNodeType` of `registerNode`, which means extending a built-in node type, the `update` function of the extended node type of the custom node will be executed once the node is updated; If the third parameter of `registerNode` is not assigned, the `draw` function of the custom node will be executed instead; + - When the `update` function is defined, whether the third parameter of `registerNode` is defined, the `update` function will be executed when the node is updated. +- `afterDraw` and `afterUpdate`: they are used for extending the exited nodes in general. e.g. adding extra image on rect node, adding animation on a circle node, ...; +- `setState` should be override when you want to response the state changes by animation. Responsing the state changes by simple styles can be achieved by [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state); +- `getAnchorPoints`: it is only required when you want to contrain the link points for nodes and their related edges. The anchorPoints can be assigned in the node data as well. + +## 1. Register a Brand New Node + +### Render the Node + +Now, we are going to register a diamond node: + +> Although there is a built-in diamond node in G6, we implement it here to override it for demonstration. + +img + +   ⚠️ Attention: From the following code, you will understand that the coordinates of the sub shapes of the custom node is related to itself, which means the `(0, 0)` is the center of the node. E.g. the `x` and `y` of the `'text'` shape are both 0, which means the shape is on the center of the node; The `path` attribute of `'path'` is also defined with the origin `(0, 0)`. In the other words, users do not need to control the sub shapes' coordinates according to the nodes' coordinate which is controlled by the matrix of the parent group of the node. + +```javascript +G6.registerNode('diamond', { + draw(cfg, group) { + // If there is style object in cfg, it should be mixed here + const keyShape = group.addShape('path', { + attrs: { + path: this.getPath(cfg), // Get the path by cfg + stroke: cfg.color, // Apply the color to the stroke. For filling, use fill: cfg.color instead + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + // allow the shape to response the drag events + draggable: true + }); + if (cfg.label) { + // If the label exists + // The complex label configurations can be defined by labeCfg + // const style = (cfg.labelCfg && cfg.labelCfg.style) || {}; + // style.text = cfg.label; + const label group.addShape('text', { + attrs: { + x: 0, // center + y: 0, + textAlign: 'center', + textBaseline: 'middle', + text: cfg.label, + fill: '#666', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', + // allow the shape to response the drag events + draggable: true + }); + } + return keyShape; + }, + // Return the path of a diamond + getPath(cfg) { + const size = cfg.size || [40, 40]; + const width = size[0]; + const height = size[1]; + // / 1 \ + // 4 2 + // \ 3 / + const path = [ + ['M', 0, 0 - height / 2], // Top + ['L', width / 2, 0], // Right + ['L', 0, height / 2], // Bottom + ['L', -width / 2, 0], // Left + ['Z'], // Close the path + ]; + return path; + }, +}); +``` + +We have registered a dimond node. Attention: you need to assign `name` and `draggable` for the shapes added in the custom node, where **the value of `name` must be unique in a custom node/edge/combo type**. `draggable: true` means that the shape is allowed to response the drag events. Only when `draggable: true`, the interact behavior `'drag-node'` can be responsed on this shape. In the codes above, if you only assign `draggable: true` to the `keyShape` but not the `label`, the drag events will only be responsed on the `keyShape`. + +The following code uses the diamond node: + +```javascript +const data = { + nodes: [ + { id: 'node1', x: 50, y: 100, type: 'diamond' }, // The simplest form + { id: 'node2', x: 150, y: 100, type: 'diamond', size: [50, 100] }, // Add the size + { id: 'node3', x: 250, y: 100, color: 'red', type: 'diamond' }, // Add the color + { id: 'node4', x: 350, y: 100, label: '菱形', type: 'diamond' }, // Add the label + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, +}); +graph.data(data); +graph.render(); +``` + +img + +### Optimize the Performance + +When the nodes or edges are updated by `graph.update(item, cfg)`, the `draw` will be called for repainting. But in the situation with large amount of data (especially the text), repainting all the graphics shapes by `draw` has bad performance. + +Therefore, override the `update` function when registering a node for partial repainting is necessary. We can repaint some of the graphics shapes instead of all the graphis by `update`. The `update` is not required if you have no performance problem. + +To update a few graphics shapes of a node in `update`, you need find the graphics shapes to be updated frist: + +- Find the [keyShape](/en/docs/manual/middle/elements/shape/shape-keyshape#keyshape) by `group.get('children')[0]`, which is the return value of `draw`; +- Find the graphics shape of label by `group.get('children')[1]`. + +The code shown below update the path and the color of the keyShape of the diamond: + +```javascript +G6.registerNode('diamond', { + draw(cfg, group) { + // ... // Same as the code above + }, + getPath(cfg) { + // ... // Same as the code above + }, + update(cfg, node) { + const group = node.getContainer(); // Get the container of the node + const shape = group.get('children')[0]; // Find the first graphics shape of the node. It is determined by the order of being added + const style = { + path: this.getPath(cfg), + stroke: cfg.color, + }; + shape.attr(style); // Update + }, +}); +``` + +## 2. Extend a Built-in Node + +### Extend the Shape + +There are several [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) in G6. You can extend them to make some modification on them. It is similar to register the diamond node. single-node is the base class of all the node types, you can also extend it. (single-edge is the base class of all the edge types.) + +For example, we are going to extend the single-node. `draw`, `update`, and `setState` have been implemented in the single-node. Thus, we only override the `getShapeStyle`, which returns the path and the styles of graphics shapes. + +```javascript +G6.registerNode( + 'diamond', + { + draw(cfg, group) { + const size = this.getSize(cfg); // translate to [width, height] + const color = cfg.color; + const width = size[0]; + const height = size[1]; + // / 1 \ + // 4 2 + // \ 3 / + const path = [ + ['M', 0, 0 - height / 2], // Top + ['L', width / 2, 0], // Right + ['L', 0, height / 2], // Bottom + ['L', -width / 2, 0], // Left + ['Z'], // Close the path + ]; + const style = G6.Util.mix( + {}, + { + path: path, + stroke: color, + }, + cfg.style, + ); + // add a path as keyShape + const keyShape = group.addShape('path', { + attrs: { + ...style, + }, + draggable: true, + name: 'diamond-keyShape', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + }); + // return the keyShape + return keyShape; + }, + }, + // Extend the 'single-node' + 'single-node', +); +``` + +### Add Animation + +We are going to add animation by `afterDraw` in this section. The result:
img + +- Extend the built-in rect node, and add a graphics shape in the rect; +- Execute the animation repeatly. + +```javascript +// Register a type of custom node named inner-animate +G6.registerNode( + 'inner-animate', + { + afterDraw(cfg, group) { + const size = cfg.size; + const width = size[0] - 14; + const height = size[1] - 14; + // Add an image shape + const image = group.addShape('image', { + attrs: { + x: -width / 2, + y: -height / 2, + width: width, + height: height, + img: cfg.img, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'image-shape', + }); + // Execute the animation + image.animate( + (ratio) => { + const matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; + const toMatrix = Util.transform(matrix, [['r', ratio * Math.PI * 2]]); + return { + matrix: toMatrix, + }; + }, + { + repeat: true, + duration: 3000, + easing: 'easeCubic', + }, + ); + }, + }, + // Extend the rect node + 'rect', +); +``` + +For more information about animation, please refer to [Basic Ainmation](/en/docs/manual/middle/animation). + +
+ +## 3. Adjust the anchorPoint + +The [anchorPoint](/en/docs/manual/middle/elements/nodes/anchorpoint) of a node is **the intersection of the node and its related edges**.
+ +img +img + +> (Left) The diamond node has no anchorPoints. (Right) The diamond node has anchorPoints. + +There are two ways to adjust the anchorPoints of the node: + +- Configure the `anchorPoints` in the data. + +**Applicable Scene:** Assign different anchorPoints for different nodes. + +- Assign `getAnchorPoints` when registering a custom node. + +**Applicable Scene:** Configure the anchorPoints globally for this type of node. + +### Configure the anchorPoints in Data + +```javascript +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 100, + anchorPoints: [ + [0, 0.5], // The center of the left border + [1, 0.5], // The center of the right border + ], + }, + //... // Other nodes + ], + edges: [ + //... // Other edges + ], +}; +``` + +### Assign anchorPoints When Registering Node + +```javascript +G6.registerNode( + 'diamond', + { + //... // Other functions + getAnchorPoints() { + return [ + [0, 0.5], // The center of the left border + [1, 0.5], // The center of the right border + ]; + }, + }, + 'rect', +); +``` + +## 4. Register Node with State Styles + +In general, nodes and edges should response the states change by styles chaging. For example, highlight the node or edge clicked/hovered by user. We can achieve it by two ways: + +1. Add a flag on the node data, control the style according to the flag in `draw` when registering a custom node; +2. Separate the interactive states from source data and `draw`, update the node only. + +We recommend adjust the state styles by the second way, which can be achieved by: + +- Response the states in `setState` function when registering a node/edge; +- Set/change the state by `graph.setItemState()`. + +Based on rect node, we extend a custom node with white filling. It will be turned to red when the mouse clicks it. Implement it by the code below: + +```javascript +// Extend rect +G6.registerNode( + 'custom', + { + // Response the states + setState(name, value, item) { + const group = item.getContainer(); + const shape = group.get('children')[0]; // Find the first graphics shape of the node. It is determined by the order of being added + if (name === 'selected') { + if (value) { + shape.attr('fill', 'red'); + } else { + shape.attr('fill', 'white'); + } + } + }, + }, + 'rect', +); + +// Click to select, cancel by clicking again +graph.on('node:click', (ev) => { + const node = ev.item; + graph.setItemState(node, 'selected', !node.hasState('selected')); // Switch the selected state +}); +``` + +G6 does not limit the states for nodes/edges, you can assign any states to a node once you response it in the `setState` function. e.g. magnify the node by hovering:
img + +```javascript +G6.registerNode( + 'custom', + { + // Response the states change + setState(name, value, item) { + const group = item.getContainer(); + const shape = group.get('children')[0]; // Find the first graphics shape of the node. It is determined by the order of being added + if (name === 'running') { + if (value) { + shape.animate( + { + r: 20, + }, + { + repeat: true, + duration: 1000, + }, + ); + } else { + shape.stopAnimate(); + shape.attr('r', 10); + } + } + }, + }, + 'circle', +); + +// Activate 'running' by mouse entering. Turn it of by mouse leaving. +graph.on('node:mouseenter', (ev) => { + const node = ev.item; + graph.setItemState(node, 'running', true); +}); + +graph.on('node:mouseleave', (ev) => { + const node = ev.item; + graph.setItemState(node, 'running', false); +}); +``` + +## 5. Custom Node with DOM + +> SVG and DOM shape are not supported in V3.3.x. +> DOM node is available only when the `renderer` of the graph instance is `'svg'`. + +⚠️ Attention: + +- Only support native HTML DOM, but not react or other components; +- If you custom a Node type or an Edge type with dom shape, please use the original DOM events instead of events of G6. +- In Safari, if you assign `position:relative` for the a dom node, the rendered position might be unexpected. It is related to the [foreignObject bug of Safari](https://bugs.webkit.org/show_bug.cgi?id=23113). [Issus](https://github.com/antvis/G6/issues/2990). + + +Here, we demonstrate customing a node named `'dom-node'` with DOM. We add a `'dom'` type shape with `group.addShape` in `draw` function, and set the `html` of it to be the `html` value we want. + +img + +```javascript +G6.registerNode( + 'dom-node', + { + draw: (cfg: ModelConfig, group: Group) => { + return group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + // DOM's html + html: ` +
+
+ img +
+ ${cfg.label} +
+ `, + }, + name: 'dom-node-keyShape', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + draggable: true, + }); + }, + }, + 'single-node', +); +``` + +Now, we have `'dom-node'` type of node with DOM. Be attention that you should assign `name` and `draggable` for the shapes you added after V3.3, where `name` is an ununique string. The shape is allowed to be dragged when `draggable` is `true`. + +We render the graph with `'dom-node'` as following: + +img + +```javascript +const data = { + nodes: [ + { id: 'node1', x: 50, y: 100 }, + { id: 'node2', x: 150, y: 100 }, + ], + edges: [(source: 'node1'), (target: 'node2')], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + defaultNode: { + type: 'dom-node', + size: [120, 40], + }, +}); +graph.data(data); +graph.render(); +``` + +⚠️ Attention: DOM Shape in G6 does not support the events on Node and Edge. You can bind events for DOM as the way in HTML. e.g.: + +```javascript +G6.registerNode( + 'dom-node', + { + draw: (cfg: ModelConfig, group: Group) => { + return group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + // DOM's html with onclick event + html: ` +
+
+ img +
+ ${cfg.label} +
+ `, + }, + name: 'dom-node-keyShape', // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + draggable: true, + }); + }, + }, + 'single-node', +); +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/custom-node.zh.md b/packages/site/docs/manual/middle/elements/nodes/custom-node.zh.md new file mode 100644 index 0000000000..6373d1c219 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/custom-node.zh.md @@ -0,0 +1,575 @@ +--- +title: 自定义节点 +order: 2 +--- + +G6 提供了一系列[内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode),包括 [circle](/zh/docs/manual/middle/elements/nodes/built-in/circle)、[rect](/zh/docs/manual/middle/elements/nodes/built-in/rect)、[diamond](/zh/docs/manual/middle/elements/nodes/built-in/diamond)、[triangle](/zh/docs/manual/middle/elements/nodes/built-in/triangle)、[star](/zh/docs/manual/middle/elements/nodes/built-in/star)、[image](/zh/docs/manual/middle/elements/nodes/built-in/image)、[modelRect](/zh/docs/manual/middle/elements/nodes/built-in/modelRect)。若内置节点无法满足需求,用户还可以通过 `G6.registerNode(typeName: string, nodeDefinition: object, extendedNodeType?: string)` 进行自定义节点,方便用户开发更加定制化的节点,包括含有复杂图形的节点、复杂交互的节点、带有动画的节点等。其参数: + +- `typeName`:该新节点类型名称; +- `extendedNodeType`:被继承的节点类型,可以是内置节点类型名,也可以是其他自定义节点的类型名。`extendedNodeType` 未指定时代表不继承其他类型的节点; +- `nodeDefinition`:该新节点类型的定义,其中必要函数详见 [自定义机制 API](/zh/docs/api/registerItem#g6registernodenodename-options-extendednodename)。当有 `extendedNodeType` 时,没被复写的函数将会继承 `extendedNodeType` 的定义。 + +**需要注意的是**,自定义节点/边时,若给定了 `extendedNodeType`,如 `draw`,`update`,`setState` 等必要的函数若不在 `nodeDefinition` 中进行复写,将会继承 `extendedNodeType` 中的相关定义。常见问题: + +- Q:节点/边更新时,没有按照在 `nodeDefinition` 中自定义实现的 `draw` 或 `drawShape` 逻辑更新。例如,有些图形没有被更新,增加了没有在 `draw` 或 `drawShape` 方法中定义的图形等。 +- A:由于继承了 `extendedNodeType`,且在 `nodeDefinition` 中没有复写 `update` 方法,导致节点/边更新时执行了 `extendedNodeType` 中的 `update` 方法,从而与自定义的 `draw` 或 `drawShape` 有出入。可以通过复写 `update` 方法为 `undefined` 解决。当 `update` 方法为 `undefined` 时,节点/边的更新将会执行 `draw` 或 `drawShape` 进行重绘。 + +在本章中我们会通过五个案例,从简单到复杂讲解节点的自定义。这五个案例是:
1. 从无到有的定义节点:绘制图形;优化性能。
2. 扩展现有的节点:附加图形;增加动画。
3. 调整节点的锚点;
4. 调整节点的鼠标选中/悬浮样式:样式变化响应;动画响应;
5. 使用 DOM 自定义节点。 + +通过 [图形 Shape](/zh/docs/manual/middle/elements/shape/shape-keyshape) 章节的学习,我们应该已经知道了自定义节点时需要满足以下两点: + +- 控制节点的生命周期; +- 解析用户输入的数据,在图形上展示。 + +G6 中自定义节点的 API 如下: + +```javascript +G6.registerNode( + 'nodeName', + { + options: { + style: {}, + stateStyles: { + hover: {}, + selected: {}, + }, + }, + /** + * 绘制节点,包含文本 + * @param {Object} cfg 节点的配置项 + * @param {G.Group} group 图形分组,节点中图形对象的容器 + * @return {G.Shape} 返回一个绘制的图形作为 keyShape,通过 node.get('keyShape') 可以获取。 + * 关于 keyShape 可参考文档 核心概念-节点/边/Combo-图形 Shape 与 keyShape + */ + draw(cfg, group) {}, + /** + * 绘制后的附加操作,默认没有任何操作 + * @param {Object} cfg 节点的配置项 + * @param {G.Group} group 图形分组,节点中图形对象的容器 + */ + afterDraw(cfg, group) {}, + /** + * 更新节点,包含文本 + * @override + * @param {Object} cfg 节点的配置项 + * @param {Node} node 节点 + */ + update(cfg, node) {}, + /** + * 更新节点后的操作,一般同 afterDraw 配合使用 + * @override + * @param {Object} cfg 节点的配置项 + * @param {Node} node 节点 + */ + afterUpdate(cfg, node) {}, + /** + * 响应节点的状态变化。 + * 在需要使用动画来响应状态变化时需要被复写,其他样式的响应参见下文提及的 [配置状态样式] 文档 + * @param {String} name 状态名称 + * @param {Object} value 状态值 + * @param {Node} node 节点 + */ + setState(name, value, node) {}, + /** + * 获取锚点(相关边的连入点) + * @param {Object} cfg 节点的配置项 + * @return {Array|null} 锚点(相关边的连入点)的数组,如果为 null,则没有控制点 + */ + getAnchorPoints(cfg) {}, + }, + // 继承内置节点类型的名字,例如基类 'single-node',或 'circle', 'rect' 等 + // 当不指定该参数则代表不继承任何内置节点类型 + extendedNodeType, +); +``` + +   ⚠️ 注意: + +- 如果不从任何现有的节点或从 `'single-node'` 扩展新节点时,`draw` 方法是必须的; +- 节点内部所有图形**使用相对于节点自身的坐标系**,即 `(0, 0)` 是该节点的中心。而节点的坐标是相对于画布的,由该节点 group 上的矩阵控制,自定义节点中不需要用户感知。若在自定义节点内增加 `rect` 图形,要注意让它的 x 与 y 各减去其长与宽的一半。详见例子 [从无到有定义节点](#1-从无到有定义节点); +- `update` 方法可以不定义: + - 当 `update` 未定义:若指定了 `registerNode` 的第三个参数 `extendedNodeType`(即代表继承指定的内置节点类型),则节点更新时将执行被继承的内置节点类型的 `update` 逻辑;若未指定 `registerNode` 的第三个参数,则节点更新时会执行 `draw` 方法,所有图形清除重绘; + - 当定义了 `update` 方法,则不论是否指定 `registerNode` 的第三个参数,在节点更新时都会执行复写的 `update` 函数逻辑。 +- `afterDraw`,`afterUpdate` 方法一般用于扩展已有的节点,例如:在矩形节点上附加图片,圆节点增加动画等; +- `setState` 只有在需要使用动画的方式来响应状态变化时需要复写,一般的样式响应状态变化可以通过 [配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式) 实现; +- `getAnchorPoints` 方法仅在需要限制与边的连接点时才需要复写,也可以在数据中直接指定。 + +## 1. 从无到有定义节点 + +### 绘制图形 + +我们自己来实现一个菱形的节点,如下图所示。 + +> G6 有内置的菱形节点 diamond。为了演示,这里实现了一个自定义的菱形,相当于复写了内置的 diamond。 + +img + +   ⚠️ 注意: 从下面代码可以看出,自定义节点中所有通过 `addShape` 增加的图形的坐标都是**相对于节点自身的子坐标系**,即 `(0, 0)` 是该节点的中心。如 `'text'` 图形的 `x` 和 `y` 均为 0,代表该图形相对于该节点居中;`'path'` 图形 `path` 属性中的坐标也是以 `(0, 0)` 为原点计算的。换句话说,在**自定义节点时不需要感知相对于画布的节点坐标**,节点坐标由该节点所在 group 的矩阵控制。 + +```javascript +G6.registerNode('diamond', { + draw(cfg, group) { + // 如果 cfg 中定义了 style 需要同这里的属性进行融合 + const keyShape = group.addShape('path', { + attrs: { + path: this.getPath(cfg), // 根据配置获取路径 + stroke: cfg.color, // 颜色应用到描边上,如果应用到填充,则使用 fill: cfg.color + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'path-shape', + // 设置 draggable 以允许响应鼠标的图拽事件 + draggable: true, + }); + if (cfg.label) { + // 如果有文本 + // 如果需要复杂的文本配置项,可以通过 labeCfg 传入 + // const style = (cfg.labelCfg && cfg.labelCfg.style) || {}; + // style.text = cfg.label; + const label = group.addShape('text', { + // attrs: style + attrs: { + x: 0, // 居中 + y: 0, + textAlign: 'center', + textBaseline: 'middle', + text: cfg.label, + fill: '#666', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'text-shape', + // 设置 draggable 以允许响应鼠标的图拽事件 + draggable: true, + }); + } + return keyShape; + }, + // 返回菱形的路径 + getPath(cfg) { + const size = cfg.size || [40, 40]; // 如果没有 size 时的默认大小 + const width = size[0]; + const height = size[1]; + // / 1 \ + // 4 2 + // \ 3 / + const path = [ + ['M', 0, 0 - height / 2], // 上部顶点 + ['L', width / 2, 0], // 右侧顶点 + ['L', 0, height / 2], // 下部顶点 + ['L', -width / 2, 0], // 左侧顶点 + ['Z'], // 封闭 + ]; + return path; + }, +}); +``` + +上面的代码自定义了一个菱形节点。值得注意的是,G6 3.3 需要用户为自定义节点中的图形设置 `name` 和 `draggable`。**其中,`name` 值必须在同元素类型内唯一**。`draggable` 为 `true` 是表示允许该图形响应鼠标的拖拽事件,只有 `draggable: true` 时,图上的交互行为 `'drag-node'` 才能在该图形上生效。若上面代码仅在 keyShape 上设置了 `draggable: true`,而 label 图形上没有设置,则鼠标拖拽只能在 keyShape 上响应。 + +现在,我们使用下面的数据输入就会绘制出 diamond 这个节点。 + +```javascript +const data = { + nodes: [ + { id: 'node1', x: 50, y: 100, type: 'diamond' }, // 最简单的 + { id: 'node2', x: 150, y: 100, type: 'diamond', size: [50, 100] }, // 添加宽高 + { id: 'node3', x: 250, y: 100, color: 'red', type: 'diamond' }, // 添加颜色 + { id: 'node4', x: 350, y: 100, label: '菱形', type: 'diamond' }, // 附加文本 + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, +}); +graph.data(data); +graph.render(); +``` + +img + +### 优化性能 + +当图中节点或边通过  `graph.update(item, cfg)` 重绘时,默认情况下会调用节点的 `draw` 方法进行重新绘制。在数据量大或节点上图形数量非常多(特别是文本多)的情况下,`draw` 方法中对所有图形、赋予样式将会非常消耗性能。 + +在自定义节点时,重写  `update` 方法,在更新时将会调用该方法替代 `draw`。我们可以在该方法中指定需要更新的图形,从而避免频繁调用  `draw` 、全量更新节点上的所有图形。当然,`update` 方法是可选的,如果没有性能优化的需求可以不重写该方法。 + +在实现 diamond 的过程中,重写  `update` 方法,找到需要更新的 shape 进行更新,从而优化性能。寻找需要更新的图形可以通过: + +- `group.get('children')[0]` 找到 [关键图形  keyShape](/zh/docs/manual/middle/elements/shape/shape-keyshape#keyshape),也就是 `draw` 方法返回的 shape; +- `group.get('children')[1]` 找到 label 图形。 + +下面代码仅更新了 diamond 的关键图形的路径和颜色。 + +```javascript +G6.registerNode('diamond', { + draw(cfg, group) { + // ... // 见前面代码 + }, + getPath(cfg) { + // ... // 见前面代码 + }, + update(cfg, node) { + const group = node.getContainer(); // 获取容器 + const shape = group.get('children')[0]; // 按照添加的顺序 + const style = { + path: this.getPath(cfg), + stroke: cfg.color, + }; + shape.attr(style); // 更新属性 + // 更新文本的逻辑类似,但是需要考虑 cfg.label 是否存在的问题 + // 通过 label.attr() 更新文本属性即可 + }, +}); +``` + +## 2. 扩展现有节点 + +### 扩展 Shape + +G6 中已经[内置了一些节点](/zh/docs/manual/middle/elements/nodes/defaultNode),如果用户仅仅想对现有节点进行调整,复用原有的代码,则可以基于现有的节点进行扩展。同样实现 diamond ,可以基于  circle、ellipse、rect 等内置节点的进行扩展。single-node 是这些内置节点类型的基类,也可以基于它进行扩展。(single-edge 是所有内置边类型的基类。) + +下面以基于 single-node 为例进行扩展。`update`,`setState` 方法在  single-node 中都有实现,这里仅需要复写 `draw` 方法即可。返回的对象中包含自定义图形的路径和其他样式。 + +```javascript +G6.registerNode( + 'diamond', + { + draw(cfg, group) { + const size = this.getSize(cfg); // 转换成 [width, height] 的模式 + const color = cfg.color; + const width = size[0]; + const height = size[1]; + // / 1 \ + // 4 2 + // \ 3 / + const path = [ + ['M', 0, 0 - height / 2], // 上部顶点 + ['L', width / 2, 0], // 右侧顶点 + ['L', 0, height / 2], // 下部顶点 + ['L', -width / 2, 0], // 左侧顶点 + ['Z'], // 封闭 + ]; + const style = G6.Util.mix( + {}, + { + path: path, + stroke: color, + }, + cfg.style, + ); + // 增加一个 path 图形作为 keyShape + const keyShape = group.addShape('path', { + attrs: { + ...style, + }, + draggable: true, + name: 'diamond-keyShape', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + }); + // 返回 keyShape + return keyShape; + }, + }, + // 注意这里继承了 'single-node' + 'single-node', +); +``` + +### 添加动画 + +通过 `afterDraw` 同样可以实现扩展,下面我们来看一个节点的动画场景,如下图所示。
img + +上面的动画效果,可以通过以下方式实现: + +- 扩展内置的 rect,在 rect 中添加一个图形; +- 反复执行新添加图形的旋转动画。 + +```javascript +// 自定义一个名为 inner-animate 的节点 +G6.registerNode('inner-animate', { + afterDraw(cfg, group) { + const size = cfg.size; + const width = size[0] - 14; + const height = size[1] - 14; + // 添加图片 + const image = group.addShape('image', { + attrs: { + x: - width / 2, + y: - height / 2, + width: width, + height: height, + img: cfg.img + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'image-shape' + }); + // 执行旋转动画 + image.animate((ratio) => { + const matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; + const toMatrix = Util.transform(matrix, [ + ['r', ratio * Math.PI * 2] + ]) ; + return { + matrix: toMatrix + }; + }, { + repeat: true + duration: 3000, + easing: 'easeCubic' + }); + } +}, +// 继承了 rect 节点 +'rect'); +``` + +更多关于动画的实现,请参考[基础动画](/zh/docs/manual/middle/animation)章节。 + +
+ +## 3. 调整锚点 anchorPoint + +节点上的[锚点 anchorPoint](/zh/docs/manual/middle/elements/nodes/anchorpoint) 作用是**确定节点与边的相交的位置**,看下面的场景:
+ +img +img + +> (左)没有设置锚点时。(右)diamond 设置了锚点后。 + +有两种方式来调整节点上的锚点: + +- 在数据里面指定 `anchorPoints`。 + +**适用场景:**可以为不同节点配置不同的锚点,更定制化。 + +- 自定义节点中通过 `getAnchorPoints` 方法指定锚点。 + +**适用场景:**全局配置锚点,所有该自定义节点类型的节点都相同。 + +### 数据中指定锚点 + +```javascript +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 100, + anchorPoints: [ + [0, 0.5], // 左侧中间 + [1, 0.5], // 右侧中间 + ], + }, + //... // 其他节点 + ], + edges: [ + //... // 边 + ], +}; +``` + +### 自定义时指定锚点 + +```javascript +G6.registerNode( + 'diamond', + { + //... // 其他方法 + getAnchorPoints() { + return [ + [0, 0.5], // 左侧中间 + [1, 0.5], // 右侧中间 + ]; + }, + }, + 'rect', +); +``` + +## 4. 调整状态样式 + +常见的交互都需要节点和边通过样式变化做出反馈,例如鼠标移动到节点上、点击选中节点/边、通过交互激活边上的交互等,都需要改变节点和边的样式,有两种方式来实现这种效果: + +1. 在数据上添加标志字段,在自定义 shape 过程中根据约定进行渲染; +2. 将交互状态同原始数据和绘制节点的逻辑分开,仅更新节点。 + +我们推荐用户使用第二种方式来实现节点的状态调整,可以通过以下方式来实现: + +- 在 G6 中自定义节点/边时在 `setState` 方法中进行节点状态变化的响应; +- 通过 `graph.setItemState()` 方法来设置状态。 + +基于 rect 扩展出一个 custom 图形,默认填充色为白色,当鼠标点击时变成红色,实现这一效果的示例代码如下: + +```javascript +// 基于 rect 扩展出新的图形 +G6.registerNode( + 'custom', + { + // 响应状态变化 + setState(name, value, item) { + const group = item.getContainer(); + const shape = group.get('children')[0]; // 顺序根据 draw 时确定 + if (name === 'selected') { + if (value) { + shape.attr('fill', 'red'); + } else { + shape.attr('fill', 'white'); + } + } + }, + }, + 'rect', +); + +// 点击时选中,再点击时取消 +graph.on('node:click', (ev) => { + const node = ev.item; + graph.setItemState(node, 'selected', !node.hasState('selected')); // 切换选中 +}); +``` + +G6 并未限定节点的状态,只要你在 `setState` 方法中进行处理你可以实现任何交互,如实现鼠标放到节点上后节点逐渐变大的效果。
img + +```javascript +G6.registerNode( + 'custom', + { + // 响应状态变化 + setState(name, value, item) { + const group = item.getContainer(); + const shape = group.get('children')[0]; // 顺序根据 draw 时确定 + if (name === 'running') { + if (value) { + shape.animate( + { + r: 20, + }, + { + repeat: true, + duration: 1000, + }, + ); + } else { + shape.stopAnimate(); + shape.attr('r', 10); + } + } + }, + }, + 'circle', +); + +// 鼠标移动到上面 running,移出结束 +graph.on('node:mouseenter', (ev) => { + const node = ev.item; + graph.setItemState(node, 'running', true); +}); + +graph.on('node:mouseleave', (ev) => { + const node = ev.item; + graph.setItemState(node, 'running', false); +}); +``` + +## 5. 使用 DOM 自定义节点 + +> SVG 与 DOM 图形在 V3.3.x 中不支持。 +> 仅在 Graph 的 `renderer` 为 `'svg'` 时可以使用 DOM 自定义节点。 + +⚠️ 注意: + +- 只支持原生 HTML DOM,不支持各类 react、vue 组件; +- 使用 `'dom'` 进行自定义的节点或边,不支持 G6 的交互事件,请使用原生 DOM 的交互事件; +- 在 Safari 中,若 dom 节点被设置了 `position:relative`,将会导致渲染异常。该问题与 [Safari 的 foreignObject bug](https://bugs.webkit.org/show_bug.cgi?id=23113) 有关。[Issus](https://github.com/antvis/G6/issues/2990)。 + +这里,我们演示使用 DOM 自定义一个名为 `'dom-node'` 的节点。在 `draw` 方法中使用 `group.addShape` 增加一个 `'dom'` 类型的图形,并设置其 `html` 为 DOM 的 `html` 值。 + +img + +```javascript +G6.registerNode( + 'dom-node', + { + draw: (cfg: ModelConfig, group: Group) => { + return group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + // 传入 DOM 的 html + html: ` +
+
+ img +
+ ${cfg.label} +
+ `, + }, + name: 'dom-node-keyShape', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + draggable: true, + }); + }, + }, + 'single-node', +); +``` + +上面的代码自定义了一个名为 `'dom-node'` 的带有 DOM 的节点。值得注意的是,G6 3.3 需要用户为自定义节点中的图形设置 `name` 和 `draggable`。**其中,`name` 值必须在同元素类型内唯一**。`draggable` 为 `true` 是表示允许该图形响应鼠标的拖拽事件,只有 `draggable: true` 时,图上的交互行为 `'drag-node'` 才能在该图形上生效。 + +现在,我们使用下面的数据输入就会绘制出带有 `'dom-node'` 节点的图。 + +img + +```javascript +const data = { + nodes: [ + { id: 'node1', x: 50, y: 100 }, + { id: 'node2', x: 150, y: 100 }, + ], + edges: [(source: 'node1'), (target: 'node2')], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + defaultNode: { + type: 'dom-node', + size: [120, 40], + }, +}); +graph.data(data); +graph.render(); +``` + +⚠️ 注意: G6 的节点/边事件不支持 DOM 类型的图形。如果需要为 DOM 节点绑定事件,请使用原生 DOM 事件。例如: + +```javascript +G6.registerNode( + 'dom-node', + { + draw: (cfg: ModelConfig, group: Group) => { + return group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + // 传入 DOM 的 html,带有原生 onclick 事件 + html: ` +
+
+ img +
+ ${cfg.label} +
+ `, + }, + name: 'dom-node-keyShape', // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + draggable: true, + }); + }, + }, + 'single-node', +); +``` diff --git a/packages/site/docs/manual/middle/elements/nodes/defaultNode.en.md b/packages/site/docs/manual/middle/elements/nodes/defaultNode.en.md new file mode 100644 index 0000000000..fb8aba4bd0 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/defaultNode.en.md @@ -0,0 +1,328 @@ +--- +title: Overview of Nodes +order: 0 +--- + +The built-in nodes in G6 include circle, rect, ellipse, diamond, triangle, star, image, modelRect, and donut(supported after v4.2.5).
img +img + +In this document, we will briefly introduce the built-in nodes in G6, the common property, and the way to configure the node type. To know more about each type of built-in nodes in G6, please refer to the corresponding documents in this directory. + +## Types of Default Nodes + +The table below shows the built-in nodes and their special properties: + +| Name | Description | Default | +| --- | --- | --- | +| circle | Circle node:
- `size` is a number representing the diameter
- The circle is centered at the node position
- `color` takes effect on the stroke
- The label is placed on the center of the circle by default
- More properties are described in [circle](/en/docs/manual/middle/elements/nodes/built-in/circle)
| img | +| rect | Rect node:
- `size` is an array, e.g. [100, 50]
- The rect in centered at the node position
- `color` takes effect on the stroke
- The label is placed on the center of the circle by default
- More properties are described in [rect](/zh/docs/manual/middle/elements/nodes/built-in/rect)
| img | +| ellipse | Ellipse node:
- `size` is an array, representing the lengths of major diameter and minor diameter
- The ellipse is centered at the node position
- `color` takes effect on the stroke
- The label is placed on the center of the circle by default
- More properties are described in [ellipse](/zh/docs/manual/middle/elements/nodes/built-in/ellipse)
| img | +| diamond | Diamond node:
- `size` is an array, representing the width and height of the diamond
- The diamond is centered on the node position
- `color` takes effect on the stroke
- The label is placed on the center of the circle by default
- More properties are described in [diamond](/zh/docs/manual/middle/elements/nodes/built-in/diamond)
| img | +| triangle | Triangle node:
- `size` is an array, representing the length of the base and the height of the triangle
- The triangle is centered on the node position
- `color` takes effect on the stroke
- he label lays on the bottom of the node by default
- More properties are described in [triangle](/zh/docs/manual/middle/elements/nodes/built-in/triangle)
| img | +| star | Star node:
- `size` is a number, representing the size of the star
- The star is centered on the node position
- `color` takes effect on the stroke
- The label is placed on the center of the circle by default
- More properties are described in [star](/zh/docs/manual/middle/elements/nodes/built-in/star)
| img | +| image | Image node:
- `size` is an array, representing the width and the height of the image
- The image is centered on the node position
- `img` The url of the image. It can be assigned in `style` as well
- `color` does not take effect
- The label lays on the bottom of the node by default
- More properties are described in [image](/zh/docs/manual/middle/elements/nodes/built-in/image)
| img | +| modelRect | Card node:
- `size` is an array, representing the width and the height of the card
- The modelRect is centered on the node position
- `color` takes effect on the stroke
- The label is placed on the center of the circle by default
- If `description` exists, it will lay below the label
- More properties are described in [modelRect](/zh/docs/manual/middle/elements/nodes/built-in/modelRect)
| img
 img | +| donut | Circle node:
- `size` is a number representing the diameter
- The circle is centered at the node position
- `color` takes effect on the stroke
- The label is placed on the center of the circle by default
- Valid property `donutAttrs` should be assigned
- More properties are in [Donut](/en/docs/manual/middle/elements/nodes/built-in/donut)
| img | + +## Common Property + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| id | true | String | The ID of the node, **MUST** be a unique string | +| x | false | Number | x coordinate | +| y | false | Number | y coordinate | +| type | false | String | The shape type of the node. It can be the type of built-in Node, or the custom Node. `'circle'` by default | +| size | false | Number / Array | The size of the node | +| anchorPoints | false | Array | The interactions of the node and related edges. It can be null. `[0, 0]` represents the anchor on the left top; `[1, 1]`represents the anchor ont he right bottom | +| style | false | Object | The node style | +| label | false | String | The label text of the node | +| labelCfg | false | Object | The configurations of the label | + +### style + +`style` is an object to configure the filling color, stroke color, shadow, and so on. Here is the commonly used properties in `style`: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| fill | false | String | The filling color | +| stroke | false | String | The stroke color | +| lineWidth | false | Number | The line width of the stroke | +| lineDash | false | Number[] | The lineDash of the stroke | +| shadowColor | false | String | The shadow color | +| shadowBlur | false | Number | The blur of the shadow | +| shadowOffsetX | false | Number | The x offset of the shadow | +| shadowOffsetY | false | Number | The y offset of the shadow | +| opacity | false | Number | The alpha or transparency of the node | +| fillOpacity | false | Number | The filling alpha or transparency of the node | +| cursor | false | String | The type of the mouse when hovering the node. The options are the same as [cursor in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) | + +Configure `style` globally when instantiating the Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // ... Other properties for nodes + style: { + fill: '#steelblue', + stroke: '#eaff8f', + lineWidth: 5, + // ... Other style properties + }, + }, +}); +``` + +### label and labelCfg + +`label` is a string which indicates the content of the label.
`labelCfg` is an object to configure the label. The commonly used configurations of `labelCfg`: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| position | false | String | The relative positions to the node. Options:  `'center'`, `'top'`, `'left'`, `'right'`, `'bottom'`. `'center'` by default | +| offset | false | Number | The offset value of the label. When the `position` is `'bottom'`, the value is the top offset of the node; When the `position` is `'left'`, the value is the right offset of the node; it is similar with other `position`. | +| style | false | Object | The style property of the label | + +The commonly used configurations for the `style` in the above table are: + +| Name | Required | Type | Remark | +| --- | --- | --- | --- | +| fill | false | String | The color of the label | +| stroke | false | String | The stroke color of the label | +| lineWidth | false | Number | The line width of the label | +| opacity | false | Number | The opacity of the label | +| fontFamily | false | String | The font family | +| fontSize | false | Number | The font size of the label | +| ... The label styles of node and edge are the same, summarized in [Text Shape API](/en/docs/api/shapeProperties/#text) | | | | + +The following code shows how to configure `label` and `labelCfg` globally when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // ... Other properties for nodes + label: 'node-label', + labelCfg: { + position: 'bottom', + offset: 10, + style: { + fill: '#666', + }, + }, + }, +}); +``` + +## Configure Nodes + +There are three methods to configure nodes: Configure nodes globally when instantiating a Graph; Configure nodes in their data; Configure nodes by `graph.node(nodeFn)`. Their priorities are: + +`graph.node(nodeFn)` > Configure in data > Configure globally + +That means, if there are same configurations in different ways, the way with higher priority will take effect. + +⚠️ Attention: Expect for `id`, and `label` which should be assigned to every single node data, the other configurations in [The Common Property](#common-property) and in each node type (refer to doc of each node type) support to be assigned by the three ways. + +### Configure Globally When Instantiating Graph + +Assign `defaultNode` to configure all the nodes globally: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'circle', + // Other properties for all the nodes + }, +}); +``` + +### Configure in Data + +To configure different nodes with different properties, you can write the properties into their data individually: + +```javascript +const data = { + nodes: [ + { + id: 'node0', + size: 100, + type: 'rect', + // ... // Other properties for this node + style: { + // ... // Style properties for this node. Different styles for different types of nodes can be refered to the subdocuments + }, + }, + { + id: 'node1', + size: [50, 100], + type: 'ellipse', + // ... // Other properties for this node + style: { + // ... // Style properties for this node. Different styles for different types of nodes can be refered to the subdocuments + }, + }, + // ... // Other nodes + ], + edges: [ + // ... // edges + ], +}; +``` + +### Configure with graph.node(nodeFn) + +By this way, we can configure different nodes with different properties. + +
⚠️Attention: + +- `graph.node(nodeFn)` must be called **before calling render()**. It does not take effect otherwise; +- It has the highest priority that will override the same properties configured by other ways; +- Each node will be updated when adding or updating items. It will cost a lot when the amount of the data is large. + +```javascript +// const data = ... +// const graph = ... +graph.node((node) => { + return { + id: node.id, + type: 'rect', + style: { + fill: 'blue', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +## Example + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + type: 'circle', + label: 'circle', + }, + { + x: 200, + y: 100, + type: 'rect', + label: 'rect', + }, + { + id: 'node-ellipse', + x: 330, + y: 100, + type: 'ellipse', + label: 'ellipse', + }, + { + id: 'node-diamond', + x: 460, + y: 100, + type: 'diamond', + label: 'diamond', + }, + { + id: 'node-triangle', + x: 560, + y: 100, + //size: 80, + type: 'triangle', + label: 'triangle', + }, + { + id: 'node-star', + x: 660, + y: 100, + //size: [60, 30], + type: 'star', + label: 'star', + }, + { + x: 760, + y: 100, + size: 50, + type: 'image', + img: 'https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg', + label: 'image', + }, + { + id: 'node-modelRect', + x: 900, + y: 100, + type: 'modelRect', + label: 'modelRect', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'mountNode', + width: 1500, + height: 300, +}); +graph.data(data); +graph.render(); +``` + +The result:
img + +- The label of the triangle and image node are layed on the bottom, and the others are layed on the center by default. + +### Adjust the Properties + +By writing the properties into the data, we adjust the label position, color, and styles of the node with `'node-ellipse'` as its id. Replace the following code to the code about `'node-ellipse'`'s data to obtain the result. + +```javascript +{ + id: 'node-ellipse', + x: 330, + y: 100, + type: 'ellipse', + size: [60, 30], + label: 'ellipse', + labelCfg: { + position: 'bottom', + offset: 5 + }, + style: { + fill: '#fa8c16', + stroke: '#000', + lineWidth: 2 + } +} +``` + +img + +Then, we add some description for the node with `'node-modelRect'` as its `id`: + +``` +{ + id: 'node-modelRect', + x: 900, + y: 100, + description: '描述文本xxxxxxxxxxx', + type: 'modelRect', + label: 'modelRect' +} +``` + +img + +## Related Reading + +- [State](/en/docs/manual/middle/states/state) —— Change the styles during the interaction process. diff --git a/packages/site/docs/manual/middle/elements/nodes/defaultNode.zh.md b/packages/site/docs/manual/middle/elements/nodes/defaultNode.zh.md new file mode 100644 index 0000000000..e11d973138 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/defaultNode.zh.md @@ -0,0 +1,331 @@ +--- +title: 节点总览 +order: 0 +--- + +G6 的内置节点包括 circle,rect,ellipse,diamond,triangle,star,image,modelRect,donut(v4.2.5 起支持)。这些内置节点的默认样式分别如下图所示。
img +img + +本文将概述 G6 中的各个内置节点类型、内置节点的通用属性、配置方法。各类型节点详细配置项及配置方法见本目录下相应文档。 + +## 内置节点类型说明 + +下面表格中显示了内置的各类节点,同时对一些特殊的字段进行了说明: + +| 名称 | 描述 | 默认示例 | +| --- | --- | --- | +| circle | 圆形:
- `size` 是单个数字,表示直径
- 圆心位置对应节点的位置
- `color` 字段默认在描边上生效
- 标签文本默认在节点中央
- 更多字段见 [Circle](/zh/docs/manual/middle/elements/nodes/built-in/circle) 节点教程
| img | +| rect | 矩形:
- `size` 是数组,例如:[100, 50]
- 矩形的中心位置是节点的位置,而不是左上角
- `color` 字段默认在描边上生效
- 标签文本默认在节点中央
- 更多字段见 [Rect](/zh/docs/manual/middle/elements/nodes/built-in/rect) 节点教程
| img | +| ellipse | 椭圆:
- `size` 是数组,表示椭圆的长轴直径和短轴直径
- 椭圆的圆心是节点的位置
- `color` 字段默认在描边上生效
- 标签文本默认在节点中央
- 更多字段见 [Ellipse](/zh/docs/manual/middle/elements/nodes/built-in/ellipse) 节点教程
| img | +| diamond | 菱形:
- `size` 是数组,表示菱形的宽和高
- 菱形的中心位置是节点的位置
- `color` 字段默认在描边上生效
- 标签文本默认在节点中央
- 更多字段见 [Diamond](/zh/docs/manual/middle/elements/nodes/built-in/diamond) 节点教程
| img | +| triangle | 三角形:
- `size` 是数组,表示三角形的底和高
- 三角形的中心位置是节点的位置
- `color` 字段默认在描边上生效
- 标签文本默认在节点下方
- 更多字段见 [Triangle](/zh/docs/manual/middle/elements/nodes/built-in/triangle) 节点教程
| img | +| star | 星形:
- `size` 是单个数字,表示星形的大小
- 星星的中心位置是节点的位置
- `color` 字段默认在描边上生效
- 标签文本默认在节点中央
- 更多字段见 [Star](/zh/docs/manual/middle/elements/nodes/built-in/star) 节点教程
| img | +| image | 图片:
- `size` 是数组,表示图片的宽和高
- 图片的中心位置是节点位置
- `img` 图片的路径,也可以在 `style` 里面设置
- `color` 字段不生效
- 标签文本默认在节点下方
- 更多字段见 [Image](/zh/docs/manual/middle/elements/nodes/built-in/image) 节点教程
| img | +| modelRect | 卡片:
- `size` 是数组,表示卡片的宽和高
- 卡片的中心位置是节点的位置
- `color` 字段默认在描边上生效
- 标签文本默认在节点中央
- 若有  `description` 字段则显示在标签文本下方显示  `description` 内容
- 更多字段见 [ModelRect](/zh/docs/manual/middle/elements/nodes/built-in/modelRect) 节点教程
| img
 img | +| donut | 圆形:
- `size` 是单个数字,表示直径
- 圆心位置对应节点的位置
- `color` 字段默认在描边上生效
- 标签文本默认在节点中央
- 必须指定合法的 `donutAttrs` 字段
- 更多字段见 [Donut](/zh/docs/manual/middle/elements/nodes/built-in/donut) 节点教程
| img | + +## 节点的通用属性 + +所有内置的节点支持的通用属性: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| id | true | String | 节点唯一 ID,**必须**是唯一的 string | +| x | false | Number | x 坐标 | +| y | false | Number | y 坐标 | +| type | false | String | 指定节点类型,内置节点类型名称或自定义节点的名称。默认为 `'circle'` | +| size | false | Number / Array | 节点的大小 | +| anchorPoints | false | Array | 指定边连入节点的连接点的位置(相对于该节点而言),可以为空。例如: `[0, 0]`,代表节点左上角的锚点,`[1, 1]`,代表节点右下角的锚点 | +| style | false | Object | 节点的样式属性。 | +| label | false | String | 文本文字 | +| labelCfg | false | Object | 文本配置项 | + +### 样式属性 style + +Object 类型。通过 `style` 配置来修改节点的填充色、边框颜色、阴影等属性。下表是 `style` 对象中常用的配置项: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| fill | false | String | 节点填充色 | +| stroke | false | String | 节点的描边颜色 | +| lineWidth | false | Number | 描边宽度 | +| lineDash | false | Number[] | 描边虚线,数组代表实、虚长度 | +| shadowColor | false | String | 阴影颜色 | +| shadowBlur | false | Number | 阴影范围 | +| shadowOffsetX | false | Number | 阴影 x 方向偏移量 | +| shadowOffsetY | false | Number | 阴影 y 方向偏移量 | +| opacity | false | Number | 设置绘图的当前 alpha 或透明值 | +| fillOpacity | false | Number | 设置填充的 alpha 或透明值 | +| cursor | false | String | 鼠标在该节点上时的鼠标样式,[CSS 的 cursor](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) 选项都支持 | + +下面代码演示在实例化图时全局配置方法中配置 `style`: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // ... 其他属性 + style: { + fill: '#steelblue', + stroke: '#eaff8f', + lineWidth: 5, + // ... 其他属性 + }, + }, +}); +``` + +### 标签文本 label 及其配置  labelCfg + +`label` String 类型。标签文本的文字内容。
`labelCfg` Object 类型。配置标签文本。下面是 `labelCfg` 对象中的常用配置项: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| position | false | String | 文本相对于节点的位置,目前支持的位置有:`'center'`,`'top'`,`'left'`,`'right'`,`'bottom'`。默认为 `'center'`。modelRect 节点不支持该属性 | +| offset | false | Number | 文本的偏移,`position` 为 `'bottom'` 时,文本的上方偏移量;`position` 为 `'left'` 时,文本的右方偏移量;以此类推在其他 `position` 时的情况。modelRect 节点的 `offset` 为左边距 | +| style | false | Object | 标签的样式属性。 | + +上表中的标签的样式属性 `style` 的常用配置项如下: + +| 名称 | 是否必须 | 类型 | 备注 | +| --- | --- | --- | --- | +| fill | false | String | 文本颜色 | +| stroke | false | String | 文本描边颜色 | +| lineWidth | false | Number | 文本描边粗细 | +| opacity | false | Number | 文本透明度 | +| fontFamily | false | String | 文本字体 | +| fontSize | false | Number | 文本字体大小 | +| ... 节点标签与边标签样式属性相同,统一整理在 [Text 图形 API](/zh/docs/api/shapeProperties/#文本-text) | | | | + +下面代码演示在实例化图时全局配置方法中配置  `label` 和  `labelCfg`。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + // ... 其他属性 + label: 'node-label', + labelCfg: { + position: 'bottom', + offset: 10, + style: { + fill: '#666', + }, + }, + }, +}); +``` + +## 节点的配置方法 + +配置节点的方式有三种:实例化图时全局配置,在数据中动态配置,使用 `graph.node(nodeFn)` 函数配置。这几种配置方法可以同时使用,优先级: + +使用 `graph.node(nodeFn)` 配置 > 数据中动态配置 > 实例化图时全局配置 + +即有相同的配置项时,优先级高的方式将会覆盖优先级低的。 + +⚠️ 注意: 除 `id`、`label` 应当配置到每个节点数据中外,其余的 [节点的通用属性](#节点的通用属性) 以及各个节点类型的特有属性(见内置节点类型)均支持这三种配置方式。 + +### 实例化图时全局配置 + +用户在实例化 Graph 时候可以通过 `defaultNode` 配置节点,这里的配置是全局的配置,将会在所有节点上生效。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'circle', + // 其他配置 + }, +}); +``` + +### 在数据中动态配置 + +如果需要为不同节点进行不同的配置,可以将配置写入到节点数据中。这种配置方式可以通过下面代码的形式直接写入数据,也可以通过遍历数据的方式写入。 + +```javascript +const data = { + nodes: [{ + id: 'node0', + size: 100, + type: 'rect', + ... // 其他属性 + style: { + ... // 样式属性,每种节点的详细样式属性参见各节点文档 + } + },{ + id: 'node1', + size: [50, 100], + type: 'ellipse', + ... // 其他属性 + style: { + ... // 样式属性,每种节点的详细样式属性参见各节点文档 + } + }, + ... // 其他节点 + ], + edges: [ + ... // 边 + ] +} +``` + +### 使用 graph.node() + +该方法可以为不同节点进行不同的配置。 + +
**提示:** + +- 该方法必须**在 render 之前调用**,否则不起作用; +- 由于该方法优先级最高,将覆盖其他地方对节点的配置,这可能将造成一些其他配置不生效的疑惑; +- 该方法在增加元素、更新元素时会被调用,如果数据量大、每个节点上需要更新的内容多时,可能会有性能问题。 + +```javascript +// const data = ... +// const graph = ... +graph.node((node) => { + return { + id: node.id, + type: 'rect', + style: { + fill: 'blue', + }, + }; +}); + +graph.data(data); +graph.render(); +``` + +## 示例 + +```javascript +const data = { + nodes: [ + { + id: 'node_circle', + x: 100, + y: 100, + type: 'circle', + label: 'circle', + }, + { + id: 'node_rect', + x: 200, + y: 100, + type: 'rect', + label: 'rect', + }, + { + id: 'node-ellipse', + x: 330, + y: 100, + type: 'ellipse', + label: 'ellipse', + }, + { + id: 'node-diamond', + x: 460, + y: 100, + type: 'diamond', + label: 'diamond', + }, + { + id: 'node-triangle', + x: 560, + y: 100, + //size: 80, + type: 'triangle', + label: 'triangle', + }, + { + id: 'node-star', + x: 660, + y: 100, + //size: [60, 30], + type: 'star', + label: 'star', + }, + { + id: 'node-image', + x: 760, + y: 100, + size: 50, + type: 'image', + img: 'https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg', + label: 'image', + }, + { + id: 'node-modelRect', + x: 900, + y: 100, + type: 'modelRect', + label: 'modelRect', + }, + ], +}; + +const graph = new G6.Graph({ + container: 'mountNode', + width: 1500, + height: 300, +}); +graph.data(data); +graph.render(); +``` + +显示结果:
img + +- triangle 节点和 image 节点的标签文本默认位置为:`position:'bottom'` ,其他节点文本的默认位置都为:`position: 'center'`; + +### 调整节点配置 + +下面演示通过将配置写入数据的方式,调整 `id` 为 `'node-ellipse'` 的椭圆节点的文本位置,颜色和样式。将下面代码替换上面代码中 `id` 为  `'node-ellipse'` 的节点数据即可生效。 + +``` +{ + id: 'node-ellipse', + x: 330, + y: 100, + type: 'ellipse', + size: [60, 30], + label: 'ellipse', + labelCfg: { + position: 'bottom', + offset: 5 + }, + style: { + fill: '#fa8c16', + stroke: '#000', + lineWidth: 2 + } +} +``` + +img + +再为  `id` 为 `'node-modelRect'` 的 modelRect 节点添加描述文字,使用下面代码替换  `id` 为  `'node-modelRect'` 的节点数据即可得到带有内容为 '描述文本 xxxxxxxxxxx' 的 modelRect 节点。 + +``` +{ + id: 'node-modelRect', + x: 900, + y: 100, + description: '描述文本xxxxxxxxxxx', + type: 'modelRect', + label: 'modelRect' +} +``` + +img + +## 相关阅读 + +- [状态 State](/zh/docs/manual/middle/states/state) —— 交互过程中的样式变化。 diff --git a/packages/site/docs/manual/middle/elements/nodes/jsx-node.en.md b/packages/site/docs/manual/middle/elements/nodes/jsx-node.en.md new file mode 100644 index 0000000000..3169f3c0b2 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/jsx-node.en.md @@ -0,0 +1,150 @@ +--- +title: Use JSX-like syntax to customize G6 nodes +order: 4 +--- + +In G6 V3.7.0 and later version, user are allow to use JSX-like syntax to customize the node by assigning the second parameter of G6.registerNode a string or function that returns a string. + +#### Basic Grammar + +``` +<[group|shape] [key]="value" style={{ [key]: value }}> + <[more tag] /> ... + value + +``` + +The basic syntax is almost the same as the familiar HTML markup language, where you can use shape or group by a tag. At the same time, you need to assign the attributes for defining a shape. Style attributes are grouped to an object, whose items' value can be `string`, `number`, and others supported by JSON (note that it cannot be a function here, which will cause parsing errors). + +Reference for the type and style of custom nodes: https://g6.antv.antgroup.com/api/shapeProperties Among them, for relative positioning, we newly added **marginTop** and **marginLeft** to define the gap between the left and top. + +#### Recommended Usage + +- Wrap the group tag on the outermost layer +- Use single quotes +- Use the template syntax of \${} +- Use `marginTop` and `marginLeft` for relative position graphics +- Using `next: inline` on last shape let next shape follow on the right + +#### Supported tags + +When using JSX-like syntax to customize G6 nodes, the following tags are supported: + +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` + +Use tags to customize nodes. All style attributes are written in style. Name, keyShape, etc. are at the same level as style, and the supported attributes are exactly the same as those in addShape. + +**Special Note**: When using JSX-like grammar to customize a G6 node, the attributes in style do not support function. That is, the marker tag is currently not supported. + +#### Case + +Using JSX-like syntax to customize a simple rectangle. + +```javascript +G6.registerNode( + 'rect-xml', + (cfg) => ` + + ${cfg.label || cfg.id} + + + + +`, +); +``` + + + +Using JSX-like syntax to customize a complicated node. + +```javascript +// Propose the data for a node as following: +const data = { + nodes: [ + { + id: 'node1', + type: 'xml-card', // the custom node's type name + metric: 'CPU usage', + cpuUsage: 80 + }, + ] +} + +// def for the drawing of the percentage bar +const percentageBar = ({ width, used, height = 12 }) => ` + + + +`; + +// def for the drawing of the jsx node +const textXML = (cfg) => ` + + + ${cfg.id} + + + FULL + ${cfg.metric}: + ${cfg.cpuUsage}% + ${percentageBar({ width: 80, used: cfg.cpuUsage })} + + +`; + +// register the custom node to G6 +G6.registerNode('test', { + jsx: textXML, +}); +``` + +Results: + + diff --git a/packages/site/docs/manual/middle/elements/nodes/jsx-node.zh.md b/packages/site/docs/manual/middle/elements/nodes/jsx-node.zh.md new file mode 100644 index 0000000000..35c139f52d --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/jsx-node.zh.md @@ -0,0 +1,150 @@ +--- +title: 使用类 JSX 语法定义 G6 节点 +order: 4 +--- + +在 G6 3.7.0 及以后的版本中,用户以使用类似 JSX 的语法来定义节点。只需要在使用 G6.registerNode 自定义节点时,将第二个参数设置为字符串或一个返回值为 `string` 的 `function`。 + +#### 基础语法 + +``` +<[group|shape] [key]="value" style={{ [key]: value }}> + <[more tag] /> ... + value + +``` + +基础语法和大家熟悉的 HTML 标记语言基本相同,通过标签名来使用 shape 或者 group,同时定义 shape 需要填写 shape 的各个 attributes,而定义形状样式的 attrs 则由 style 属性来进行表达。style 里面的结构是一个 Object,对象的值可以是字符串,数字等 JSON 支持的数据类型(注意,这里不能够是函数,函数只会导致解析错误)。 + +自定义节点的类型和 style 参考:https://g6.antv.antgroup.com/api/shapeProperties 其中,为了相对定位,我们新加入了 marginTop 和 marginLeft 来定义左边和上面的间隔。 + +#### 推荐用法 + +- 在最外层包裹 `group` 标签,保证节点里面图形树结构完整 +- 字符串最好使用单引号包裹,以免遇到解析错误 +- `style` 中随 node 变化的变量推荐使用 \${} 的模板语法加入 +- 图形内的相对定位推荐使用 `marginTop` 和 `marginLeft` 进行设置,`x` 与 `y` 会破坏层级关系定位 +- 如果涉及到需要横向排列的元素,在上一个元素使用`next: inline`来实现下一个元素跟随在上个元素后方 + +#### 支持的标签 + +使用类 JSX 语法来定义 G6 节点时,支持使用以下的标签: + +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` +- `` + +使用标签的形式来定义节点,所有的样式属性都写到 style 里面,name、keyShape 等和 style 同级,所支持的属性和 addShape 中完全一致。 + +**特别说明**:使用类 HTML 语法定义节点时,style 里面属性不支持 function,因此使用类 HTML 语法定义节点时,目前不支持 marker 标签。 + +#### 案例 + +我们先来看一下,使用类 JSX 语法来定义一个简单的矩形。 + +```javascript +G6.registerNode( + 'rect-xml', + (cfg) => ` + + ${cfg.label || cfg.id} + + + + +`, +); +``` + + + +我们再来看一个稍微复杂的案例。 + +```javascript +// 假设一个节点数据如下: +const data = { + nodes: [ + { + id: 'node1', + type: 'xml-card', // 使用自定义的节点名称 + metric: 'CPU usage', + cpuUsage: 80 + }, + ] +} + +// 定义进度条的绘制方式 +const percentageBar = ({ width, used, height = 12 }) => ` + + + +`; + +// 定义节点的 jsx 绘制方式 +const textXML = (cfg) => ` + + + ${cfg.id} + + + FULL + ${cfg.metric}: + ${cfg.cpuUsage}% + ${percentageBar({ width: 80, used: cfg.cpuUsage })} + + +`; + +// 注册节点 +G6.registerNode('xml-card', { + jsx: textXML, +}); +``` + +效果如下图所示: + + diff --git a/packages/site/docs/manual/middle/elements/nodes/react-node.en.md b/packages/site/docs/manual/middle/elements/nodes/react-node.en.md new file mode 100644 index 0000000000..8a97add31d --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/react-node.en.md @@ -0,0 +1,177 @@ +--- +title: How to custom node with React Component +order: 5 +--- + +Customing nodes has long been a problem, and even with the introduction of the jsx solution to simplify it, it's still difficult, so we've introduced `@antv/g6-react-node`, a package that makes it easier to define nodes. event animations, etc., to make it easier to use G6. + +### How to use + +First of all, after installing G6, you need to additionally install `@antv/g6-react-node` + +```bash +npm install @antv/g6-react-node +// yarn add @antv/g6-react-node +``` + +As an example of a simple card with definitions, custom events, node data management, etc.. + +```jsx +import React from 'react'; +import G6 from '@antv/g6'; +import { Rect, Text, Circle, Image, Group, createNodeFromReact } from '@antv/g6-react-node'; + +const Tag = ({ text, color }) => ( + + {text} + +); + +const Card = ({ cfg }) => { + const { collapsed = false } = cfg; + + return ( + + + + This is a card + + + I'm loooooooooooooooooooooooooooooooooog + + {collapsed && ( + + + + + + + + + + )} + + { + graph.updateItem(node, { + collapsed: !collapsed, + }); + }} + > + {collapsed ? '-' : '+'} + + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(Card)); +``` + +It results in this + +card + + +### Using Guide + +#### Shape React Component + +When defining React component nodes, you cannot use any hook or asynchronous fetching logic as node drawing currently needs to be a synchronous process, and it is recommended to put all state as well as data information in the node itself DATA for easier management. In a React component node, all data flow should be: node data -> react component props(cfg) -> node content changes. The component itself needs to be free of any side effects and all changes to the node data should be based on updateItem. + + +#### React inner layouts + +If you don't do any positioning or layout, all layouts will follow the normal document flow, top-down. To give you more freedom of layout, React also has internal support for flex layouts, which you can use by manipulating: `alignContent`,`alignItems`,`alignSelf`,`display`,`flex`,`flexBasis`,`flexGrow`,` flexShrink`, `flexDirection`, `flexWrap`, `height`, `width`, `justifyContent`, `margin`, `padding`, `maxHeight`, `maxWidth`, `minHeight`, ` minWidth` which control the internal layout of the node. + + +#### Event handling based on the React component Shape + +To make it easier to control nodes, we support event binding to a graph inside a node (event bubbling will be supported in a later version), these event binding functions have uniform parameters: `(evt: the event of G6 itself, node: the node where the event occurs, shape: the Shape where the event occurs, graph: the graph where the event is emitted graph)`, we currently support most of the G6 events: `onClick`, `onDBClick`, `onMouseEnter`, `onMouseMove`, `onMouseOut`, `onMouseOver`, `onMouseLeave`, ` onMouseDown `,`onMouseUp `,`onDragStart `,`onDrag `,`onDragEnd `,`onDragEnter `,`onDragLeave `,`onDragOver `,`onDrop `,`onContextMenu ` + +⚠️ Note: After using the event, you need to mount the event on the performed pair of graphs using the function `appenAutoShapeListener(graph)`, which can be derived directly from the `@antv/g6-react-node` package. + +#### Simple animations based on React component Shape (alpha) + +In order to make it easier to add animations to nodes, we have built in some simple animations to use, hopefully to satisfy the effects of basic interaction. In the first phase we have only introduced six animations for now, the `animation` property is animated when it is set, and the property stops animating when it is empty. + +For Example: + +```jsx + +``` + +animate-show + +### More Help + +[G6 React Node Docs](https://dicegraph.github.io/g6-react-node/) + + + + + + diff --git a/packages/site/docs/manual/middle/elements/nodes/react-node.zh.md b/packages/site/docs/manual/middle/elements/nodes/react-node.zh.md new file mode 100644 index 0000000000..d486d59326 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/nodes/react-node.zh.md @@ -0,0 +1,178 @@ +--- +title: 使用 React 定义节点 +order: 5 +--- + +长期以来,定义节点一直是一个比较让大家烦恼的问题,即使推出了 jsx 方案来简化,也依然有一定难度,于是我们推出了 `@antv/g6-react-node` 这一个包,让大家可以更简单的定义节点,这个包支持 ts 提示,并且包含了高阶的基于 shape 的事件动画等,让大家可以更方便的使用 G6。 + +### 怎么使用 + +首先在安装完 G6 后,你需要额外安装 `@antv/g6-react-node` + +```bash +npm install @antv/g6-react-node +// yarn add @antv/g6-react-node +``` + +以一个简单的卡片为例子,它包含了定义,自定义事件,节点数据管理等的示例: + +```jsx +import React from 'react'; +import G6 from '@antv/g6'; +import { Rect, Text, Circle, Image, Group, createNodeFromReact } from '@antv/g6-react-node'; + +const Tag = ({ text, color }) => ( + + {text} + +); + +const Card = ({ cfg }) => { + const { collapsed = false } = cfg; + + return ( + + + + 这是一个卡片 + + + 我是一段特别特别特别特别特别特别特别长的描述 + + {collapsed && ( + + + + + + + + + + )} + + { + graph.updateItem(node, { + collapsed: !collapsed, + }); + }} + > + {collapsed ? '-' : '+'} + + + + + ); +}; + +G6.registerNode('test', createNodeFromReact(Card)); +``` + +展示了这样一个卡片的节点: + +graph + + +### 使用指南 + +#### 图形 React 组件 + +定义 React 组件节点的时候,你不能使用任何的 hook 或者异步获取的逻辑,因为目前节点绘制需要是一个同步的过程,并且,我们推荐把所有状态以及数据信息放在节点本身 data 中,这样可以更方便的进行管理。在React组件节点中,所有的数据流动都应该是:节点数据 -> react 组件 props(cfg) -> 节点内容变化。组件本身需要是没有任何副作用的,所有对于节点数据的改变,都是基于 updateItem 的。 + +#### React 组件内部的布局 + +如果你没有做任何定位或者布局,所有布局都会按照正常的文档流,自上而下排布。为了让大家有更自由的布局方式, React 内部还支持了 flex 布局,你可以通过操作:`alignContent`,`alignItems`,`alignSelf`,`display`,`flex`,`flexBasis`,`flexGrow`,`flexShrink`,`flexDirection`,`flexWrap`,`height`,`width`,`justifyContent`,`margin`,`padding`,`maxHeight`,`maxWidth`,`minHeight`,`minWidth` 这几个属性来控制节点内部的布局。 + +#### 基于 React 组件 Shape 的事件处理 + +为了更加方便的控制节点,我们支持了在节点内部的某一个图形进行事件绑定(事件冒泡会在后续版本支持),这些事件绑定函数都有统一的参数: `(evt: G6本身的事件, node: 事件发生的节点, shape: 事件发生的Shape, graph: 发出事件的graph)`,目前我们支持了大部分的 G6 事件:`onClick`,`onDBClick `,`onMouseEnter`,`onMouseMove `,`onMouseOut`,`onMouseOver `,`onMouseLeave`,`onMouseDown `,`onMouseUp `,`onDragStart `,`onDrag`,`onDragEnd `,`onDragEnter `,`onDragLeave `,`onDragOver`,`onDrop`,`onContextMenu` + +⚠️ 注意: 使用了事件后,需要使用函数 `appenAutoShapeListener(graph)` 对所进行对图进行事件挂载才可以生效,该方法可以直接从 `@antv/g6-react-node` 包引出。 + +#### 基于 React 组件 Shape 的简单动画(alpha) + +为了更加方便给节点添加动画,所以我们内置了一些简单的动画来使用,希望能满足基本交互的效果,第一期我们暂时只推出了六种动画, `animation` 属性设置后就有动画,属性为空则停止动画。 + +示例: + +```jsx + +``` + +animate show + +### 更多帮助 + +[「如何用React在G6里面优雅的定制节点」](https://www.yuque.com/docs/share/e1cb2776-ed13-45bb-8172-69b1d3db2fc2?#) + + +[G6 React Node Docs](https://dicegraph.github.io/g6-react-node/) + + + + + + diff --git a/packages/site/docs/manual/middle/elements/overview.en.md b/packages/site/docs/manual/middle/elements/overview.en.md new file mode 100644 index 0000000000..61ca55cad6 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/overview.en.md @@ -0,0 +1,15 @@ +--- +title: Item Overview +order: 0 +--- + +There are three types of items in a graph in G6: Nodes, Edges and Combos. Each item consists of one or more [Shapes](/en/docs/manual/middle/elements/shape/shape-keyshape) with its own unique keyShape. Several built-in items are provided by G6, for example, nodes can be circles, rectangles, images, etc. All built-in items in are listed in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode), [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge), [Built-in Combos](/en/docs/manual/middle/elements/combos/defaultCombo). In addition to using built-in nodes/edges/combos, G6 also allows user to customize these items by creating and combining shapes, see [Custom Nodes](/en/docs/manual/middle/elements/nodes/custom-node), [Custom Edge](/en/docs/manual/middle/elements/edges/custom-edge), [Custom Combo](/en/docs/manual/middle/elements/combos/custom-combo) for more details. + +The properties of an item can be be divided into two categories: + +- Style Property: Corresponds to the style of the keyshape, e.g. `fill`, `stroke`.When the [State](/en/docs/manual/middle/states/state) of an item is changed, the style can be updated. +- Other Property: Such as `type`, `id`, they are a kind of properties that will not be changed when the State of the item is changed. They need to be updated manually with [graph.updateItem](/en/docs/api/graphFunc/item#graphupdateitemitem-model-stack). A complete list of item properties can be found in [Item Properties](/en/docs/api/Items/itemProperties). In addition to these common properties shared by all items, each kind of item (node/edge/combo) has its unique properties. + +There are [common methods](/en/docs/api/Items/itemMethods) on item instances for updating, destroying, getting attributes, modifying state, etc. And changes to instances can also be made by calling methods on [graph](/en/docs/api/Graph). + +This chapter provides an overview of the common properties and methods of graph items in G6, different types of items, i.e. nodes, edges and combos, will be described in detail in later chapters. diff --git a/packages/site/docs/manual/middle/elements/overview.zh.md b/packages/site/docs/manual/middle/elements/overview.zh.md new file mode 100644 index 0000000000..2d32f6cef7 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/overview.zh.md @@ -0,0 +1,15 @@ +--- +title: 图元素总览 +order: 0 +--- + +图的元素(Item)包含图上的节点 Node 、边 Edge 和 Combo 三大类。每个图元素由一个或多个 [图形(Shape)](/zh/docs/manual/middle/elements/shape/shape-keyshape) 组成,且都会有自己的唯一关键图形(keyShape)。G6 内置了一系列具有不同基本图形样式的节点/边/ Combo,例如,节点可以是圆形、矩形、图片等。G6 中所有内置的元素样式详见 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode),[内置边](/zh/docs/manual/middle/elements/edges/defaultEdge),[内置 Combo](/zh/docs/manual/middle/elements/combos/defaultCombo)。除了使用内置的节点/边/ Combo 外,G6 还允许用户通过自己搭配和组合 shape 进行节点/边/ Combo 的自定义,详见 [自定义节点](/zh/docs/manual/middle/elements/nodes/custom-node),[自定义边](/zh/docs/manual/middle/elements/edges/custom-edge),[自定义 Combo](/zh/docs/manual/middle/elements/combos/custom-combo)。 + +图元素具有公共的通用属性和通用方法。图元素的属性包括: + +- 样式属性,通过 `style` 字段对象进行配置,和元素的关键图形相关,例如 `fill`,`stroke`。可在[元素状态](/zh/docs/manual/middle/states/state)改变时被改变。 +- 其他属性,例如 `id`、`type`,不能在元素状态改变是进行改变,可通过 [graph.updateItem](/zh/docs/api/graphFunc/item#graphupdateitemitem-model-stack) 进行手动更新。完整的元素属性列表参考:[元素配置项](/zh/docs/api/Items/itemProperties)。除了各类元素共有的通用属性外,每种节点/边/ Combo 都有各自的特有属性。 + +图元素实例上具有对元素进行更新、销毁、获取属性、修改状态等[通用方法](/zh/docs/api/Items/itemMethods),同时,对于实例的变更也可以通过调用 [graph](/zh/docs/api/Graph) 上的方法进行。 + +本章对三大类图元素的通用属性和方法进行了概览性介绍,每种图元素(节点/边/ Combo)各自的属性和使用方法将在后面章节中详述。 diff --git a/packages/site/docs/manual/middle/elements/shape/graphics-group.en.md b/packages/site/docs/manual/middle/elements/shape/graphics-group.en.md new file mode 100644 index 0000000000..2a4bc7597b --- /dev/null +++ b/packages/site/docs/manual/middle/elements/shape/graphics-group.en.md @@ -0,0 +1,76 @@ +--- +title: Graphics Group +order: 2 +--- + +   ⚠️Attention:
Graphics Group and [Node Combo](/en/docs/manual/middle/elements/combos/defaultCombo) are totally different concepts with the same name Group. + +- Graphics Group is the group for [Graphics Shape](/en/docs/manual/middle/elements/shape/shape-keyshape); +- [Node Combo](/en/docs/manual/middle/elements/combos/defaultCombo) is the group for [Node](/en/docs/manual/middle/elements/nodes/defaultNode)s, which is related to the hierarchy and groups in the data. + +
+ +## What + +Graphics Group (hereinafter referred to as Group) in G6 is similar to `` tag in SVG : Group a container of a group of graphics. The transformations on a Group such as clipping, rotating, zooming, and translating will be applied to all the children of the Group. The properties like color and position will also be inherited by its children. Besides, Group can be nested for complicated objects. + +In G6, all the nodes instances in a Graph is grouped by a Group named `nodeGroup`, all the edges instances are grouped by `edgeGroup`. And the visual level (zIndex) of `nodeGroup` is higher than `edgeGroup`, which means all the nodes will be drawed on the top of all the edges. + +
As shown in the figure below: The three nodes in (Left) are belong to the `nodeGroup`, the two edges are belong to the `edgeGroup`. The visual level (zIndex) of `nodeGroup` is higher than `edgeGroup`, so the three nodes are drawed on the top of the two edges. We reduce the opacity of the nodes in (Right) to clearly see the edges are drawed under the nodes.
+ +![image.png](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*oqKUSoRWMrcAAAAAAAAAAABkARQnAQ)![image.png](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*cudnTqD-g_4AAAAAAAAAAABkARQnAQ) + +> (Left) Demonstration of the graphics Group of nodes and edges. (Right) Nodes with opacity. + +## When + +Graphics Group is refered by [Custom Node](/en/docs/manual/middle/elements/nodes/custom-node) and [Custom Edge](/en/docs/manual/middle/elements/edges/custom-edge). It is a mechanism to combine and manage the graphis shapes. + +
For example, there is a node A which has a group contains all the graphics shapes (a circle and a text shape) of A. Node B is a custom node which also has a group contains all the graphics shapes (a circle, a rect, and a text shape) of B.
+ +img +img + +
+ +## How + +The functions below will be used in [Custom Node](/en/docs/manual/middle/elements/nodes/custom-node) and [Custom Edge](/en/docs/manual/middle/elements/edges/custom-edge). + +### Get group of item + +```javascript +// Find the graphics group of the item +const group = item.getContainer(); + +// equal to +const group = item.get('group'); +``` + +### Functions of Group + +- addGroup(cfgs) + +Add a new group to the group. + +```javascript +const subGroup = group.addGroup({ + id: 'rect', +}); +``` + +- addShape(type, cfgs) + +Add a shape to the group. + +```javascript +const keyShape = group.addShape('rect', { + attrs: { + stroke: 'red', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', +}); +``` + +**Tips:** The `clip`, `transform`, and other operations on a group will affect all the elements in the group. diff --git a/packages/site/docs/manual/middle/elements/shape/graphics-group.zh.md b/packages/site/docs/manual/middle/elements/shape/graphics-group.zh.md new file mode 100644 index 0000000000..7d51d1ceba --- /dev/null +++ b/packages/site/docs/manual/middle/elements/shape/graphics-group.zh.md @@ -0,0 +1,72 @@ +--- +title: 图形分组 Group +order: 2 +--- + +   ⚠️ 注意:
图形分组 Group 与 [节点分组 Combo](/zh/docs/manual/middle/elements/combos/defaultCombo) 属于不同层次的概念。 + +- 图形分组针对 [图形 Shape](/zh/docs/manual/middle/elements/shape/shape-keyshape) 层次的分组; +- [节点分组 Combo](/zh/docs/manual/middle/elements/combos/defaultCombo)  是针对 [节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 的分组,与数据结构中的层次、分组对应。 + +
+ +## 什么是图形分组 Group + +图形分组 group 类似于 SVG 中的 `` 标签:元素  `g`  是用来组合图形对象的容器。在 group  上添加变换(例如剪裁、旋转、放缩、平移等)会应用到其所有的子元素上。在 group  上添加属性(例如颜色、位置等)会被其所有的子元素继承。此外, group 可以多层嵌套使用,因此可以用来定义复杂的对象。 + +在 G6 中,Graph 的一个实例中的所有节点属于同一个变量名为 `nodeGroup` 的 group,所有的边属于同一个变量名为 `edgeGroup` 的 group。节点 group 在视觉上的层级(zIndex)高于边 group,即所有节点会绘制在所有边的上层。
如下图(左)三个节点属于  `nodeGroup` ,两条边属于 `edgeGroup` , `nodeGroup`  层级高于 `edgeGroup` ,三个节点绘制在两条边的上层。下图(右)是(左)图的节点降低透明度后的效果,可以更清晰看到边绘制在节点下方。
+ +![image.png](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*oqKUSoRWMrcAAAAAAAAAAABkARQnAQ)![image.png](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*cudnTqD-g_4AAAAAAAAAAABkARQnAQ) + +> (左)节点和边的图形分组 Group 演示。(右)给左图的节点降低了透明度。 + +## 何时使用图形分组 Group + +[自定义节点](/zh/docs/manual/middle/elements/nodes/custom-node)、[自定义边](/zh/docs/manual/middle/elements/edges/custom-edge)时将会涉及到图形分组 Group 的概念。图形分组 Group 方便了用户对节点或边上元素的组合和管理。
例如,如下图中的节点 A 有一个包含节点 A 中所有图形的 group,该 group 中包含了一个 circle 图形和一个文本图形。节点 B 是一个自定义节点,有一个包含节点 B 中所有图形的 group,该 group 包含了 circle 图形、rect 图形、文本图形。
+ +img +img + +
+ +## 如何使用图形分组 Group + +图形分组一般会在[自定义节点](/zh/docs/manual/middle/elements/nodes/custom-node)、[自定义边](/zh/docs/manual/middle/elements/edges/custom-edge)时用到。Group 的完整实例方法请参考 [Graphics Group API](/zh/docs/api/Group)。 + +### 获取元素的 group + +```javascript +// 获取元素(节点/边/Combo)的图形对象的容器 +const group = item.getContainer(); + +// 等价于 +const group = item.get('group'); +``` + +### 实例方法 + +- addGroup(cfgs) + +向分组中添加新的分组。 + +```javascript +const subGroup = group.addGroup({ + id: 'rect', +}); +``` + +- addShape(type, cfgs) + +向分组中添加新的图形。 + +```javascript +const keyShape = group.addShape('rect', { + attrs: { + stroke: 'red', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'rect-shape', +}); +``` + +提示:在分组上添加的 `clip`, `transform` 等会影响到该分组中的所有元素(子分组或图形)。 diff --git a/packages/site/docs/manual/middle/elements/shape/shape-and-properties.en.md b/packages/site/docs/manual/middle/elements/shape/shape-and-properties.en.md new file mode 100644 index 0000000000..5e938240bb --- /dev/null +++ b/packages/site/docs/manual/middle/elements/shape/shape-and-properties.en.md @@ -0,0 +1,369 @@ +--- +title: Graphics Shape Properties +order: 1 +--- + +An item (node/edge) in G6 **Consists of One or More** [**Graphics Shape**](/en/docs/manual/middle/elements/shape/shape-keyshape). You can add shapes to a custom item by `group.addShape` in the `draw` function of registering item. The shapes in G6: + +- [circle](#circle); +- [rect](#rect); +- [ellipse](#ellipse); +- [polygon](#polygon); +- [fan](#fan); +- [image](#image); +- [marker](#marker); +- [path](#path); +- [text](#text); +- [dom(svg)](#dom-svg): DOM (available only when the `renderer` of Graph instance is `'svg'`). + +## The Common Properties of Shapes + +| Name | Description | Remark | +| --- | --- | --- | +| fill | The color or gradient color for filling. | The corresponding property in canvas is `fillStyle`. | +| stroke | The color, gradient color, or pattern for stroke. | The corresponding property in canvas is `strokeStyle`. | +| lineWidth | The width of the stroke | | +| lineDash | The lineDash of the stroke | Number[] are the lengths of the lineDash | +| shadowColor | The color for shadow. | | +| shadowBlur | The blur level for shadow. | Larger the value, more blur. | +| shadowOffsetX | The horizontal offset of the shadow. | | +| shadowOffsetY | The vertical offset of the shadow. | | +| opacity | The opacity (alpha value) of the shape. | The corresponding property in canvas is `globalAlpha`. | +| cursor | The type of the mouse when hovering the node. The options are the same as [cursor in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) | | + +### Usage + +```javascript +group.addShape('rect', { + attrs: { + fill: 'red', + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowColor: 'blue', + shadowBlur: 10, + opacity: 0.8, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', +}); +``` + +## The Common Functions of Shapes + +### attr() + +Get or set the shape's attributes. + +### attr(name) + +Get the shape's attribute named `name`. + +```javascript +const width = shape.attr('width'); +``` + +### attr(name, value) + +Update the shape's attribute named `name` with `value`. + +### attr({...}) + +Update the shape's multiple attributes. + +```javascript +rect.attr({ + fill: '#999', + stroke: '#666', +}); +``` + +## Circle + +### Property + +| Name | Description | +| ---- | ------------------------------ | +| x | The x coordinate of the center | +| y | The y coordinate of the center | +| r | The radius | + +### Usage + +```javascript +group.addShape('circle', { + attrs: { + x: 100, + y: 100, + r: 50, + fill: 'blue', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', +}); +``` + +## Rect + +### Property + +| Name | Description | Remark | +| --- | --- | --- | +| x | The x coordinate of the left top | | +| y | The y coordinate of the left top | | +| width | The width of the rect | | +| height | The height of the rect | | +| radius | The border radius. | It can be an integer or an array, representing the border radii of lefttop, righttop, rightbottom, leftbotton respectively.
- `radius: 1` or `radius: [ 1 ]` is equal to `radius: [ 1, 1, 1, 1 ]`
- `radius: [ 1, 2 ]` is equal to `radius: [ 1, 2, 1, 2 ]`
- `radius: [ 1, 2, 3 ]` is equal to `radius: [ 1, 2, 3, 2 ]`
| + +### Usage + +```javascript +group.addShape('rect', { + attrs: { + x: 150, + y: 150, + width: 150, + height: 150, + stroke: 'black', + radius: [2, 4], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', +}); +``` + +## Ellipse + +### Property + +| Name | Description | +| ---- | ------------------------------------ | +| x | The x coordinate of the center | +| y | The y coordinate of the center | +| rx | The horizontal radius of the ellipse | +| ry | The vertical radius of the ellipse | + +### Usage + +```javascript +group.addShape('ellipse', { + attrs: { + x: 100, + y: 100, + rx: 50, + ry: 50, + fill: 'blue', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'ellipse-shape', +}); +``` + +## Polygon + +### Property + +| Name | Description | Remark | +| ------ | --------------------------------------------- | -------------- | +| points | A set of vertexes' coordinates of the polygon | It is an array | + +### Usage + +```javascript +group.addShape('polygon', { + attrs: { + points: [ + [30, 30], + [40, 20], + [30, 50], + [60, 100], + ], + fill: 'red', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'polygon-shape', +}); +``` + +## Image + +### Property + +| Name | Description | Remark | +| --- | --- | --- | +| x | The x coordinate of the left top of the image | | +| y | The y coordinate of the left top of the image | | +| width | The width of the image | | +| height | The height of the image | | +| img | The source of the image | Supports: url, ImageData, Image, and canvas | + +### Usage + +```javascript +group.addShape('image', { + attrs: { + x: 0, + y: 0, + img: 'https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'image-shape', +}); +``` + +## Marker + +### Property + +| Name | Description | Remark | +| --- | --- | --- | +| x | The x coordinate of the center | | +| y | The y coordinate of the center | | +| r | The radius of the marker | | +| symbol | The shape | We built in some commonly used shapes for it: `circle`, `square`, `diamond`, `triangle`, and `triangle-down`. You can customize it by path | + +### Usage + +```javascript +group.addShape('marker', { + attrs: { + x: 10, + y: 10, + r: 10, + symbol: function (x, y, r) { + return [['M', x, y], ['L', x + r, y + r], ['L', x + r * 2, y], ['Z']]; + }, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'marker-shape', +}); +``` + +## Path + +   ⚠️Attention: When the edge is too thin to be hitted by mouse, set **lineAppendWidth** to enlarge the hitting area. + +### Property + +| Name | Description | Remark | +| --- | --- | --- | +| path | The path. | It can be a String, or an Array of path | +| startArrow | The arrow on the start of the path | When `startArrow` is `true`, show a default arrow on the start of the path. User can customize an arrow by path | +| endArrow | The arrow on the end of the path | When `endArrow` is `true`, show a default arrow on the end of the path. User can customize an arrow by path | +| lineAppendWidth | The hitting area of the path | Enlarge the hitting area by enlarging its value | +| lineCap | The style of two ends of the path | | +| lineJoin | The style of the intersection of two path | | +| lineWidth | The line width of the current path | | +| miterLimit | The maximum miter length | | +| lineDash | The style of the dash line | It is an array that describes the length of gaps and line segments. If the number of the elements in the array is odd, the elements will be dulplicated. Such as [5, 15, 25] will be regarded as [5, 15, 25, 5, 15, 25] | + +### Usage + +```javascript +group.addShape('path', { + attrs: { + startArrow: { + // The custom arrow is a path points at (0, 0), and its tail points to the positive direction of x-axis + path: 'M 0,0 L 20,10 L 20,-10 Z', + // the offset of the arrow, nagtive value means the arrow is moved alone the positive direction of x-axis + // d: -10 + }, + endArrow: { + // The custom arrow is a path points at (0, 0), and its tail points to the positive direction of x-axis + path: 'M 0,0 L 20,10 L 20,-10 Z', + // the offset of the arrow, nagtive value means the arrow is moved alone the positive direction of x-axis + // d: -10 + }, + path: [ + ['M', 100, 100], + ['L', 200, 200], + ], + stroke: '#000', + lineWidth: 8, + lineAppendWidth: 5, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', +}); +``` + +## Text + +### Properties + +| Name | Description | Remark | +| --- | --- | --- | +| fill | The color or gradient color for filling. | The corresponding property in Canvas is `fillStyle`. | +| stroke | The color, gradient color, or pattern for stroke. | The corresponding property in Canvas is `strokeStyle`. | +| shadowColor | The color for shadow. | | +| shadowBlur | The blur level for shadow. | Larger the value, more blur. | +| shadowOffsetX | The horizontal offset of the shadow. | | +| shadowOffsetY | The vertical offset of the shadow. | | +| opacity | The opacity (alpha value) of the shape. | The corresponding property in Canvas is `globalAlpha`. | +| textAlign | The align way of the text. | Options: `'center'` / `'end'` / `'left'` / `'right'` / `'start'`. `'start'` by default. | +| textBaseline | The base line of the text. | Options:
`'top'` / `'middle'` / `'bottom'` / `'alphabetic'` / `'hanging'`. `'bottom'` by default. | +| fontStyle | The font style of the text. | The corresponding property in CSS is `font-style` | +| fontVariant | The font variant of the text. | The corresponding property in CSS is `font-variant` | +| fontWeight | The font weight of the text. | The corresponding property in CSS is `font-weight` | +| fontSize | The font size of the text. | The corresponding property in CSS is `font-size` | +| fontFamily | The font family of the text. | The corresponding property in CSS is `font-family` | +| lineHeight | Line height of the text | The corresponding property in CSS is `line-height` | + +### Usage + +```javascript +group.addShape('text', { + attrs: { + text: 'test text', + fill: 'red', + fontWeight: 400, + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowColor: 'blue', + shadowBlur: 10, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', +}); +``` + +## DOM (svg) + +> This shape is available only when the `renderer` is assgined to `'svg'` for graph instance. + +⚠️ Attention: + +- Only support native HTML DOM, but not react or other components; +- If you custom a Node type or an Edge type with dom shape, please use the original DOM events instead of events of G6. +- In Safari, if you assign `position:relative` for the a dom node, the rendered position might be unexpected. It is related to the [foreignObject bug of Safari](https://bugs.webkit.org/show_bug.cgi?id=23113). [Issus](https://github.com/antvis/G6/issues/2990). + + +### Properties + +| Name | Description | Remark | +| ---- | ---------------------------- | ------ | +| html | The html value for DOM shape | | + +### Usage + +```javascript +group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + // DOM's html + html: ` +
+
+ img +
+ ${cfg.label} +
+ `, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'dom-shape', + draggable: true, +}); +``` diff --git a/packages/site/docs/manual/middle/elements/shape/shape-and-properties.zh.md b/packages/site/docs/manual/middle/elements/shape/shape-and-properties.zh.md new file mode 100644 index 0000000000..8014121366 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/shape/shape-and-properties.zh.md @@ -0,0 +1,369 @@ +--- +title: 图形 Shape 及其属性 +order: 1 +--- + +G6 中的元素(节点/边)是**由一个或多个**[**图形 Shape**](/zh/docs/manual/middle/elements/shape/shape-keyshape) **组成**,主要通过自定义节点或自定义边时在 `draw` 方法中使用 `group.addShape` 添加,G6 中支持以下的图形 Shape: + +- [circle](#圆图形-circle):圆; +- [rect](#矩形图形-rect):矩形; +- [ellipse](#椭圆图形-ellipse):椭圆; +- [polygon](#多边形图形-polygon):多边形; +- [fan](#扇形图形-fan):扇形; +- [image](#图片图形-image):图片; +- [marker](#标记图形-marker):标记; +- [path](#路径-path):路径; +- [text](#文本-text):文本; +- [dom(svg)](#dom-svg):DOM(图渲染方式 `renderer` 为 `'svg'` 时可用)。 + +## 各图形 Shape 的通用属性 + +| 属性名 | 含义 | 备注 | +| --- | --- | --- | +| fill | 设置用于填充绘画的颜色、渐变或模式 | 对应 Canvas 属性 `fillStyle` | +| stroke | 设置用于笔触的颜色、渐变或模式 | 对应 Canvas 属性 `strokeStyle` | +| lineWidth | 描边宽度 | | +| lineDash | 描边虚线 | Number[] 类型代表实、虚长度 | +| shadowColor | 设置用于阴影的颜色 | | +| shadowBlur | 设置用于阴影的模糊级别 | 数值越大,越模糊 | +| shadowOffsetX | 设置阴影距形状的水平距离 | | +| shadowOffsetY | 设置阴影距形状的垂直距离 | | +| opacity | 设置绘图的当前 alpha 或透明值 | 对应 Canvas 属性 `globalAlpha` | +| fillOpacity | 设置填充的 alpha 或透明值 | | +| cursor | 鼠标在该节点上时的鼠标样式,[CSS 的 cursor](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) 选项都支持 | | + +### 用法 + +```javascript +group.addShape('rect', { + attrs: { + fill: 'red', + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowColor: 'blue', + shadowBlur: 10, + opacity: 0.8, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'rect-shape', +}); +``` + +## 各图形 Shape 的通用方法 + +### attr() + +设置或获取实例的绘图属性。 + +### attr(name) + +获取实例的属性值。 + +``` +const width = shape.attr('width'); +``` + +### attr(name, value) + +更新实例的单个绘图属性。 + +### attr({...}) + +批量更新实例绘图属性。 + +``` +rect.attr({ + fill: '#999', + stroke: '#666' +}); +``` + +## 圆图形 Circle + +### 属性 + +| 属性名 | 含义 | +| ------ | ------------- | +| x | 圆心的 x 坐标 | +| y | 圆心的 y 坐标 | +| r | 圆的半径 | + +### 用法 + +```javascript +group.addShape('circle', { + attrs: { + x: 100, + y: 100, + r: 50, + fill: 'blue', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'circle-shape', +}); +``` + +## 矩形图形 Rect + +### 属性 + +| 属性名 | 含义 | 备注 | +| --- | --- | --- | +| x | 矩形左上角的 x 坐标 | | +| y | 矩形左上角的 y 坐标 | | +| width | 矩形的宽度 | | +| height | 矩形的高度 | | +| radius | 定义圆角 | 支持整数或数组形式, 分别对应左上、右上、右下、左下角的半径:
- radius 缩写为 1 或 [ 1 ] 相当于 [ 1, 1, 1, 1 ]
- radius 缩写为 [ 1, 2 ] 相当于 [ 1, 2, 1, 2 ]
- radius 缩写为 [ 1, 2, 3 ] 相当于 [ 1, 2, 3, 2 ]
| + +### 用法 + +```javascript +group.addShape('rect', { + attrs: { + x: 150, + y: 150, + width: 150, + height: 150, + stroke: 'black', + radius: [2, 4], + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'rect-shape', +}); +``` + +## 椭圆图形 Ellipse + +### 属性 + +| 属性名 | 含义 | +| ------ | ------------- | +| x | 圆心的 x 坐标 | +| y | 圆心的 y 坐标 | +| rx | 水平半径 | +| ry | 垂直半径 | + +### 用法 + +```javascript +group.addShape('ellipse', { + attrs: { + x: 100, + y: 100, + rx: 50, + ry: 50, + fill: 'blue', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'ellipse-shape', +}); +``` + +## 多边形图形 Polygon + +### 属性 + +| 属性名 | 含义 | 备注 | +| ------ | -------------------- | -------- | +| points | 多边形的所有端点坐标 | 数组形式 | + +### 用法 + +```javascript +group.addShape('polygon', { + attrs: { + points: [ + [30, 30], + [40, 20], + [30, 50], + [60, 100], + ], + fill: 'red', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'polygon-shape', +}); +``` + +## 图片图形 Image + +### 属性 + +| 属性名 | 含义 | 备注 | +| ------ | ------------------- | ---------------------------------------------------- | +| x | 图片左上角的 x 坐标 | | +| y | 图片左上角的 y 坐标 | | +| width | 图片宽度 | | +| height | 图片高度 | | +| img | 图片源 | G6 支持多种格式的图片:url、ImageData、Image、canvas | + +### 用法 + +```javascript +group.addShape('image', { + attrs: { + x: 0, + y: 0, + img: 'https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'image-shape', +}); +``` + +## 标记图形 Marker + +### 属性 + +| 属性名 | 含义 | 备注 | +| --- | --- | --- | +| x | 中心的 x 坐标 | | +| y | 中心的 y 坐标 | | +| r | 形状半径 | | +| symbol | 指定形状 | 内置了一些常用形状,如圆形 `circle` , 矩形  `square` , 菱形  `diamond` ,三角形  `triangle` , 倒三角形 `triangle-down` ,也可以是自定义的 path 路径。 | + +### 用法 + +```javascript +group.addShape('marker', { + attrs: { + x: 10, + y: 10, + r: 10, + symbol: function (x, y, r) { + return [['M', x, y], ['L', x + r, y + r], ['L', x + r * 2, y], ['Z']]; + }, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'marker-shape', +}); +``` + +## 路径 Path + +   ⚠️ 注意: 当边太细交互不易命中时,请设置 `lineAppendWidth` 属性值。 + +### 属性 + +| 属性名 | 含义 | 备注 | +| --- | --- | --- | +| path | 线条路径 | 可以是 String 形式,也可以是线段的数组。 | +| startArrow | 起始端的箭头 | 为 `true` 时在边的结束端绘制默认箭头,为 `false` 时不绘制结束端箭头。也可以是一个通过 path 自定义的箭头 | +| endArrow | 末尾端的箭头 | 为 `true` 时在边的开始端绘制默认箭头,为 `false` 时不绘制开始端箭头。也可以是一个通过 path 自定义的箭头 | +| lineAppendWidth | 边的击中范围 | 提升边的击中范围,扩展响应范围,数值越大,响应范围越广 | +| lineCap | 设置线条的结束端点样式 | | +| lineJoin | 设置两条线相交时,所创建的拐角形状 | | +| lineWidth | 设置当前的线条宽度 | | +| miterLimit | 设置最大斜接长度 | | +| lineDash | 设置线的虚线样式,可以指定一个数组 | 一组描述交替绘制线段和间距(坐标空间单位)长度的数字。 如果数组元素的数量是奇数, 数组的元素会被复制并重复。例如, [5, 15, 25] 会变成 [5, 15, 25, 5, 15, 25]。 | + +### 用法 + +```javascript +group.addShape('path', { + attrs: { + startArrow: { + // 自定义箭头指向(0, 0),尾部朝向 x 轴正方向的 path + path: 'M 0,0 L 20,10 L 20,-10 Z', + // 箭头的偏移量,负值代表向 x 轴正方向移动 + // d: -10, + }, + endArrow: { + // 自定义箭头指向(0, 0),尾部朝向 x 轴正方向的 path + path: 'M 0,0 L 20,10 L 20,-10 Z', + // 箭头的偏移量,负值代表向 x 轴正方向移动 + // d: -10, + }, + path: [ + ['M', 100, 100], + ['L', 200, 200], + ], + stroke: '#000', + lineWidth: 8, + lineAppendWidth: 5, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'path-shape', +}); +``` + +## 文本 Text + +### 属性 + +| 属性名 | 含义 | 备注 | +| --- | --- | --- | +| fill | 设置用于填充绘画的颜色、渐变或模式 | 对应 Canvas 属性 `fillStyle` | +| stroke | 设置用于笔触的颜色、渐变或模式 | 对应 Canvas 属性 `strokeStyle` | +| shadowColor | 设置用于阴影的颜色 | | +| shadowBlur | 设置用于阴影的模糊级别 | 数值越大,越模糊 | +| shadowOffsetX | 设置阴影距形状的水平距离 | | +| shadowOffsetY | 设置阴影距形状的垂直距离 | | +| opacity | 设置绘图的当前 alpha 或透明值 | 对应 Canvas 属性 `globalAlpha` | +| textAlign | 设置文本内容的当前对齐方式 | 支持的属性:`center` / `end` / `left` / `right` / `start`,默认值为 `start` | +| textBaseline | 设置在绘制文本时使用的当前文本基线 | 支持的属性:
`top` / `middle` / `bottom` / `alphabetic` / `hanging`。默认值为 `bottom` | +| fontStyle | 字体样式 | 对应 `font-style` | +| fontVariant | 设置为小型大写字母字体 | 对应 `font-variant` | +| fontWeight | 字体粗细 | 对应 `font-weight` | +| fontSize | 字体大小 | 对应 `font-size` | +| fontFamily | 字体系列 | 对应 `font-family` | +| lineHeight | 行高 | 对应 `line-height` | + +### 用法 + +```javascript +group.addShape('text', { + attrs: { + text: 'test text', + x: 0, + y: 10, + fontSize: 14, + textAlign: 'left', + textBaseline: 'middle', + fill: '#0000D9', + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'text-shape', +}); +``` + +## DOM (svg) + +> 仅在 Graph 的 `renderer` 为 `'svg'` 时可以使用。 + +⚠️ 注意: + +- 只支持原生 HTML DOM,不支持各类 react、vue 组件; +- 使用 `'dom'` 进行自定义的节点或边,不支持 G6 的交互事件,请使用原生 DOM 的交互事件; +- 在 Safari 中,若 dom 节点被设置了 `position:relative`,将会导致渲染异常。该问题与 [Safari 的 foreignObject bug](https://bugs.webkit.org/show_bug.cgi?id=23113) 有关。[Issus](https://github.com/antvis/G6/issues/2990)。 + +### 特殊属性 + +| 属性名 | 含义 | 备注 | +| ------ | -------------- | ---- | +| html | DOM 的 html 值 | | + +### 用法 + +```javascript +group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + // DOM's html + html: ` +
+
+ img +
+ ${cfg.label} +
+ `, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'dom-shape', + draggable: true, +}); +``` diff --git a/packages/site/docs/manual/middle/elements/shape/shape-keyshape.en.md b/packages/site/docs/manual/middle/elements/shape/shape-keyshape.en.md new file mode 100644 index 0000000000..0e9600052f --- /dev/null +++ b/packages/site/docs/manual/middle/elements/shape/shape-keyshape.en.md @@ -0,0 +1,127 @@ +--- +title: Graphics Shape and KeyShape +order: 0 +--- + +## Graphics Shape + +Graphics Shape (hereinafter referred to as Shape) in G6 is the shape of items (nodes/edges/combos), it can be a circle, a rect, path, and so on. **A node / edge / combo is made up of one or several Shapes. The configurations on a node, an edge, or a label will be writed onto corresponding graphics Shape.** + +In the figure(Left) below, there is a node with a circle Shape; (Center) a node with a circle Shape and a text Shape; (right) a node with a text Shape and 5 circle Shapes including the main circle and four anchor points. Each node or edge has only one keyShape. The keyShape of each nodes in the figure below is the green circle. [keyShape](#keyshape) is the Shape that responses interactions and [State](/en/docs/manual/middle/states/state) changing.
img     img      img + +> (Left) A node with one circle Shape, the keyShape is the circle. (Center) A node with a text Shape and the circle Shape, the keyShape is the circle. (Right) A node with a text Shape and five circle Shapes including the main circle and four anchors, the keyShape is the green circle. + +G6 designs abundant built-in nodes / edges / combos by combing different Shapes. Built-in nodes includes 'circle', 'rect', 'ellipse', ...(Refer to [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode)); Built-in edges includes 'line', 'polyline', 'cubic', ... (Refer to [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge)); Built-in combos includes 'circle', 'rect' (Refer to [Built-in Combos](/en/docs/manual/middle/elements/combos/defaultCombo)). + +Besides, G6 allows users to define their own types of item by register a custom node / edge / combo. Refer to [Custom Node](/en/docs/manual/middle/elements/nodes/custom-node), [Custom Edge](/en/docs/manual/middle/elements/edges/custom-edge), and [Custom Combo](/en/docs/manual/middle/elements/combos/custom-combo). + +## KeyShape + +As stated above, there is only one keyShape for each type of item. keyShape is returned by `draw()` of each type of item. It has two main effcts: + +### Response the Style + +The property `style` in built-in nodes / edges / combos of G6 is only reponsed by keyShape. And the way to define the styles for different states (`nodeStateStyles` / `edgeStateStyles` / `comboStateStyles` on graph or `stateStyles` of itself) on keyShape and other shapes are different, refer to [Configure Styles for State](/en/docs/manual/middle/states/state#configure-styles-for-state) . + +To break the rules above and achieve free definations, you can register a type of [Custom Node](/en/docs/manual/middle/elements/nodes/custom-node), [Custom Edge](/en/docs/manual/middle/elements/edges/custom-edge), or [Custom Combo](/en/docs/manual/middle/elements/combos/custom-combo). + +#### Example + +We use the built-in rect node in this example. The keyShape of the node is the rect Shape. There are other shapes including four small circle Shapes around and a text Shape for the label. The code below assigns the `style` for the node. `style` only takes effect on the keyShape. The styles for other Shapes need to be configured by other properties such as `linkPoints` and `labelCfg`. We also listen to the mouse enter and mouse leave events to activate/inactivate the hover state, the responsing styles defined in `nodeStateStyles` only takes effect on keyShape as well. + +keyshape-demo + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + label: 'rect', + type: 'rect', + style: { + // The style for the keyShape + fill: 'lightblue', + stroke: '#888', + lineWidth: 1, + radius: 7, + }, + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + // ... Styles for linkPoints can be assigned here + }, + // labelCfg: {...} // The style for the label con be assigned here + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 300, + nodeStateStyles: { + // The state styles defined as following will take effect on keyShape only. To define state styles on other shapes, refer to the link Configure Styles for State above + hover: { + fillOpacity: 0.1, + lineWidth: 10, + }, + }, +}); +graph.data(data); +graph.render(); +// Listen to the mouse enter event on node +graph.on('node:mouseenter', (evt) => { + const node = evt.item; + // activate the hover state of the node + graph.setItemState(node, 'hover', true); +}); +// Listen to the mouse leave event on node +graph.on('node:mouseleave', (evt) => { + const node = evt.item; + // inactivate the hover state of the node + graph.setItemState(node, 'hover', false); +}); +``` + +### Bounding Box + +KeyShape is used for **defining the Bounding Box —— bbox(x, y, width, height)** of the node / combo to do some transformations and calculate the link points. Different keyShape will lead to different result link points. + +### Example  + +There is a node with a rect Shape and a circle Shape in transparent filling and grey stroke. + +- When the keyShape of the node is the circle: + +img + +- When the keyShape of the node is the rect: + +img + +## The Life Cycle of Shape + +> You can skip this part if you are going to use the built-in items. For the users who have the requirements to [Custom Node](/en/docs/manual/middle/elements/nodes/custom-node), [Custom Edge](/en/docs/manual/middle/elements/edges/custom-edge), you'd better know the life cycle of Shape, and [Custom Combo](/en/docs/manual/middle/elements/combos/custom-combo), you'd better know the life cycle of Shape. + +The life cycle of Shape: + +- Initiate and render; +- Update; +- Manipulate; +- Destroy. + +'Destroy' can be controlled by the Graph. The other three states should be considered: + +- Render: Draw a Shape; +- Update: Update the Shape when the data changed; +- Manipulate: Add some states to the Shape, e.g. selected, active, and so on. + +There are three key functions of custom node and edge which should be overrode according to your requirements: + +- `draw(cfg, group)`: Draw the Shape with configurations and its container. **MUST** return a proper shape as the keyShape; +- `update(cfg, n)`: Update the item according to the configurations and the item; +- `setState(name, value, item)`: Response the states change for items. + +For more information about custom node and edge, refer to [Custom Item API](/en/docs/api/registerItem). diff --git a/packages/site/docs/manual/middle/elements/shape/shape-keyshape.zh.md b/packages/site/docs/manual/middle/elements/shape/shape-keyshape.zh.md new file mode 100644 index 0000000000..108a0a0bad --- /dev/null +++ b/packages/site/docs/manual/middle/elements/shape/shape-keyshape.zh.md @@ -0,0 +1,129 @@ +--- +title: 图形 Shape 与 keyShape +order: 0 +--- + +## 图形 Shape + +Shape 指 G6 中的图形、形状,它可以是圆形、矩形、路径等。它一般与 G6 中的节点、边、Combo 相关。**G6 中的每一种节点/边/ Combo 由一个或多个 Shape 组成。节点、边、Combo、标签文本的配置都会被体现到对应的图形上。** + +例如下图(左)的节点包含了一个圆形图形;下图(中)的节点含有有一个圆形和一个文本图形;下图(右)的节点中含有 5 个圆形(蓝绿色的圆和上下左右四个锚点)、一个文本图形。但每种节点/边/ Combo 都会有自己的唯一关键图形 keyShape,下图中三个节点的 keyShape 都是蓝绿色的圆,keyShape 主要用于交互检测、样式随[状态](/zh/docs/manual/middle/states/state)自动更新等,见  [keyShape](#keyshape)。
img     img      img + +> (左)只含有一个圆形图形的节点,keyShape 是该圆形。(中)含有圆形和文本图形的节点,keyShape 是圆形。(右)含有主要圆形、文本、上下左右四个小圆形的节点,keyShape 是圆形。 + +G6 使用不同的 shape 组合,设计了多种内置的节点/边/ Combo 。G6 内置节点的有 'circle', 'rect','ellipse',...(详见 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode));内置边的有 'line','polyline','cubic',...(详见 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge));内置 Combo 有 'circle','rect',()详见 [内置 Combo](/zh/docs/manual/middle/elements/combos/defaultCombo))。 + +除了使用内置的节点/边/ Combo 外,G6 还允许用户通过自己搭配和组合 shape 进行节点/边/ Combo 的自定义,详见 [自定义节点](/zh/docs/manual/middle/elements/nodes/custom-node),[自定义边](/zh/docs/manual/middle/elements/edges/custom-edge),[自定义 Combo](/zh/docs/manual/middle/elements/combos/custom-combo)。 + +## KeyShape + +如上所述,每一种节点/边/ Combo 都有一个唯一的关键图形 keyShape。keyShape 是在节点/边/ Combo 的 `draw()` 方法或 `drawShape()` 方法中返回的图形对象。它有两个主要特点: + +### 响应样式 + +内置节点/边/ Combo 配置项中的 `style` 只体现在它的 keyShape 上。而内置节点/边/ Combo 的状态样式 (图实例的 `nodeStateStyles` / `edgeStateStyles` / `comboStateStyles` 或元素自身的 `stateStyles`) 中需要体现在 keyShape 或其他图形上的写法有所不同,详见 [配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式)。 + +想要更自由地响应样式(绘制或状态变化时),可以 [自定义节点](/zh/docs/manual/middle/elements/nodes/custom-node) / [自定义边](/zh/docs/manual/middle/elements/edges/custom-edge) / [自定义 Combo](/zh/docs/manual/middle/elements/combos/custom-combo)。 + +#### 示例 + +该示例使用了内置 rect 节点,节点的 keyShape 是中间的 rect,其他 Shape 包括上下左右四个 circle 以及一个 text。代码设置了节点的样式 `style`,仅在 rect 上生效,其他 Shape 都以默认样式渲染。该节点上的其他图形需要使用其他配置项进行配置。例如,上下左右四个 circle 的样式需要在 `linkPoints` 中配置,文本样式需要在 `labelCfg` 中配置。 + +代码中还监听了鼠标的进入节点和离开节点事件,触发了 hover 状态后 `nodeStateStyles` 里 hover 的样式仅在 keyShape 上生效。若需要节点中其他图形响应状态发生样式变化,参考 [配置状态样式](/zh/docs/manual/middle/states/state#配置-state-样式)。 + +keyshape-demo + +```javascript +const data = { + nodes: [ + { + x: 100, + y: 100, + label: 'rect', + type: 'rect', + style: { + // 仅在 keyShape 上生效 + fill: 'lightblue', + stroke: '#888', + lineWidth: 1, + radius: 7, + }, + linkPoints: { + top: true, + bottom: true, + left: true, + right: true, + // ... 四个圆的样式可以在这里指定 + }, + // labelCfg: {...} // 标签的样式可以在这里指定 + }, + ], +}; +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 300, + nodeStateStyles: { + // 各状态下的样式,平铺的配置项仅在 keyShape 上生效。需要在其他 shape 样式上响应状态变化则写法不同,参见上文提到的 配置状态样式 链接 + hover: { + fillOpacity: 0.1, + lineWidth: 10, + }, + }, +}); +graph.data(data); +graph.render(); +// 监听鼠标进入节点事件 +graph.on('node:mouseenter', (evt) => { + const node = evt.item; + // 激活该节点的 hover 状态 + graph.setItemState(node, 'hover', true); +}); +// 监听鼠标离开节点事件 +graph.on('node:mouseleave', (evt) => { + const node = evt.item; + // 关闭该节点的 hover 状态 + graph.setItemState(node, 'hover', false); +}); +``` + +### 包围盒确定 + +**确定节点 / Combo 的包围盒(Bounding Box) —— bbox(x, y, width, height)** ,从而计算相关边的连入点(与相关边的交点)。若 keyShape 不同,节点与边的交点计算结果不同。 + +#### 示例   + +本例中的一个节点由一个 rect 图形和一个带灰色描边、填充透明的 circle 图形构成。 + +- 当节点的 keyShape 为 circle 时: + +img + +- 当节点的 keyShape 为 rect 时: + +img + +## Shape 的生命周期 + +> 当用户需要[自定义节点](/zh/docs/manual/middle/elements/nodes/custom-node)、[自定义边](/zh/docs/manual/middle/elements/edges/custom-edge)、[自定义 Combo](/zh/docs/manual/middle/elements/combos/custom-combo) 时,需要了解 Shape 的生命周期。使用内置节点/边/ Combo 则可以跳过这一部分内容。 + +从整体来看,Shape 的生命周期分为: + +- 初始化渲染; +- 更新; +- 操作; +- 销毁。 + +Shape 作为 Graph 上的核心元素,这几个阶段都需要考虑,但是销毁可以交给 Graph 来处理,所以在定义 Shape 时不需要考虑,仅需要考虑三个阶段即可: + +- 绘制:从无到有的绘制 Shape 及文本; +- 更新:数据发生改变导致 Shape 及文本发生变化; +- 操作:给 Shape 添加状态,如:selected,active 等。 + +所以我们在设计自定义节点/边/ Combo 时,定义了三个方法,若需要自定义节点/边/ Combo ,需要有选择性地复写它们: + +- `draw(cfg, group)`: 绘制,提供了绘制的配置项(数据定义时透传过来)和图形容器,**必须**返回合理的图形作为 keyShape; +- `update(cfg, n)`: 更新,更新时的配置项(更新的字段和原始字段的合并)和元素对象; +- `setState(name, value, item)`: 响应节点/边/ Combo 状态的变化。 + +关于自定义节点和边的更多方法请参考 [自定义节点与边 API](/zh/docs/api/registerItem)。 diff --git a/packages/site/docs/manual/middle/elements/shape/transform.en.md b/packages/site/docs/manual/middle/elements/shape/transform.en.md new file mode 100644 index 0000000000..dff5733b1a --- /dev/null +++ b/packages/site/docs/manual/middle/elements/shape/transform.en.md @@ -0,0 +1,140 @@ +--- +title: Transform a Shape or a Graphics Group +order: 3 +--- + +### G6 3.2 + +In G6 3.2 and previous versions, you can transform a shape as below: + +#### transform(ts) + +Transform a shape with multiple operations. `ts` is the array of the operations, which will be executed in order. + +For example, there is a rect shape: + +```javascript +const rect = group.addShape('rect', { + attrs: { + width: 100, + height: 100, + x: 100, + y: 100, + fill: '#9EC9FF', + stroke: '#5B8FF9', + lineWidth: 3, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', +}); +``` + +img + +Now, we call the transform: + +```javascript +rect.transform([ + ['t', 10, 10], // translate 10 pixels alone x-axis, and 10 pixels alone y-axis + ['s', 0.1, 1.2], // scale 1.2 times + ['r', Math.PI / 4], // rotate 45 degree +]); +``` + +img + +#### translate(x, y) + +Translate the shape or group with vector (x, y). + +#### move(x, y) + +Translate the shape or group with vector (x, y). + +#### rotate(radian) + +Rotate the shape or group with `radian`. + +#### scale(sx, sy) + +Scale the shape or group to sx times on x-axis and sy times on y-axis. + +#### resetMatrix() + +Clear the matrix to reset all the transformantions on the shape or group. + +#### getTotalMatrix() + +Get all the transformations of the shape or group. + +### G6 3.3 + +After G6 3.3, the following transform methods are discarded: + +- 🗑 translate; +- 🗑 move; +- 🗑 scale; +- 🗑 rotate; +- 🗑 rotateAtStart: rotate the shape or group with center (0, 0)。 + +To achive some transformation in G6 3.3, you should set the matrix value manually: + +- Get the current matrix of a shape or a group: getMatrix(); +- Set the matrix to a shape or a group: setMatrix(matrix) or attr('matrix', matrix); +- Reset the matrix: resetMatrix(). + +We provide the function for transformantion: + +```javascript +import { ext } from '@antv/matrix-util'; + +const transform = ext.transform; + +// transform a 3*3 matrix +transform(m, [ + ['t', x, y], // translate with vector (x, y) + ['r', Math.PI], // rotate + ['s', 2, 2], // scale at x-axis and y-axis +]); +``` + +#### Example + +The following code registers a custom node with a transfromed rect with: translation with vector `(100, 50)`, rotating with angle `Math.PI / 4`, magnifying 2 times on x-axis and 0.5 times on y-axis: + +```javascript +import { ext } from '@antv/matrix-util'; + +const transform = ext.transform; + +G6.registerNode('example', { + drawShape: (cfg, group) => { + const rect = group.addShape('rect', { + attrs: { + width: 100, + height: 100, + x: 100, + y: 100, + fill: '#9EC9FF', + stroke: '#5B8FF9', + lineWidth: 3, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', + }); + let matrix = rect.getMatrix(); + + // the init matrix for a shape or a group is null, initiate it with unit matrix + if (!matrix) matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; + + // transform a 3*3 matrix + const newMatrix = transform(matrix, [ + ['t', 100, 50], // translate + ['r', Math.PI / 4], // rotate + ['s', 2, 0.5], // scale + ]); + + rect.setMatrix(newMatrix); + }, +}); +``` diff --git a/packages/site/docs/manual/middle/elements/shape/transform.zh.md b/packages/site/docs/manual/middle/elements/shape/transform.zh.md new file mode 100644 index 0000000000..f714d8aa91 --- /dev/null +++ b/packages/site/docs/manual/middle/elements/shape/transform.zh.md @@ -0,0 +1,141 @@ +--- +title: 图形或图形分组的变换 +order: 3 +--- + +### G6 3.2 + +G6 3.2 及以下版本中,实现变换可通过以下方式。 + +#### transform(ts) + +实例变换方法。参数以数组形式提供,按顺序执行。 + +例如画布上有以下的一个矩形实例。 + +```javascript +const rect = group.addShape('rect', { + attrs: { + width: 100, + height: 100, + x: 100, + y: 100, + fill: '#9EC9FF', + stroke: '#5B8FF9', + lineWidth: 3, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'rect-shape', +}); +``` + +得到的结果如下图所示: img + +对其进行如下操作: + +```javascript +rect.transform([ + ['t', 10, 10], // x 方向平移 10, y 方向平移 10 + ['s', 1.2], // 缩放 1.2 倍 + ['r', Math.PI / 4], // 旋转 45 度 +]); +``` + + + +#### translate(x, y) + +实例的相对位移方法。 + +#### move(x, y) + +实例的相对位移方法。 + +#### rotate(radian) + +根据旋转弧度值对图形进行旋转。 + +#### scale(sx, sy) + +对图形进行缩放。 + +#### resetMatrix() + +清除图形实例的所有变换效果。 + +#### getTotalMatrix() + +获取应用到实例上的所有变换的矩阵。 + +### G6 3.3 + +在 G6 3.3 及以上版本中,废弃了 Group / Canvas 上只适用于三阶矩阵的变换函数: + +- 🗑 平移函数 translate; +- 🗑 移动函数 move; +- 🗑 缩放函数 scale; +- 🗑 旋转函数 rotate; +- 🗑 以 (0, 0) 点为中心的旋转函数 rotateAtStart。 + +在 G6 3.3 版本中要应用矩阵变换的效果,需要手动设置矩阵的值: + +- 获取当前矩阵:getMatrix(); +- 设置矩阵:setMatrix(matrix) 或 attr('matrix', matrix); +- 重置矩阵:resetMatrix()。 + +为了方面使用,我们提供了矩阵变换的工具方法: + +```javascript +import { ext } from '@antv/matrix-util'; + +const transform = ext.transform; + +// 3*3 矩阵变换,用于二维渲染 +transform(m, [ + ['t', 100, 50], // translate (100, 50) + ['r', Math.PI], // rotate Math.PI + ['s', 2, 2], // scale 2 times at x-axis and y-axis +]); +``` + +#### 示例 + +以下方法实现了在自定义节点 example 中增加一个矩形,并将该矩形位移 `(100, 50)` 后,旋转 `Math.PI / 4`,最后在 x 方向放大 2 倍,并在 y 方向缩小 2 倍: + +```javascript +import { ext } from '@antv/matrix-util'; + +const transform = ext.transform; + +G6.registerNode('example', { + drawShape: (cfg, group) => { + const rect = group.addShape('rect', { + attrs: { + width: 100, + height: 100, + x: 100, + y: 100, + fill: '#9EC9FF', + stroke: '#5B8FF9', + lineWidth: 3, + }, + // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 + name: 'rect-shape', + draggable: true, + }); + let matrix = rect.getMatrix(); + + // 图形或分组的初始矩阵时 null,为了避免变换一个 null 矩阵,需要将其初始化为单位矩阵 + if (!matrix) matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; + + // 3*3 矩阵变换,用于二维渲染 + const newMatrix = transform(matrix, [ + ['t', 100, 50], // translate + ['r', Math.PI / 4], // rotate + ['s', 2, 0.5], // scale + ]); + + rect.setMatrix(newMatrix); + }, +}); +``` diff --git a/packages/site/docs/manual/middle/graph.en.md b/packages/site/docs/manual/middle/graph.en.md new file mode 100644 index 0000000000..4e91a65c64 --- /dev/null +++ b/packages/site/docs/manual/middle/graph.en.md @@ -0,0 +1,185 @@ +--- +title: Graph +order: 1 +--- + +## What is Graph + +"Graph" means graphics, images, figures in the traditional cognitive. The "Graph" in graph theory and visuzliation has specific definition: it is make up of objects and their relationships. It might not a visual graph, but a relational data.
+ +Graph is the carrier of G6. All the operations about events, behaviors, items are mounted on the instance of Graph. + +The life cycle of an instance of Graph is: + +Initialize -> Load data -> Render -> Update -> Destroy. + +In [Getting Started](/en/docs/manual/getting-started), we introduce the process of initialization, data loading, graph rendering. In this document, we will introduce the initialization/instantiating process in detail. + +## Prerequisite Code + +The code for interpretation of this chapter will base on the following JavaScript code embedded in HTML. By defining the data, instantiating the graph, loading the data, and rendering the graph, the code below results in the graph in the figure:
img + +```html + + + + + Tutorial Demo + + + /* The container of the graph */ +
+ /* Import G6 */ + + + + +``` + +## Initialize/Instantiate a Graph + +Instantiate a Graph by `new G6.Graph(config)`, where the parameter `config` is an object of graph configurations. Most global configurations are assigned here. As shown in [Prerequisite Code](#Prerequisite Code), we instantiate a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', // Assign the id of the graph container + // The width and the height of the graph + width: 800, + height: 500, +}); +``` + +### Required Configuration + +There are three required configurations when instantiating a Graph: + +- `container` + +Type: String | Object. The DOM container of the graph. It can be a string for the `id` of the DOM container, or an object for the DOM object. + +- `width` and `height` + +Type: Number. THe width and the height of the graph. + +### Commonly used Configuration + +There are some commonly used configurations. For complete configurations, please refer to [Graph API](/en/docs/api/Graph). + +#### Rendering + +- `renderer` + +Type: String; Default: 'canvas', the value could be 'canvas' or 'svg'. Render the graph with Canvas or SVG. _It is supported expecting V3.3.x._ SVG rendering in G6 supports all the functions in Canvas rendering. We all known that the performance of SVG is not good as canvas. So use Canvas rendering in the case of large data instead. Expect for default nodes and edges and graphics shapes used in custom node and edge as Canvas version, SVG also supports `'dom'` shape when customing node or edge. Detials are in [Custom Node with Dom](/en/docs/manual/middle/elements/nodes/custom-node#5-custom-node-with-dom). + +#### Auto Fit + +- `fitView` + +Type: Boolean; Default: 'false'. Whether to fit the canvas to the view port automatically. + +- `fitViewPadding` + +Type: Number | Array; Default: 0. It is the padding between canvas and the border of view port. Takes effect only when `fitView: true`. + +- `fitCenter` + +Type: Boolean; Default: 'false'. Whether to translate the graph to align its center with the canvas. _Supported by v3.5.1._ + +#### Global Item Configuration + +- `defaultNode` + +Type: Object. The global configuration for all the nodes in the graph in default state. It includes the style properties and other properties of nodes. Refer to [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode). + +- `defaultEdge` + +Type: Object. The global configuration for all the edges in the graph in default state. It includes the style properties and other properties of edges. Refer to [Built-in Edges](/en/docs/manual/middle/elements/nodes/defaultEdge). + +- `nodeStateStyles` + +Type: Object. The style properties of nodes in different states expect for default state. Refer to [State](/en/docs/manual/middle/states/state). + +- `edgeStateStyles` + +Type: Object. The style properties of edges in different states expect for default state. Refer to [State](/en/docs/manual/middle/states/state). + +#### Layout + +- `layout` + +Type: Object. If there is no position information in data, Random Layout will take effect by default. The layout options and their configurations can be found in [Layout](/en/docs/manual/middle/layout/graph-layout),[Graph Layout API](/en/docs/api/graphLayout/guide) or [TreeGraph Layout API](/en/docs/api/treeGraphLayout/guide). + +#### Interaction + +- `modes` + +Type: Array. It is the set of interactions modes. One mode is made up of one or more interaction events. Refer to [Mode](/en/docs/manual/middle/states/mode). + +#### Animation + +- `animate` + +Type: Boolean; Default: 'false'. Whether to activate the global animation. If it is `true`, the positions of nodes will be changed animatively when the layout is changed. + +- `animateCfg` + +Type: Object. The configurations for global animation, includes easing functions, duration, and so on. Refer to [Animation](/en/docs/manual/middle/animation). + +#### Plugin + +- `plugins` + +Type: Array. The plugins to assist the anaysis. Refer to [Plugins and Tools](/en/docs/manual/tutorial/plugins). + +## Commonly Used Functions + +There are two required functions in the code of [Prerequisite Code](#Prerequisite Code): + +```javascript +// Load the data +graph.data(data); +// Render the graph +graph.render(); +``` + +- `data(data)`: Load the source `data` to the instance `graph`. +- render(): render the graph. + +For complete functions for Graph, refer to [Graph API](/en/docs/api/Graph). diff --git a/packages/site/docs/manual/middle/graph.zh.md b/packages/site/docs/manual/middle/graph.zh.md new file mode 100644 index 0000000000..46ed21d988 --- /dev/null +++ b/packages/site/docs/manual/middle/graph.zh.md @@ -0,0 +1,183 @@ +--- +title: Graph 图 +order: 1 +--- + +## 什么是 Graph + +中文字“图”在大家的传统认知里指的是图画、图像,而图论与可视化中的“图”—— Graph 则有着更精确的定位:主体(objects)与关系(relationships)的组成。它甚至不局限于视觉,主体与关系的数据也可以称为图。
+ +> —— 摘自 AntV 专栏文章:Graph Visualization · 知多少 之 《HelloWorld 图可视化》。 + +在 G6 中,Graph 对象是图的载体,它包含了图上的所有元素(节点、边等),同时挂载了图的相关操作(如交互监听、元素操作、渲染等)。
Graph 对象的生命周期为:初始化 —> 加载数据 —> 渲染 —> 更新 —> 销毁。 + +在 [快速上手](/zh/docs/manual/getting-started) 中,我们简单介绍了 初始化、加载数据、渲染图 的使用方法。本文将主要介绍初始化/实例化图。 + +## 前提代码 + +本文的讲解将会基于下面这份内嵌 JavaScript 的 HTML 代码。该代码通过定义数据、实例化图、读取数据、渲染图等操作中完成了下图中简单的图:
img + +```html + + + + + Tutorial Demo + + + /* 图的画布容器 */ +
+ /* 引入 G6 */ + + + + +``` + +## 初始化/实例化图 + +通过  `new G6.Graph(config)` 进行图的实例化。其中参数  `config` 是 Object 类型的图的配置项,图的大部分功能可以通过该配置项进行全局配置。如 [前提代码](#前提代码) 这样实例化图: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', // 指定图画布的容器 id,与第 9 行的容器对应 + // 画布宽高 + width: 800, + height: 500, +}); +``` + +### 必要配置项 + +上面代码中实例化 Graph 的部分使用了三个必要的配置项: + +- `container` + +类型:String | Object。图的 DOM 容器。可以是 String,为 DOM 容器的 `id`。也可以是 Object ,为 DOM 对象。 + +- `width`、`height` + +类型:Number。图的画布的宽度和高度。 + +### 常用配置项 + +下面列举实例化图时常见的配置项,完整的配置项参见 [Graph API](/zh/docs/api/Graph)。 + +#### 使用 canvas 或 svg 渲染 + +- `renderer` + +类型:String;默认:'canvas',可选项:'canvas' / 'svg' 。配置使用 canvas 或 svg 渲染,_除 V3.3.x 外其他版本均支持。_ G6 默认使用 Canvas 渲染图, SVG 渲染也支持 Canvas 的所有功能。需要注意的是,我们都知道 SVG 的性能较差,在大规模数据或图元的情况下请谨慎选择。SVG 除支持内置的所有节点/边类型以及自定义节点/边时使用与 Canvas 相同的图形外,还支持在自定义节点/边时使用 `'dom'` 图形,详见 [使用 DOM 自定义节点](/zh/docs/manual/middle/elements/nodes/custom-node#5-使用-dom-自定义节点)。 + +#### 自适应画布 + +- `fitView` + +类型:Boolean;默认:'false'。图是否自适应画布。 + +- `fitViewPadding` + +类型:Number | Array;默认:0。图自适应画布时的四周留白像素值。`fitView` 为 `true` 时生效。 + +- `fitCenter` + +类型:Boolean;默认:'false'。是否平移图使其中心对齐到画布中心。_v3.5.1 后支持。_ + +#### 全局元素配置 + +- `defaultNode` + +类型:Object。默认情况下全局节点的配置项,包括样式属性和其他属性。详见 [内置节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 教程。 + +- `defaultEdge` + +类型:Object。默认情况下全局边的配置项,包括样式属性和其他属性。详见 [内置边](/zh/docs/manual/middle/elements/edges/defaultEdge) 教程。 + +- `nodeStateStyles` + +类型:Object。除默认状态外的其他状态下节点的样式配置。详见 [状态 State](/zh/docs/manual/middle/states/state) 教程。 + +- `edgeStateStyles` + +类型:Object。除默认状态外的其他状态下边的样式配置。详见  [状态 State](/zh/docs/manual/middle/states/state) 教程。 + +#### 布局相关 + +- `layout` + +类型:Object。若数据中不存在节点位置,则默认为随机布局。配置布局类型及其参数。详见 [使用布局 Layout](/zh/docs/manual/middle/layout/graph-layout) 教程,[图布局 API](/zh/docs/api/graphLayout/guide) 或 [树图布局 API](/zh/docs/api/treeGraphLayout/guide)。 + +#### 交互行为相关 + +- `modes` + +类型:Array。配置多种交互模式及其包含的交互事件的。详见 [交互模式 Mode](/zh/docs/manual/middle/states/mode)。 + +#### 动画相关 + +- `animate` + +类型:Boolean;默认:'false'。是否启用全局动画。启用后,布局变化时将会以动画形式变换节点位置。 + +- `animateCfg` + +类型:Object。全局动画的配置项,包括动画效果、动画时长等。详见 [动画 Animation](/zh/docs/manual/middle/animation)。 + +#### 插件 + +- `plugins` + +类型:Array。配置辅助插件。详见 [插件与工具](/zh/docs/manual/tutorial/plugins)。 + +## 常用函数 + +在前面的代码中使用了两个必要的函数: + +```javascript +// 读取数据 +graph.data(data); +// 渲染图 +graph.render(); +``` + +- `data(data)`:读取数据源 `data` 到图实例 `graph` 中。 +- render():渲染图。 + +Graph 的完整函数参见 [Graph API](/zh/docs/api/Graph)。 diff --git a/packages/site/docs/manual/middle/layout/ai-layout.en.md b/packages/site/docs/manual/middle/layout/ai-layout.en.md new file mode 100644 index 0000000000..92a7f3a2bc --- /dev/null +++ b/packages/site/docs/manual/middle/layout/ai-layout.en.md @@ -0,0 +1,52 @@ +--- +title: AI Layout Prediction +order: 6 +--- + +### Background + +In an application of graph visualization, how to choose a suitable layout so that the data queried every time can be clearly displayed is a big challenge. Although we can allow user to switch the layout and their configuration like Gephi, but it is obviousely inefficient. And the users might not able to choose a perfect layout. In order to completely solve the problem, G6 provides intelligent layout prediction capabilities. The prediction engine will recommend the most suitable layout based on the data. + +### Definition + +Intelligent layout refers to modeling in combination with neural networks and training output prediction models through a large amount of labeled data (label layout classification). In business scenarios, the real graph data is predicted through the model, so as to recommend the most suitable data layout classification method. + +### Prediction Engine + +@antv/vis-predict-engine is positioned as a prediction engine for visualization, which is mainly used for classification prediction of graph layout in the short term. Generally speaking, the prediction engine will support layout configuration parameter prediction, node category prediction, chart category prediction, etc. + +The overall process of G6 map layout prediction is shown in the following figure: + + + +### Usage + +The AntV team encapsulated the ability of graph layout prediction into the NPM package @antv/vis-predict-engine, and used the predict method to predict the layout of the provided data. The basic usage is as follows: + +``` +import G6 from '@antv/g6' +import { GraphLayoutPredict } from '@antv/vis-predict-engine' +const data = { + nodes: [], + edges: [] +} +// predictLayout indicates the predicted layout type, such as 'force' or 'radial' +// 'confidence' is the confidence of the prediction +const { predictLayout, confidence } = await GraphLayoutPredict.predict(data); +const graph = new G6.Graph({ + ... // other configurations + layout: { + type: predictLayout + } +}) +``` + +### Result + +As shown in the figure below, in a medical and health map, the layout effect of "force" obtained by the intelligent layout prediction engine is the best, and the comparison experiment also meets expectations. + + + +### Demo + +[AI Layout Prediction DEMO](/en/examples/net/aiLayout#layoutPrediction) diff --git a/packages/site/docs/manual/middle/layout/ai-layout.zh.md b/packages/site/docs/manual/middle/layout/ai-layout.zh.md new file mode 100644 index 0000000000..cd83758f3f --- /dev/null +++ b/packages/site/docs/manual/middle/layout/ai-layout.zh.md @@ -0,0 +1,52 @@ +--- +title: G6 智能布局预测 +order: 6 +--- + +### 背景 + +图可视分析应用中,如何选择合适的布局让每次查询的数据都能够很清晰地展示是一个不小的挑战,虽然我们可以像 Gephi 一样,把布局的配置切换交给用户来做,让用户自己切换布局、调整参数来选择合适的布局,但这样的效率显然太低。为了彻底解决布局选择的问题,G6 提供了智能布局预测的能力,预测引擎会根据查询到的数据,推荐最适合的布局,用户直接使用推荐的布局即可。 + +### 定义 + +智能布局是指在结合神经网络进行建模,通过大量的标注数据(标记布局分类)进行训练输出预测模型,业务场景中针对通过模型对真实的图数据进行预测,从而推荐出该数据最适合的布局分类的方法。 + +### 预测引擎 + +@antv/vis-predict-engine 定位为可视化预测引擎,短期内主要用于图布局的分类预测。常久来看,可视化预测引擎会支持布局配置参数预测、节点类别预测、chart 类别预测等。 + +G6 中图布局预测的整体流程如下图所示。 + + + +### 用法 + +AntV 团队将图布局预测的能力封装成 NPM 包 @antv/vis-predict-engine,通过 predict 方法来预测提供的数据应该使用什么布局,基本用法如下。 + +``` +import G6 from '@antv/g6' +import { GraphLayoutPredict } from '@antv/vis-predict-engine' +const data = { + nodes: [], + edges: [] +} +// predictLayout 表示预测的布局,如 force 或 radial +// confidence 表示预测的可信度 +const { predictLayout, confidence } = await GraphLayoutPredict.predict(data); +const graph = new G6.Graph({ + // 省略其他配置 + layout: { + type: predictLayout + } +}) +``` + +### 效果 + +如下图所示,在一份医疗健康图谱中,通过智能布局预测引擎得出 "Force" 的布局效果最佳,通过对比实验,也符合预期。 + + + +### Demo + +具体 Demo 参考这里:[AI 智能布局推荐 DEMO](/zh/examples/net/aiLayout#layoutPrediction) diff --git a/packages/site/docs/manual/middle/layout/custom-layout.en.md b/packages/site/docs/manual/middle/layout/custom-layout.en.md new file mode 100644 index 0000000000..4fb6e406c6 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/custom-layout.en.md @@ -0,0 +1,222 @@ +--- +title: Custom Layout +order: 6 +--- + +G6 provides abundant commonly used built-in layouts for Graph and TreeGraph respectively. The usage can be found in: [Graph Layout](/en/docs/manual/middle/layout/graph-layout), [Tree Layout](/en/docs/manual/middle/layout/tree-graph-layout), [Graph Layout API](/en/docs/api/graphLayout/guide) or [TreeGraph Layout API](/en/docs/api/treeGraphLayout/guide). Custom layout mechanism of G6 allows the users to design their own type of layout to meet their special requirements. + +   ⚠️Attention: The TreeGraph does not support custom layout temporarily. + +In this document, we will introduce the custom layout by registering a layout for Bigraph. + +## The API of Cumstom Layout + +```javascript +/** + * Register a Layout + * @param {string} type The layout type is must assigned to an unique string + * @param {object} layout The layout method + */ +Layout.registerLayout = function(type, { + /** + * The default configurations of the custom layout. It will be mixed by the configurations from users + */ + getDefaultCfg() { + return {}; + }, + /** + * Initialize + * @param {object} data data + */ + init(data) {}, + /** + * Execute the layout + */ + execute() {}, + /** + * Layout with the data + * @param {object} data 数据 + */ + layout(data) {}, + /** + * Update the layout configurations, but do not execute the layout + * @param {object} cfg The new configurations + */ + updateCfg(cfg) {}, + /** + * Destroy + */ + destroy() {}, +}); +``` + +## Custom Layout + +Now, we are going to register a layout for Bigraph. Bigraph is the graph with nodes divided into two parts. There will be no edges between the nodes which are belong to the same part. In the custom layout, we sort the nodes according to their topology to reduce the edge crossings.
+ +img + +The data of the Bigraph is shown below, where the nodes are divided into `'part1'` and `'part2'` by the property `cluster`. + +```javascript +const data = { + nodes: [ + { id: '0', label: 'A', cluster: 'part1' }, + { id: '1', label: 'B', cluster: 'part1' }, + { id: '2', label: 'C', cluster: 'part1' }, + { id: '3', label: 'D', cluster: 'part1' }, + { id: '4', label: 'E', cluster: 'part1' }, + { id: '5', label: 'F', cluster: 'part1' }, + { id: '6', label: 'a', cluster: 'part2' }, + { id: '7', label: 'b', cluster: 'part2' }, + { id: '8', label: 'c', cluster: 'part2' }, + { id: '9', label: 'd', cluster: 'part2' }, + ], + edges: [ + { source: '0', target: '6' }, + { source: '0', target: '7' }, + { source: '0', target: '9' }, + { source: '1', target: '6' }, + { source: '1', target: '9' }, + { source: '1', target: '7' }, + { source: '2', target: '8' }, + { source: '2', target: '9' }, + { source: '2', target: '6' }, + { source: '3', target: '8' }, + { source: '4', target: '6' }, + { source: '4', target: '7' }, + { source: '5', target: '9' }, + ], +}; +``` + +### Requirements Analysis + +To reduce the edge crossings, we sort the nodes in `part1` and `part2` respectively. The process is: + +- Step 1: Assign the index from 0 randomly for the nodes in `'part1'` and `'part2'` respectively; +- Step 2: Traverse the nodes in `'part1'`. For each node A: + - Find the set of related nodes of A (connect to A directly) in `'part2'` ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-WOhQIGg9l8AAAAAAAAAAABkARQnAQ). Sum up the indexes of the nodes in ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-WOhQIGg9l8AAAAAAAAAAABkARQnAQ), and divided it by the number of elements in ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-WOhQIGg9l8AAAAAAAAAAABkARQnAQ). Replace the index of A by the result: ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*VfXiSK1f02cAAAAAAAAAAABkARQnAQ) +- Step 3: Tranverse the nodes in `'part2'`. For each node A(Similar to the Step 2): + - Find the set of related nodes of B (connect to B directly) in `'part1'` ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GqZiSKI-nB8AAAAAAAAAAABkARQnAQ). Sum up the indexes of the nodes in ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GqZiSKI-nB8AAAAAAAAAAABkARQnAQ), and divided it by the number of elements in ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GqZiSKI-nB8AAAAAAAAAAABkARQnAQ). Replace the index of A by the result: ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-8b2Spfb4HIAAAAAAAAAAABkARQnAQ) +- Step 4: Sort the nodes in `part1` and `part2` respectively according to their indexed. The result order determine the postions of the nodes in the final layout. + +## Implementation + +The following code below register a layout named `'bigraph-layout'` for Bigraph. The complete code can be found in: Cusom Layout-Bigraph. The usage of custom layout is the same as built-in layouts: configure the `layout` to the graph when instantiating. Refer to: [Graph Layout](/en/docs/manual/middle/layout/graph-layout). + +```javascript +G6.registerLayout('bigraph-layout', { + // Default configurations + getDefaultCfg: function getDefaultCfg() { + return { + center: [0, 0], // The center of the layout + biSep: 100, // The separation of these two parts + nodeSep: 20, // The separation between nodes in the same part + direction: 'horizontal', // The direction of the two parts + nodeSize: 20, // The node size + }; + }, + // Execute the layout + execute: function execute() { + var self = this; + var center = self.center; + var biSep = self.biSep; + var nodeSep = self.nodeSep; + var nodeSize = self.nodeSize; + var part1Pos = 0, + part2Pos = 0; + // Layout the graph in horizontally + if (self.direction === 'horizontal') { + part1Pos = center[0] - biSep / 2; + part2Pos = center[0] + biSep / 2; + } + var nodes = self.nodes; + var edges = self.edges; + var part1Nodes = []; + var part2Nodes = []; + var part1NodeMap = new Map(); + var part2NodeMap = new Map(); + // Separate the nodes and init the positions + nodes.forEach(function (node, i) { + if (node.cluster === 'part1') { + part1Nodes.push(node); + part1NodeMap.set(node.id, i); + } else { + part2Nodes.push(node); + part2NodeMap.set(node.id, i); + } + }); + + // Sort the nodes in part1 + part1Nodes.forEach(function (p1n) { + var index = 0; + var adjCount = 0; + edges.forEach(function (edge) { + var sourceId = edge.source; + var targetId = edge.target; + if (sourceId === p1n.id) { + index += part2NodeMap.get(targetId); + adjCount++; + } else if (targetId === p1n.id) { + index += part2NodeMap.get(sourceId); + adjCount++; + } + }); + index /= adjCount; + p1n.index = index; + }); + part1Nodes.sort(function (a, b) { + return a.index - b.index; + }); + + // Sort the nodes in part2 + part2Nodes.forEach(function (p2n) { + var index = 0; + var adjCount = 0; + edges.forEach(function (edge) { + var sourceId = edge.source; + var targetId = edge.target; + if (sourceId === p2n.id) { + index += part1NodeMap.get(targetId); + adjCount++; + } else if (targetId === p2n.id) { + index += part1NodeMap.get(sourceId); + adjCount++; + } + }); + index /= adjCount; + p2n.index = index; + }); + part2Nodes.sort(function (a, b) { + return a.index - b.index; + }); + + // Place the ndoes + var hLength = part1Nodes.length > part2Nodes.length ? part1Nodes.length : part2Nodes.length; + var height = hLength * (nodeSep + nodeSize); + var begin = center[1] - height / 2; + if (self.direction === 'vertical') { + begin = center[0] - height / 2; + } + part1Nodes.forEach(function (p1n, i) { + if (self.direction === 'horizontal') { + p1n.x = part1Pos; + p1n.y = begin + i * (nodeSep + nodeSize); + } else { + p1n.x = begin + i * (nodeSep + nodeSize); + p1n.y = part1Pos; + } + }); + part2Nodes.forEach(function (p2n, i) { + if (self.direction === 'horizontal') { + p2n.x = part2Pos; + p2n.y = begin + i * (nodeSep + nodeSize); + } else { + p2n.x = begin + i * (nodeSep + nodeSize); + p2n.y = part2Pos; + } + }); + }, +}); +``` diff --git a/packages/site/docs/manual/middle/layout/custom-layout.zh.md b/packages/site/docs/manual/middle/layout/custom-layout.zh.md new file mode 100644 index 0000000000..977b46eead --- /dev/null +++ b/packages/site/docs/manual/middle/layout/custom-layout.zh.md @@ -0,0 +1,224 @@ +--- +title: 自定义布局 Layout +order: 6 +--- + +G6 提供了一般图和树图的一些常用布局,使用方式参见:中级教程  [一般图布局 Layout](/zh/docs/manual/middle/layout/graph-layout),[树图布局 Layout](/zh/docs/manual/middle/layout/tree-graph-layout),[图布局 API](/zh/docs/api/graphLayout/guide) 或 [树图布局 API](/zh/docs/api/treeGraphLayout/guide)。当这些内置布局无法满足需求时,G6 还提供了一般图的自定义布局的机制,方便用户进行更定制化的扩展。 + +   ⚠️ 注意: 树图暂时不支持自定义布局。 + +本文将会通过自定义 Bigraph 布局的例子讲解自定义布局。 + +## 自定义布局 API + +G6 中自定义布局的 API 如下: + +```javascript +/** + * 注册布局的方法 + * @param {string} type 布局类型,外部引用指定必须,不要与已有布局类型重名 + * @param {object} layout 布局方法 + */ +Layout.registerLayout = function(type, { + /** + * 定义自定义行为的默认参数,会与用户传入的参数进行合并 + */ + getDefaultCfg() { + return {}; + }, + /** + * 初始化 + * @param {object} data 数据 + */ + init(data) {}, + /** + * 执行布局 + */ + execute() {}, + /** + * 根据传入的数据进行布局 + * @param {object} data 数据 + */ + layout(data) {}, + /** + * 更新布局配置,但不执行布局 + * @param {object} cfg 需要更新的配置项 + */ + updateCfg(cfg) {}, + /** + * 销毁 + */ + destroy() {}, +}); +``` + +## 自定义布局 + +下面,我们将讲解如何自定义布局如下图的二分图 Bigraph。二分图只存在两部分节点之间的边,同属于一个部分的节点之间没有边。我们希望布局能够对两部分节点分别进行排序,减少边的交叉。
+ +img + +该二分图数据如下,节点根据 `cluster` 字段分为 了 `'part1'` 和 `'part2'`,代表二分图的两部分。 + +```javascript +const data = { + nodes: [ + { id: '0', label: 'A', cluster: 'part1' }, + { id: '1', label: 'B', cluster: 'part1' }, + { id: '2', label: 'C', cluster: 'part1' }, + { id: '3', label: 'D', cluster: 'part1' }, + { id: '4', label: 'E', cluster: 'part1' }, + { id: '5', label: 'F', cluster: 'part1' }, + { id: '6', label: 'a', cluster: 'part2' }, + { id: '7', label: 'b', cluster: 'part2' }, + { id: '8', label: 'c', cluster: 'part2' }, + { id: '9', label: 'd', cluster: 'part2' }, + ], + edges: [ + { source: '0', target: '6' }, + { source: '0', target: '7' }, + { source: '0', target: '9' }, + { source: '1', target: '6' }, + { source: '1', target: '9' }, + { source: '1', target: '7' }, + { source: '2', target: '8' }, + { source: '2', target: '9' }, + { source: '2', target: '6' }, + { source: '3', target: '8' }, + { source: '4', target: '6' }, + { source: '4', target: '7' }, + { source: '5', target: '9' }, + ], +}; +``` + +### 需求分析 + +为了减少边的交叉,可以通过排序,将 `'part1'`  的节点 A 对齐到所有与 A 相连的 `'part2'` 中的节点的平均中心;同样将 `'part2'` 中的节点 a 对齐到所有与 a 相连的 `'part1'` 中的节点的平均中心。可以描述成如下过程: + +- Step 1:为  `'part1'`  和  `'part2'`  的节点初始化随机序号 index,都分别从 0 开始; +- Step 2:遍历  `'part1'` 的节点,对每一个节点 A: + - 找到与 A 相连的属于  `'part2'`  的节点的集合 ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-WOhQIGg9l8AAAAAAAAAAABkARQnAQ),加和  ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-WOhQIGg9l8AAAAAAAAAAABkARQnAQ) 中所有节点的 index,并除以 ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-WOhQIGg9l8AAAAAAAAAAABkARQnAQ) 的元素个数,得数覆盖 A 的 index 值:![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*VfXiSK1f02cAAAAAAAAAAABkARQnAQ) +- Step 3:遍历  `'part2'` 的节点,对每一个节点 B(与  Step 2 相似): + - 找到与 B 相连的属于  `'part1'`  的节点的集合 ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GqZiSKI-nB8AAAAAAAAAAABkARQnAQ),加和  ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GqZiSKI-nB8AAAAAAAAAAABkARQnAQ)  中所有节点的 index,并除以 ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GqZiSKI-nB8AAAAAAAAAAABkARQnAQ)  的元素个数,得数覆盖 B 的 index 值:![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-8b2Spfb4HIAAAAAAAAAAABkARQnAQ) +- Step 4:两部分节点分别按照节点的序号 index 进行排序,最终按照节点顺序安排节点位置。 + +## 实现 + +下面代码展示了自定义名为  `'bigraph-layout'` 的二分图布局,完整代码参见:自定义布局-二分图。使用该布局的方式与使用内置布局方式相同,都是在实例化图时将其配置到 `layout` 配置项中,详见:[一般图布局](/zh/docs/manual/middle/layout/graph-layout)。 + +```javascript +G6.registerLayout('bigraph-layout', { + // 默认参数 + getDefaultCfg: function getDefaultCfg() { + return { + center: [0, 0], // 布局的中心 + biSep: 100, // 两部分的间距 + nodeSep: 20, // 同一部分的节点间距 + direction: 'horizontal', // 两部分的分布方向 + nodeSize: 20, // 节点大小 + }; + }, + // 执行布局 + execute: function execute() { + var self = this; + var center = self.center; + var biSep = self.biSep; + var nodeSep = self.nodeSep; + var nodeSize = self.nodeSize; + var part1Pos = 0, + part2Pos = 0; + // 若指定为横向分布 + if (self.direction === 'horizontal') { + part1Pos = center[0] - biSep / 2; + part2Pos = center[0] + biSep / 2; + } + var nodes = self.nodes; + var edges = self.edges; + var part1Nodes = []; + var part2Nodes = []; + var part1NodeMap = new Map(); + var part2NodeMap = new Map(); + // separate the nodes and init the positions + nodes.forEach(function (node, i) { + if (node.cluster === 'part1') { + part1Nodes.push(node); + part1NodeMap.set(node.id, i); + } else { + part2Nodes.push(node); + part2NodeMap.set(node.id, i); + } + }); + + // 对 part1 的节点进行排序 + part1Nodes.forEach(function (p1n) { + var index = 0; + var adjCount = 0; + edges.forEach(function (edge) { + var sourceId = edge.source; + var targetId = edge.target; + if (sourceId === p1n.id) { + index += part2NodeMap.get(targetId); + adjCount++; + } else if (targetId === p1n.id) { + index += part2NodeMap.get(sourceId); + adjCount++; + } + }); + index /= adjCount; + p1n.index = index; + }); + part1Nodes.sort(function (a, b) { + return a.index - b.index; + }); + + // 对 part2 的节点进行排序 + part2Nodes.forEach(function (p2n) { + var index = 0; + var adjCount = 0; + edges.forEach(function (edge) { + var sourceId = edge.source; + var targetId = edge.target; + if (sourceId === p2n.id) { + index += part1NodeMap.get(targetId); + adjCount++; + } else if (targetId === p2n.id) { + index += part1NodeMap.get(sourceId); + adjCount++; + } + }); + index /= adjCount; + p2n.index = index; + }); + part2Nodes.sort(function (a, b) { + return a.index - b.index; + }); + + // 放置节点 + var hLength = part1Nodes.length > part2Nodes.length ? part1Nodes.length : part2Nodes.length; + var height = hLength * (nodeSep + nodeSize); + var begin = center[1] - height / 2; + if (self.direction === 'vertical') { + begin = center[0] - height / 2; + } + part1Nodes.forEach(function (p1n, i) { + if (self.direction === 'horizontal') { + p1n.x = part1Pos; + p1n.y = begin + i * (nodeSep + nodeSize); + } else { + p1n.x = begin + i * (nodeSep + nodeSize); + p1n.y = part1Pos; + } + }); + part2Nodes.forEach(function (p2n, i) { + if (self.direction === 'horizontal') { + p2n.x = part2Pos; + p2n.y = begin + i * (nodeSep + nodeSize); + } else { + p2n.x = begin + i * (nodeSep + nodeSize); + p2n.y = part2Pos; + } + }); + }, +}); +``` diff --git a/packages/site/docs/manual/middle/layout/graph-layout.en.md b/packages/site/docs/manual/middle/layout/graph-layout.en.md new file mode 100644 index 0000000000..d5af4c63c7 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/graph-layout.en.md @@ -0,0 +1,370 @@ +--- +title: Graph Layout +order: 0 +--- + +## Introduction + +Graph layouts are the algorithms arranging the node positions to obtain a understandable visualizaiton. According to the differences of data strucutre, the layouts can be categorized into: general graph layout and tree graph layout. There are several layout algorithms for them respectively. By utilizing the built-in layouts, [Translating the layouts and their configurations, translating the data](/en/docs/manual/middle/layout/layout-mechanism) can be achieved. Besides, G6 provides the [Web-Worker](/en/docs/manual/middle/layout/webworker) for general graph layout in case layout calculation takes too long to block page interaction. + +Besides, G6 supports [Custom Layout](/en/docs/manual/middle/layout/custom-layout) mechanism for users to design their own layout algorithm. + +In fact, 'layout' is a free mechanism in G6. The built-in layouts only calculate and manipulate the `x` and `y` in node data. In other word, users can assign `x` and `y` to nodes by any other ways including the algorithms from the third-party libraries. Once G6 find the `x` and `y` information on data, it will render the graph according to it. + +In this ducoment, we will introduce the layout algorithms in detail. + +## G6 Layouts Overview + +- [Random Layout](#random): Randomizes the node postions; +- [Force2 Layout](#force2): Force-directed layout comes from graphin-force, but with better performance; +- [GForce Layout](#gforce): Classical force-directed layout supports GPU parallel computing, supported by G6 4.0; +- [Force Layout](#force): Classical force-directed layout imported from d3; +- [Fruchterman Layout](#fruchterman): A kind of force-directed layout; +- [Circular Layout](#circular): Arranges the nodes on a circle; +- [Radial Layout](#radial): Arranges the nodes around a focus node radially; +- [MDS Layout](#mds): Multidemensional Scaling; +- [Dagre Layout](#dagre): Arranges the nodes hierarchically; +- [Concentric Layout](#concentric): Arranges the nodes on concentric circles; +- [Grid Layout](#grid): Arranges the nodes on grid. +- [Combo Force Layout](#combo-force):_New feature of V3.5_ Designed for graph with combos. + +## Configure the Graph + +Configure `layout` to the Graph instance to assign the layout methods and their configurations. The following code assigns the layout with `type: 'force'`, which means the classical force-directed layout algorithm. The configurations `preventOverlap: true` and `nodeSize: 30` are assigned to prevent node overlappings, where the `nodeSize` is used for collide detection. More layout configurations can be found in the following sections. + +```javascript +const graph = new G6.Graph({ + // ... // Other configurations for the graph + layout: { + // Object, the layout configuration. Random layout by default + type: 'force', + preventOverlap: true, + nodeSize: 30, + // workerEnabled: true, // Whether enable webworker + // gpuEnabled: true // Whether enable GPU version. supported by G6 4.0, and only support gForce and fruchterman layout + // ... // Other configurations for the layout + }, +}); +``` + +Different layout algorithms have different configurations. For all the general graph layout algorithms in G6, you can enable the web-worker by configure `workerEnabled: true` in the `layout` configuration above. With web-worker, layout algorithms performed on large data with high cost will not block the web page. + +When the `layout` is not assigned: + +- If there is position information with `x` and `y` in node data, G6 renders the graph with them; +- If the position information does not exist in the node data, Random Layout will take effect by default. + +## Layouts for Graph + +General graph layout API: [General Graph Layout API](/en/docs/api/graphLayout/guide). + +### Random + +img + +
**Description**: Randomizes the node positions.
**API**: [Random API](/en/docs/api/graphLayout/random)
**Configuration**: + +| Name | Type | Example | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| width | Number | 300 | The width of the graph | | +| height | Number | 300 | The height of the graph | | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | + +### Force2 + +img + +
**Description**: Force2 implements the force-directed layout algorithm by G6 4.7.0, comes from graphin-force. It supports assign different masses and center gravities for different nodes freedomly. Comparing to graphin-force, it has much better performance. If you want to fix the positions for some nodes during calculation, assign `fx` and `fy` for the nodes as fixing positions. [Demo for fixing node](/en/examples/net/forceDirected#force2Fix). +
**API**: [GForce API](/en/docs/api/graphLayout/force2) +
**Configuration**: + +| Name | Type | Example | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| animate | boolean | false | false | Whether refresh the node positions on the canvas each iteration. If it is `true`, the nodes on the canvas will looks like animating with forces | +| linkDistance | Number / Function | Example 1: `50` 
Example 2:
d => {
  // d is an edge
  if (d.id === 'edge1') {
    return 100;
  }
  return 50;
} | 1 | The edge length. It can be a function to define the different edge lengths for different edges (Example 2) | +| nodeStrength | Number / Function | Exmaple 1: -30 
Exmaple 2:
d => {
  // d is a node
  if (d.id === 'node1') {
    return -100;
  }
  return -30;
} / 1000 | 1000 | The strength of node force. Positive value means repulsive force, negative value means attractive force (it is different from 'force')(As example 2) | +| edgeStrength | Number / Function | Example 1: 1 
Example 2:
d => {
  // d is a node
  if (d.id === 'node1') {
    return 10;
  }
  return 1;
} | 200 | The strength of edge force. Calculated according to the degree of nodes by default (As Example 2) | +| preventOverlap | Boolean | false | false | Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned | +| nodeSize | Array / Number | 20 | undefined | The diameter of the node. It is used for preventing node overlappings. If `nodeSize` is not assigned, the size property in node data will take effect. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default | +| nodeSpacing

| Number / Function | Example 1 : 10
Example 2 : 
d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | img
Takes effect when `preventOverlap` is `true`. It is the minimum distance between nodes to prevent node overlappings. It can be a function to define different distances for different nodes (example 2) | +| minMovement | Number | 0.1 | 0.5 | When the average/minimum/maximum (according to `distanceThresholdMode`) movement of nodes in one iteration is smaller than `minMovement`, terminate the layout | +| distanceThresholdMode | 'mean' / 'max' / 'min' | 'mean' | 'mean' | The condition to judge with `minMovement`, `'mean'` means the layout stops while the nodes' average movement is smaller than `minMovement`, `'max'` / `'min'` means the layout stops while the nodes' maximum/minimum movement is smaller than `minMovement`. `'mean'` by default | +| maxIteration | Number | 500 | 1000 | The max number of iterations. If the average movement do not reach `minMovement` but the iteration number is over `maxIteration`, terminate the layout | +| damping | Number | 0.99 | 0.9 | Range [0, 1], affect the speed of decreasing node moving speed. Large the number, slower the decreasing | +| interval | number | 0.05 | 0.02 | controls the speed of the nodes' movement in each iteration | +| factor | number | 1 | 1 | Coefficient for the repulsive force. Larger the number, larger the repulsive force | +| maxSpeed | Number | 10 | 1000 | The max speed in each iteration | +| coulombDisScale | Number | 0.003 | 0.005 | A parameter for repulsive force between nodes. Large the number, larger the repulsion | +| getMass | Function | d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | undefined | It is a callback returns the mass of each node. If it is not assigned, the degree of each node will take effect. The usage is similar to `nodeSpacing` | +| getCenter | Function | (d, degree) => {
  // d is a node, degree is the degree of the node
  if (d.degree === 0') {
    return [100, 100, 10]; // x, y, strength
  }
  return [210, 150, 5]; // x, y, strength
} | undefined | It is a callback returns gravity center and the gravity strength for each node | +| gravity | Number | 20 | 10 | The gravity strength to the `center` for all the nodes. Larger the number, more compact the nodes | +| centripetalOptions | CentripetalOptions | refers to below | refers to below | Configurations for the center forces, including the center coordinates and the force strengths for leaf nodes, discrete nodes, and other nodes | +| leafCluster | boolean | false | false | Whether to cluster the leaf nodes. If it is `true`, the value of `centripetalOptions.single` will be set to 100; The returned value of `getClusterNodeStrength` will be used for `centripetalOptions.leaf`; `getClusterNodeStrength.center` will take the average center for all the leaf nodes in current iteration | +| clustering | boolean | false | false | Whehter cluster all the nodes according to `nodeClusterBy`. If it is `true`, the returned value of `getClusterNodeStrength` will be used for `centripetalOptions.single`, `centripetalOptions.leaf`, and `centripetalOptions.others`; `centripetalOptions.center` will take the average center of all the nodes in the same cluster | +| nodeClusterBy | string | undefined | undefined | The field name in the node data to cluster the nodes. Takes effect when `clustering` is `true`, and the `centripetalOptions` will be generated automatically. You could configure the strengths for different nodes with `clusterNodeStrength` | +| clusterNodeStrength | number / Function | node => node.weight | 20 | The clustering center force strengths for different nodes, takes effect with `clustering` and `nodeClusterBy` | +| monitor | (params:{ energy: number, nodes: NodeData[], edges: EdgeData[], iterations: number }) => void | undefined | undefined | The callback function for each iteration, the parameters including the energy of the layout, all the nodes' data, all the edges' data, and the current iteration number. Note that the calculation for energy will take extra cost. If the `monitor` is not configured, the calculation will be ignore. | +| onTick | Function | | undefined | The callback function of each iteration | +| onLayoutEnd | Function | | undefined | The callback function after layout | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | +| gpuEnabled | Boolean | true / false | false | Whether to enable the GPU parallel computing, supported by G6 4.0. If the machine or browser does not support GPU computing, it will be degraded to CPU computing automatically. | + + +Type `CentripetalOptions`: + +| Parameter | Type | Example | Default | Description | +| --- | --- | --- | --- | --- | +| single | number / Function | 2 | 2, | the center force strength for discrete nodes (with 0 degree) | +| leaf | number / Function | 2 | 2 | the center force strength for leaf nodes (with 1 degree) | +| others | number / Function | 1 | 1 | the center force strength for other nodes beside leaf and discrete nodes | +| center | Function | (node, nodes, edges) => ({ x: 10, y: 10 }) | center of the graph | the center force's coordinate. You can return different values for different nodes | + +Example for `centripetalOptions`: + +``` +centripetalOptions: { + // single, leaf, and others support function configuration, the parameters are the current node data, all the nodes' data, all the edges' data + single: (node, nodes, edges) => node.field1 || 1, + leaf: (node, nodes, edges) => node.field2 || 1, + others: (node, nodes, edges) => node.field3|| 1, + // the parameters are current node data, all the nodes' data, all the edges' data, width of the graph, height of the graph + center: (node, nodes, edges, width, height) => { + if (node.field4) return { x: width / 2, y: height / 2 }; + if (node.field5) return { x: node.field6, y: node.field7 }; + // ... + } +} +``` + + +### GForce + +img + +
**Description**: GForce implements the classical force-directed layout algorithm by G6 4.0. It supports assign different masses and center gravities for different nodes freedomly. More importantly, it supports GPU parallel acceleration. If you want to fix the positions for some nodes during calculation, assign `fx` and `fy` for the nodes as fixing positions. [Demo for fixing node](/en/examples/net/forceDirected#gForceFix). +
**API**: [GForce API](/en/docs/api/graphLayout/gforce) +
**Configuration**: + +| Name | Type | Example | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| linkDistance | Number / Function | Example 1: `50` 
Example 2:
d => {
  // d is an edge
  if (d.id === 'edge1') {
    return 100;
  }
  return 50;
} | 1 | The edge length. It can be a function to define the different edge lengths for different edges (Example 2) | +| nodeStrength | Number / Function | Exmaple 1: -30 
Exmaple 2:
d => {
  // d is a node
  if (d.id === 'node1') {
    return -100;
  }
  return -30;
} / 1000 | 1000 | The strength of node force. Positive value means repulsive force, negative value means attractive force (it is different from 'force')(As example 2) | +| edgeStrength | Number / Function | Example 1: 1 
Example 2:
d => {
  // d is a node
  if (d.id === 'node1') {
    return 10;
  }
  return 1;
} | 200 | The strength of edge force. Calculated according to the degree of nodes by default (As Example 2) | +| preventOverlap | Boolean | false | false | Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned | +| nodeSize | Array / Number | 20 | undefined | The diameter of the node. It is used for preventing node overlappings. If `nodeSize` is not assigned, the size property in node data will take effect. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default | +| nodeSpacing

| Number / Function | Example 1 : 10
Example 2 : 
d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | img
Takes effect when `preventOverlap` is `true`. It is the minimum distance between nodes to prevent node overlappings. It can be a function to define different distances for different nodes (example 2) | +| minMovement | Number | 0.1 | 0.5 | When the average movement of nodes in one iteration is smaller than `minMovement`, terminate the layout | +| maxIteration | Number | 500 | 1000 | The max number of iterations. If the average movement do not reach `minMovement` but the iteration number is over `maxIteration`, terminate the layout | +| damping | Number | 0.99 | 0.9 | Range [0, 1], affect the speed of decreasing node moving speed. Large the number, slower the decreasing | +| maxSpeed | Number | 10 | 1000 | The max speed in each iteration | +| coulombDisScale | Number | 0.003 | 0.005 | A parameter for repulsive force between nodes. Large the number, larger the repulsion | +| getMass | Function | d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | undefined | It is a callback returns the mass of each node. If it is not assigned, the degree of each node will take effect. The usage is similar to `nodeSpacing` | +| getCenter | Function | (d, degree) => {
  // d is a node, degree is the degree of the node
  if (d.degree === 0') {
    return [100, 100, 10]; // x, y, strength
  }
  return [210, 150, 5]; // x, y, strength
} | undefined | It is a callback returns gravity center and the gravity strength for each node | +| gravity | Number | 20 | 10 | The gravity strength to the `center` for all the nodes. Larger the number, more compact the nodes | +| onTick | Function | | undefined | The callback function of each iteration | +| onLayoutEnd | Function | | undefined | The callback function after layout | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | +| gpuEnabled | Boolean | true / false | false | Whether to enable the GPU parallel computing, supported by G6 4.0. If the machine or browser does not support GPU computing, it will be degraded to CPU computing automatically. | +### Force + +imggraphLayout/guide + +
**Description**: Classical force-directed layout algorithm. If you want to fix the positions for some nodes during calculation, assign `fx` and `fy` for the nodes as fixing positions. [Demo for fixing the dragged node with force layout](/en/examples/net/forceDirected#basicForceDirectedDragFix). +
**API**: [Force API](/en/docs/api/graphLayout/force) +
**Configuration**: Corresponds to the configurations in force-directed algorithm in d3.js + +| Name | Type | Example | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| linkDistance | Number / Function | Example 1: `50` 
Example 2:
d => {
  // d is an edge
  if (d.id === 'edge1') {
    return 100;
  }
  return 50;
} | 50 | The edge length. It can be a function to define the different edge lengths for different edges (Example 2) | +| nodeStrength | Number / Function | Example 1: `-30` 
Example 2:
d => {
  // d is a node
  if (d.id === 'node1') {
    return -100;
  }
  return -30;
} | null | The strength of node force. Positive value means attractive force, negative value means repulsive force (Example 2) | +| edgeStrength | Number | Example 1: 1 
Example 2:
d => {
  // d is a node
  if (d.id === 'node1') {
    return 10;
  }
  return 1;
} | null | The strength of edge force, ranges from 0 to 1. Calculated according to the degree of nodes by default (Example 2) | +| preventOverlap | Boolean | false | false | Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned. If the `nodeSize` and size in data are both undefiend, `nodeSize` will be assigned to 10 by default | +| nodeSize | Array / Number | 20 | undefined | The diameter of the node. It is used for preventing node overlappings. If `nodeSize` is not assigned, the size property in node data will take effect. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default | +| nodeSpacing

| Number / Function | Example 1: 10
Example 2:  
d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | img
Takes effect when `preventOverlap` is `true`. It is the minimum distance between nodes to prevent node overlappings. It can be a function to define different distances for different nodes (example 2)
| +| alphaDecay | Number | 0.03 | 0.028 | The decay ratio of alpha for convergence. THe range is [0, 1]. 0.028 corresponds to 300 times iteration | +| alphaMin | Number | 0.03 | 0.001 | The threshold to stop the iteration | +| alpha | Number | 0.1 | 0.3 | The current alpha of convergence | +| collideStrength | Number | 0.8 | 1 | The strength of force for preventing node overlappings. The range is [0, 1] | +| clustering | Boolean | false | false | Whether run the force layout with clustering | +| clusterNodeStrength | Number | -1 | -0.8 | The force between nodes. It will be repulsive force while it is negative | +| clusterEdgeStrength | Number | 0.1 | 0.2 | The force along the edge | +| clusterEdgeDistance | Number | 100 | 50 | The edge length between the clusters | +| clusterNodeSize | Number | 10 | 15 | The node size(diameter) for clustering | +| clusterFociStrength | Number | 0.8 | 0.5 | The force for the clustering foci | +| forceSimulation | Object | | null | Customed force simulation. If it is not assigned, the force simulation of d3.js will take effect | +| onTick | Function | | {} | The callback function of each iteration | +| onLayoutEnd | Function | | {} | The callback function after layout | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | + +### Fruchterman + +img + +
**Description**: Fruchterman is a kind of force-directed layout. If you want to fix the positions for some nodes during calculation, assign `fx` and `fy` for the nodes as fixing positions. [Demo for fixing node](/en/examples/net/fruchtermanLayout#fructhermanFix). +
**API**: [Fruchterman API](/en/docs/api/graphLayout/fruchterman) +
**Configuration**: + +| Name | Type | Example | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| maxIteration | Number | 1000 | 1000 | The maximum interation number | +| gravity | Number | 10 | 10 | The gravity, which affects the compactness of the layout | +| speed | Number | 1 | 1 | The moving speed in each iteration. Large value might lead to violent swing | +| clustering | Boolean | false | false | Whether to layout by clustering | +| clusterGravity | Number | 30 | 10 | The gravity of each clusterm which affects the compactness of each cluster | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | +| gpuEnabled | Boolean | true / false | false | Whether to enable the GPU parallel computing, supported by G6 4.0 | + +### Circular + +img +img +img + +
**Description**: Arranges the nodes on a circle.
**API**: [Circular API](/en/docs/api/graphLayout/circular)
**Configuration**: + +| Name | Type | Example/Options | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| radius | Number | 50 | null | The radius of the circle. If the `raidus` exists, `startRadius` and `endRadius` do not take effect. | +| startRadius | Number | 10 | null | The start radius of spiral layout | +| endRadius | Number | 100 | null | The end radius of spiral layout | +| clockwise | Boolean | true | true | Whether to layout clockwisely | +| divisions | Number | 3 | 1 | The division number of the nodes on the circle. Takes effect when `endRadius - startRadius !== 0` | +| ordering | String | null | 'topology' | 'degree' | null | The ordering method for nodes. `null` by default, which means the nodes are arranged in data order. 'topology' means in topology order; 'degree' means in degree order. | +| angleRatio | Number | 1 | 1 | How many 2\*PIs Between the first node and the last node | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | + +### Radial + +img + +
**Description**: Arranges the nodes to concentrics centered at a focus node according to their shortest path length to the focus node.
**API**: [Radial API](/en/docs/api/graphLayout/radial)
**Configuration**: + +| Name | Type | Example | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| linkDistance | Number | 50 | 50 | The edge length | +| maxIteration | Number | 1000 | 1000 | The max iteration number. | +| focusNode | String / Object | 'node1' | null | The focus node of the radial layout. The first node of the data is the default value. It can be the id of a node or the node item. | +| unitRadius | Number | 10 | 100 | The separation between adjacent circles. If `unitRadius` is not assigned, the layout will fill the canvas automatically. | +| preventOverlap | Boolean | false | false | Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned. | +| maxPreventOverlapIteration | Number | 500 | 200 | The maximum iteration number of preventing node overlappings | +| nodeSize | Number | 10 | 10 | The diameter of the node. It is used for preventing node overlappings.
:
The size in the node data will take effect if `nodeSize` is not assigned. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default | +| nodeSpacing
| Number / Function | Example 1: 10
Example 2:  
d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | img
Takes effect when `preventOverlap` is `true`. It is the minimum distance between nodes to prevent node overlappings. It can be a function to define different distances for different nodes (example 2)
| +| strictRadial | Boolean | true | false | Whether to layout the graph as strict radial, which means the nodes will be arranged on each circle strictly. Takes effect only when `preventOverlap` is `true`. Refer to [Radial-strictRadial API](/en/docs/api/graphLayout/radial#layoutcfgstrictradial)
- When `preventOverlap` is `true`, and `strictRadial` is `false`, the overlapped nodes are arranged along their circles strictly. But for the situation that there are too many nodes on a circle to be arranged, the overlappings might not be eliminated completely
- When `preventOverlap` is `true`, and `strictRadial` is `true` , the overlapped nodes can be arranged around their circle with small offsets.
| +| sortBy | String | 'data' / 'cluster' | undefined | Sort the nodes of the same level. `undefined` by default, which means place the nodes with connections as close as possible; `'data'` means place the node according to the ordering in data, the closer the nodes in data ordering, the closer the nodes will be placed. `sortBy` also can be assigned to any name of property in nodes data, such as `'cluster'`, `'name'` and so on (make sure the property exists in the data) | +| sortStrength | Number | 10 | 10 | The strength to sort the nodes in the same circle. Larger number means place the nodes with smaller distance of `sortBy` more closely. Takes effect only when `sortBy` is not `undefined` | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | + +### MDS + +img
**Description**: MDS (Multidimensional scaling) is used for project high dimensional data onto low dimensional space.
**API**: [MDS API](/en/docs/api/graphLayout/mds)
**Configuration**: + +| Name | Type | Example | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| linkDistance | Number | 50 | 50 | The edge length | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | + +### Dagre + +img
**Description**: An hierarchical layout.
**API**: [Dagre API](/en/docs/api/graphLayout/dagre)
**Configuration**: + +| Name | Type | Example/Options | Default | Description | +| --- | --- | --- | --- | --- | +| rankdir | String | 'TB' / 'BT' / 'LR' / 'RL' | 'TB' | The layout direction. T: top; B: bottom; L: left; R: right | +| align | String | 'UL' / 'UR' / 'DL' / 'DR' / undefined | undefined | The alignment of the nodes. `undefined` by default, align to the center. U: upper; D: down; L: left; R: right | +| nodesep | Number | 40 | 50 | The separation between nodes with unit px. When `rankdir` is `'TB'` or `'BT'`, `nodesep` represents the horizontal separations between nodes; When `rankdir` is `'LR'` or `'RL'`, `nodesep` represents the vertical separations between nodes | +| ranksep | Number | 40 | 50 | The separations between adjacent levels with unit px. When `rankdir` is `'TB'` or `'BT'`, `ranksep` represents the vertical separations between adjacent levels; when `rankdir` is `'LR'` or `'RL'`, `rankdir` represents the horizontal separations between adjacent levels | +| nodesepFunc

| Function | d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | undefined | The function for node separation with unit px. You can adjust the separations between different node pairs by using this function instead of `nodesep`. When `rankdir` is `'LR'` or `'RL'`, `nodesep` represents the vertical separations between nodes. The priority of `nodesepFunc` is higher than `nodesep`, which means if `nodesepFunc` is assigned, the `nodesep` will not take effect | +| ranksepFunc

| Function | d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | undefined | The function for level separation with unit px. You can adjust the separations between different adjacent levels by using this function instead of `ranksep`. When `rankdir` is `'TB'` or `'BT'`, `ranksep` represents the vertical separations between adjacent levels; when `rankdir` is `'LR'` or `'RL'`, `rankdir` represents the horizontal separations between adjacent levels. The priority of `ranksepFunc` is higher than `ranksep`, which means if `ranksepFunc` is assigned, the `ranksep` will not take effect | +| controlPoints | Boolean | true | true | Whether to keep the control points of layout | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | +| sortByCombo | Boolean | true / false | false | Whether to sort the nodes in a level according to the `comboId` in their data. Enable `sortByCombo` to avoid combo overlappings | + +### Concentric + +img
Tips: Concentric layout in G6 refers to cytoscape.js, we obey the MIT license
**Description**: Arranges the nodes on several concentric circles.
**API**: [Concentric API](/en/docs/api/graphLayout/concentric)
**Configuration**: + +| Name | Type | Example/Options | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| nodeSize | Number | 30 | 30 | The diameter of the node. It is used for preventing node overlappings | +| minNodeSpacing | Number | 10 | 10 | The minimum separation between adjacent circles | +| preventOverlap | Boolean | false | false | Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned. If the size in node data does not exist either, `nodeSize` is assigned to 30 by default | +| sweep | Number | Math.PI | undefined | How many radians should be between the first and last node (defaults to full circle). If it is undefined, 2 _ Math.PI _ (1 - 1 / | level.nodes | ) will be used, where level.nodes is nodes set of each level, | level.nodes | is the number of nodes of the level | +| equidistant | Boolean | false | false | Whether levels have an equal radial distance between them, may cause bounding box overflow | +| startAngle | Number | 3.14 | 3 / 2 \* Math.PI | Where nodes start in radians | +| clockwise | Boolean | false | false | Place the nodes in clockwise or not | +| maxLevelDiff | Number | 0.5 | undefined | The sum of concentric values in each level. If it is undefined, maxValue / 4 will take place, where maxValue is the max value of ordering properties. For example, if `sortBy='degree'`, maxValue is the max degree value of all the nodes | +| sortBy | String | 'property1' / 'weight' / ... | undefined | Order the nodes according to this parameter. It is the property's name of node. The node with higher value will be placed to the center. If it is undefined, the algorithm will order the nodes by their degree
| +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | + +### Grid + +img
Tips: Concentric layout in G6 refers to cytoscape.js, we obey the MIT license.
**Description**: Orders the nodes according to the configurations and arranged them onto grid.
**API**: [Grid API](/en/docs/api/graphLayout/grid)
**Configuration**: + +| Name | Type | Example/Options | Default | Description | +| --- | --- | --- | --- | --- | +| begin | Array | [ 0, 0 ] | [ 0, 0 ] | 网格开始位置(左上角) | +| preventOverlap | Boolean | false | false | Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned. If the size in node data does not exist either, `nodeSize` is assigned to 30 by default | +| preventOverlapPadding | Number | 10 | 10 | The minimum padding between nodes to prevent node overlappings. Takes effect when `preventOverlap` is `true` | +| nodeSize | Number | 30 | 30 | The diameter of the node. It is used for preventing node overlappings. | +| condense | Boolean | false | false | Wheter to utilize the minimum space of the canvas. `false` means utilizing the full space, `true` means utilizing the minimum space. | +| rows | Number | 5 | undefined | The row number of the grid. If `rows` is undefined, the algorithm will calculate it according to the space and node numbers automatically | +| cols | Number | 5 | undefined | The column number of the grid. If `cols` is undefined, the algorithm will calculate it according to the space and node numbers automatically | +| sortBy | String | 'property1' / 'weight' / ... | 'degree' | The ordering method for nodes. Smaller the index in the ordered array, more center the node will be placed. If `sortBy` is undefined, the algorithm order the nodes according to their degrees | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | + +### Combo Force + +img
**API**:[Combo Force API](/en/docs/api/graphLayout/comboForce)
**Parameters**: + +| Name | Type | Example/Options | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| maxIteration | Number | 100 | 100 | The maximum iteration number | +| linkDistance | Number / Function | e.g. 1: 50 
e.g. 2:
d => {
  // d is an edge
  if (d.id === 'edge1') {
    return 100;
  }
  return 50;
} | 10 | The edge length | +| nodeStrength | Number / Function | e.g. 1: 10 
e.g. 2:
d => {
  // d is a node
  if (d.id === 'node1') {
    return 10;
  }
  return 30;
} / null | 30 | The strength of node force | +| edgeStrength | Number / Function | e.g. 1: 1 
e.g. 2:
d => {
  // d is a node
  if (d.id === 'node1') {
    return 10;
  }
  return 1;
} | 0.2 | The strength of edge force | +| preventOverlap | Boolean | false | false | Whether to prevent node overlappings and combo overlappings. If it is assign `true`, `preventNodeOverlap` and `preventComboOverlap` will be set to `true`. See the API of `preventNodeOverlap` and `preventComboOverlap` for more detail | +| preventNodeOverlap | Boolean | false | true | Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned | +| preventComboOverlap | Boolean | false | true | Whether to prevent combo overlappings | +| collideStrength | Number | 0.1 | undefined | The unified strength of force for preventing node overlappings and combo overlappings. The range is [0, 1]. If it is not undefined, the `nodeCollideStrength` and `comboCollideStrength` will be set to the same value | +| nodeCollideStrength | Number | 0.4 | 0.5 | The strength of force for preventing node overlappings. The range is [0, 1] | +| comboCollideStrength | Number | 0.4 | 0.5 | The strength of force for preventing combo overlappings. The range is [0, 1] | +| nodeSize | Array / Number | 10 | 10 | The diameter of the node. It is used for preventing node overlappings. If `nodeSize` is not assigned, the size property in node data will take effect. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default | +| nodeSpacing

| Number / Function | e.g. 1 : 10
e.g. 2 : 
d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | img
Takes effect when `preventNodeOverlap` or `preventOverlap` is `true`. It is the minimum distance between nodes to prevent node overlappings. It can be a function to define different distances for different nodes (example 2)
| +| comboSpacing

| Number / Function | e.g. 1 : 10
e.g. 2 : 
d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | Takes effect when `preventComboOverlap` or `preventOverlap` is `true`. It is the minimum distance between combos to prevent combo overlappings. It can be a function to define different distances for different combos (example 2)
| +| comboPadding

| Number / Function | e.g. 1 : 10
e.g. 2 : 
d => {
  // d is a node
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | The padding value inside each combo. It is not about rendering, only used for force calculation
| +| alphaDecay | Number | 0.03 | 0.028 | The decay ratio of alpha for convergence. The range is [0, 1]. 0.028 corresponds to 300 iterations | +| alphaMin | Number | 0.03 | 0.001 | The threshold to stop the iteration | +| alpha | Number | 0.1 | 1 | The current alpha of convergence | +| onTick | Function | | {} | The callback function of each iteration | +| onLayoutEnd | Function | | {} | The callback function after layout | +| gravity | Number | | 10 | The gravity, which will affect the compactness of the layout | +| comboGravity | Number | | 30 | The gravity of each combo, which will affect the compactness of each combo | +| optimizeRangeFactor | Number | | 1 | When the distance between two nodes is larger than `optimizeRangeFactor * width`, the forces between them will not be calculated any more. A proper value for `optimizeRangeFactor` will lead to less calculation to optimize the performance of the layout | +| depthAttractiveForceScale | Number | | 0.5 | The scale for adjusting the strength of attractive force between nodes with different depths. The range is [0, 1]. Lager the depth difference, smaller the attractive force strength | +| depthRepulsiveForceScale | Number | | 2 | The scale for adjusting the strength of repulsive force between nodes with different depths. The range is [1, Infinity]. Lager the depth difference, larger the attractive force strength | +| velocityDecay | Number | 0.2 | 0.6 | The decay speed of the moving velocity of nodes for each iteration | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | + +### Combo Combined + +img
**API**:[Combo Combined API](/en/docs/api/graphLayout/comboCombined)
**Parameters**: + +| Name | Type | Example/Options | Default | Description | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout | +| nodeSize | Array / Number | 10 | 10 | The diameter of the node. It is used for preventing node overlappings. If `nodeSize` is not assigned, the size property in node data will take effect. If the size in node data does not exist either, `nodeSize` is assigned to 10 by default | +| spacing | Number / Function | 10 | 0 | Takes effect when the `preventNodeOverlap` or `preventOverlap` is `true`. The minimum distances between nodes and combos to prevent overlappings. It can be a function to assign different values for different items | +| comboPadding | Number / Function | 10 | 10 | The padding inside a Combo, not for rendering but for force calculation. We suggest to assign the corresponding values to the graph config | +| outerLayout | Object | GForce instance | ForceAtlas2 instance | The layout instance for the outer combos. gForce by default. For the parameters, please refer to the corresponding layout docs | +| innerLayout | Object | Concentric instance | Grid instance | The inner layout inside combos. Concentric by default. It should be synchronous algorithm. For the parameters, please refer to the corresponding layout docs | +| workerEnabled | Boolean | true / false | false | Whether to enable the web-worker in case layout calculation takes too long to block page interaction | diff --git a/packages/site/docs/manual/middle/layout/graph-layout.zh.md b/packages/site/docs/manual/middle/layout/graph-layout.zh.md new file mode 100644 index 0000000000..0b70139a40 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/graph-layout.zh.md @@ -0,0 +1,371 @@ +--- +title: 一般图布局 Layout +order: 0 +--- + +## 简介 + +图布局是指图中节点的排布方式,根据图的数据结构不同,布局可以分为两类:一般图布局、树图布局。G6 为这两类图都内置了一些常用的图布局算法。使用内置的图布局可以完成[布局的参数、方法、数据的切换](/zh/docs/manual/middle/layout/layout-mechanism)等。G6 还提供了一般图布局的 [Web-Worker 机制](/zh/docs/manual/middle/layout/webworker),在大规模图布局中使用该机制可以使布局计算不阻塞页面。 + +除了内置布局方法外,一般图布局还支持 [自定义布局](/zh/docs/manual/middle/layout/custom-layout) 机制。 + +事实上,G6 的布局是自由的,内置布局算法仅仅是操作了数据中节点的 `x` 和 `y` 值。因此,除了使用内置布局以及自定义的一般图布局外,用户还可以使用外部图布局算法,计算节点位置后赋值到数据中节点的 `x` 和 `y` 字段上,G6 便可以根据该位置信息进行绘制。 + +本文将逐一介绍内置的布局算法,及其使用方式。 + +## 一般图 Graph 布局方法总览 + +- [Random Layout](#random):随机布局; +- [Force2 Layout](#force2):G6 4.7.0 后支持力导向布局,与 gForce 相比性能更强; +- [GForce Layout](#gForce):G6 4.0 支持的经典力导向布局,支持 GPU 并行计算; +- [Force Layout](#force):引用 d3 的经典力导向布局; +- [Fruchterman Layout](#fruchterman):Fruchterman 布局,一种力导布局; +- [Circular Layout](#circular):环形布局; +- [Radial Layout](#radial):辐射状布局; +- [MDS Layout](#mds):高维数据降维算法布局; +- [Dagre Layout](#dagre):层次布局; +- [Concentric Layout](#concentric):同心圆布局; +- [Grid Layout](#grid):网格布局; +- [Combo Force Layout](#combo-force):*V3.5 新增。*适用于带有 combo 图的力导向布局,推荐有 combo 的图使用该布局。 +- [Combo Combined Layout](#combo-combined):*V4.6 新增。*适用于带有 combo 的图,可自由组合内外布局,默认情况下可以有较好的效果,推荐有 combo 的图使用该布局。 + +## 配置一般图布局 + +用户可以通过在实例化图时使用图的配置项 `layout` 指定布局方法。下面代码在实例化图时设置了布局方法为 `type: 'force'`,即经典力导向图布局。并设置了参数 `preventOverlap: true`  和 `nodeSize: 30`,表示希望节点不重叠。节点大小 `nodeSize` 用于算法中判断节点是否重叠,更多配置项见各布局的配置项。 + +```javascript +const graph = new G6.Graph({ + // ... // 其他配置项 + layout: { + // Object,可选,布局的方法及其配置项,默认为 random 布局。 + type: 'force', + preventOverlap: true, + nodeSize: 30, + // workerEnabled: true, // 是否启用 webworker + // gpuEnabled: true // 是否使用 gpu 版本的布局算法,G6 4.0 支持,目前仅支持 gForce 及 fruchterman + // ... // 其他配置 + }, +}); +``` + +除了每种布局方法各自的配置项外,所有布局方法都可以在上面代码的 `layout` 中配置 `workerEnabled: true` 以开启布局的 web-worker 机制。开启后图的布局计算过程将不会阻塞页面。 + +当实例化图时没有配置布局时: + +- 若数据中节点有位置信息(`x` 和 `y`),则按照数据的位置信息进行绘制; +- 若数据中节点没有位置信息,则默认使用 Random Layout 进行布局。 + +## 一般图布局方法 + +图布局通用 API:[Layout API](/zh/docs/api/graphLayout/guide)。 + +### Random + +img + +
**描述**:随机布局。
**API**:[Random API](/zh/docs/api/graphLayout/random)
**参数**: + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| width | Number | 300 | 图的宽 | | +| height | Number | 300 | 图的高 | | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | + +### Force2 + +img + +
**描述**:G6 4.0 支持力导向布局(由 graphin-force 沉淀,性能更强)。能够更加自由地支持设置节点质量、群组中心力等。当你希望固定某个节点的位置,不受力的影响时,可以在该节点数据中配置 `fx` 与 `fy` 作为固定的坐标。[Force2 布局固定被拖拽节点位置的 Demo](/zh/examples/net/forceDirected#force2Fix)。 +
**API**:[Force API](/zh/docs/api/graphLayout/force2) +
**参数**: + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| animate | boolean | false | false | 是否每次迭代都刷新画布,若为 `true` 则将表现出带有动画逐步布局的效果 | +| linkDistance | number / Function | 示例 1: 50 
示例 2:
d => {
  // d 是一条边
  if (d.id === 'edge1') {
    return 100;
  }
  return 50;
} | 1 | 边长。可以使用回调函数的形式对不同对边定义不同边长(如示例 2) | +| nodeStrength | number / Function | 示例 1: -30 
示例 2:
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return -100;
  }
  return -30;
} / 1000 | 1000 | 节点作用力,正数代表节点之间的斥力作用,负数代表节点之间的引力作用(注意与 'force' 相反)(如示例 2) | +| edgeStrength | number / Function | 示例 1: 1 
示例 2:
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 10;
  }
  return 1;
} | 200 | 边的作用力,默认根据节点的出入度自适应。可以使用回调函数的形式对不同对节点定义不同边作用力(如示例 2) | +| preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 `nodeSize` ,只有设置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测。若未设置 `nodeSize` ,则根据节点数据中的 `size` 进行碰撞检测。若二者都未设置,则默认以 10 为节点大小进行碰撞检测 | +| nodeSize | Array / Number | 20 | undefined | 节点大小(直径)。用于碰撞检测。
若不指定,则根据传入的数据节点中的 `size`  字段计算。若即不指定,节点中也没有 `size`,则默认大小为 10 | +| nodeSpacing | number / Function | 示例 1 : 10
示例 2 : 
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | img
`preventOverlap` 为 `true` 时生效,防止重叠时节点边缘间距的最小值。可以是回调函数,为不同节点设置不同的最小间距,如示例 2 所示
| +| minMovement | number | 0.1 | 0.5 | 当一次迭代的平均/最大/最小(根据`distanceThresholdMode`决定)移动长度小于该值时停止迭代。数字越小,布局越收敛,所用时间将越长 | +| distanceThresholdMode | 'mean' / 'max' / 'min' | 'mean' | 'mean' | `minMovement` 的使用条件,`'mean'` 代表平均移动距离小于 `minMovement` 时停止迭代,`'max'` / `'min'` 代表最大/最小移动距离小于时 `minMovement` 时停时迭代。默认为 `'mean'` | +| maxIteration | number | 500 | 1000 | 最大迭代次数。当迭代次数超过该值,但平均移动长度仍然没有达到 minMovement,也将强制停止迭代 | +| damping | number | 0.99 | 0.9 | 阻尼系数,取值范围 [0, 1]。数字越大,速度降低得越慢 | +| interval | number | 0.05 | 0.02 | 控制每个迭代节点的移动速度 | +| factor | number | 1 | 1 | 斥力系数,数值越大,斥力越大 | +| maxSpeed | number | 10 | 1000 | 一次迭代的最大移动长度 | +| coulombDisScale | number | 0.003 | 0.005 | 库伦系数,斥力的一个系数,数字越大,节点之间的斥力越大 | +| getMass | Function | d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | undefined | 每个节点质量的回调函数,若不指定,则默认使用度数作为节点质量。使用方法与 `nodeSpacing` 类似,每个回调函数返回一个数值作为该节点的质量 | +| getCenter | Function | (d, degree) => {
  // d 是一个节点, degree 为该节点度数
  if (d.degree === 0') {
    return [100, 100, 10]; // x, y, 强度
  }
  return [210, 150, 5]; // x, y, 强度
} | undefined | 每个节点中心力的 x、y、强度的回调函数,若不指定,则没有额外中心力 | +| gravity | number | 20 | 10 | 中心力大小,指所有节点被吸引到 `center` 的力。数字越大,布局越紧凑 | +| centripetalOptions | CentripetalOptions | 见下文 | 见下文 | 详细配置见下文。向心力配置,包括叶子节点、离散点、其他节点的向心中心及向心力大小 | +| leafCluster | boolean | false | false | 是否需要叶子结点聚类,若为 `true`,则 centripetalOptions.single 将为 100;centripetalOptions.leaf 将使用 `getClusterNodeStrength` 返回值;getClusterNodeStrength.center 将为叶子节点返回当前所有叶子节点的平均中心 | +| clustering | boolean | false | false | 是否需要全部节点聚类,若为 `true`,将使用 `nodeClusterBy` 配置的节点数据中的字段作为聚类依据。 centripetalOptions.single、centripetalOptions.leaf、centripetalOptions.others 将使用 `getClusterNodeStrength` 返回值;leaf、centripetalOptions.center 将使用当前节点所属聚类中所有节点的平均中心 | +| nodeClusterBy | string | undefined | undefined | 指定节点数据中的字段名称作为节点聚类的依据,`clustering` 为 `true` 时生效,自动生成 `centripetalOptions`,可配合 `clusterNodeStrength` 使用 | +| clusterNodeStrength | number / Function | node => node.weight | 20 | 配合 `clustering` 和 `nodeClusterBy` 使用,指定聚类向心力的大小 | +| monitor | (params:{ energy: number, nodes: NodeData[], edges: EdgeData[], iterations: number }) => void | undefined | undefined | 每个迭代的监控信息回调,`energy` 表示布局的收敛能量。若配置可能带来额外的计算能量性能消耗,不配置则不计算 | +| onTick | Function | undefined | undefined| 每一次迭代的回调函数 | +| onLayoutEnd | Function | undefined | undefined | 布局完成后的回调函数 | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | + + +`CentripetalOptions` 类型说明: + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| single | number / Function | 2 | 2, | 离散节点(即度数为 0 的节点)受到的向心力大小 | +| leaf | number / Function | 2 | 2 | 叶子节点(即度数为 1 的节点)受到的向心力大小 | +| others | number / Function | 1 | 1 | 除离散节点、叶子节点以外的其他节点(即度数 > 1 的节点)受到的向心力大小 | +| center | Function | (node, nodes, edges) => ({ x: 10, y: 10 }) | 图的中心 | 向心力发出的位置,可根据节点、边的情况返回不同的值 | + +`centripetalOptions` 示例: + +``` +centripetalOptions: { + // single、leaf、others 的函数形式的参数为当前节点数据、所有节点数据、所有边数据 + single: (node, nodes, edges) => node.field1 || 1, + leaf: (node, nodes, edges) => node.field2 || 1, + others: (node, nodes, edges) => node.field3|| 1, + // 参数为当前节点数据、所有节点数据、所有边数据、画布宽度、画布高度 + center: (node, nodes, edges, width, height) => { + if (node.field4) return { x: width / 2, y: height / 2 }; + if (node.field5) return { x: node.field6, y: node.field7 }; + // ... + } +} +``` + + +### GForce + +img + +
**描述**:G6 4.0 支持的经典力导向布局。能够更加自由地支持设置节点质量、群组中心力等。更重要的是,它支持 GPU 并行计算。当你希望固定某个节点的位置,不受力的影响时,可以在该节点数据中配置 `fx` 与 `fy` 作为固定的坐标。[GForce 布局固定被拖拽节点位置的 Demo](/zh/examples/net/forceDirected#gForceFix)。 +
**API**:[Force API](/zh/docs/api/graphLayout/gforce) +
**参数**: + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| linkDistance | Number / Function | 示例 1: 50 
示例 2:
d => {
  // d 是一条边
  if (d.id === 'edge1') {
    return 100;
  }
  return 50;
} | 1 | 边长。可以使用回调函数的形式对不同对边定义不同边长(如示例 2) | +| nodeStrength | Number / Function | 示例 1: -30 
示例 2:
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return -100;
  }
  return -30;
} / 1000 | 1000 | 节点作用力,正数代表节点之间的斥力作用,负数代表节点之间的引力作用(注意与 'force' 相反)(如示例 2) | +| edgeStrength | Number / Function | 示例 1: 1 
示例 2:
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 10;
  }
  return 1;
} | 200 | 边的作用力,默认根据节点的出入度自适应。可以使用回调函数的形式对不同对节点定义不同边作用力(如示例 2) | +| preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 `nodeSize` ,只有设置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测。若未设置 `nodeSize` ,则根据节点数据中的 `size` 进行碰撞检测。若二者都未设置,则默认以 10 为节点大小进行碰撞检测 | +| nodeSize | Array / Number | 20 | undefined | 节点大小(直径)。用于碰撞检测。
若不指定,则根据传入的数据节点中的 `size`  字段计算。若即不指定,节点中也没有 `size`,则默认大小为 10 | +| nodeSpacing | Number / Function | 示例 1 : 10
示例 2 : 
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | img
`preventOverlap` 为 `true` 时生效,防止重叠时节点边缘间距的最小值。可以是回调函数,为不同节点设置不同的最小间距,如示例 2 所示
| +| minMovement | Number | 0.1 | 0.5 | 当一次迭代的平均移动长度小于该值时停止迭代。数字越小,布局越收敛,所用时间将越长 | +| maxIteration | Number | 500 | 1000 | 最大迭代次数。当迭代次数超过该值,但平均移动长度仍然没有达到 minMovement,也将强制停止迭代 | +| damping | Number | 0.99 | 0.9 | 阻尼系数,取值范围 [0, 1]。数字越大,速度降低得越慢 | +| maxSpeed | Number | 10 | 1000 | 一次迭代的最大移动长度 | +| coulombDisScale | Number | 0.003 | 0.005 | 库伦系数,斥力的一个系数,数字越大,节点之间的斥力越大 | +| getMass | Function | d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | undefined | 每个节点质量的回调函数,若不指定,则默认使用度数作为节点质量。使用方法与 `nodeSpacing` 类似,每个回调函数返回一个数值作为该节点的质量 | +| getCenter | Function | (d, degree) => {
  // d 是一个节点, degree 为该节点度数
  if (d.degree === 0') {
    return [100, 100, 10]; // x, y, 强度
  }
  return [210, 150, 5]; // x, y, 强度
} | undefined | 每个节点中心力的 x、y、强度的回调函数,若不指定,则没有额外中心力 | +| gravity | Number | 20 | 10 | 中心力大小,指所有节点被吸引到 `center` 的力。数字越大,布局越紧凑 | +| onTick | Function | undefined | undefined | 每一次迭代的回调函数 | +| onLayoutEnd | Function | undefined | undefined | 布局完成后的回调函数 | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | +| gpuEnabled | Boolean | true / false | false | 是否启用 GPU 并行计算,G6 4.0 支持。若用户的机器或浏览器不支持 GPU 计算,将会自动降级为 CPU 计算 | + +### Force + +img + +
**描述**:经典力导向布局。当你希望固定某个节点的位置,不受力的影响时,可以在该节点数据中配置 `fx` 与 `fy` 作为固定的坐标。[Force 布局固定被拖拽节点位置的 Demo](/zh/examples/net/forceDirected#basicForceDirectedDragFix)。 +
**API**:[Force API](/zh/docs/api/graphLayout/force) +
**参数**:与 d3.js 的力导布局参数相对应。 + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| linkDistance | Number / Function | 示例 1: 50 
示例 2:
d => {
  // d 是一条边
  if (d.id === 'edge1') {
    return 100;
  }
  return 50;
} | 50 | 边长。可以使用回调函数的形式对不同对边定义不同边长(如示例 2) | +| nodeStrength | Number / Function | 示例 1: -30 
示例 2:
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return -100;
  }
  return -30;
} / null | -30 | 节点作用力,正数代表节点之间的引力作用,负数代表节点之间的斥力作用。可以使用回调函数的形式对不同对节点定义不同节点作用力(如示例 2) | +| edgeStrength | Number / Function | 示例 1: 1 
示例 2:
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 10;
  }
  return 1;
} | null | 边的作用力,范围是 0 到 1,默认根据节点的出入度自适应。可以使用回调函数的形式对不同对节点定义不同边作用力(如示例 2) | +| preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 `nodeSize` ,只有设置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测。若未设置 `nodeSize` ,则根据节点数据中的 `size` 进行碰撞检测。若二者都未设置,则默认以 10 为节点大小进行碰撞检测 | +| nodeSize | Array / Number | 20 | undefined | 节点大小(直径)。用于碰撞检测。
若不指定,则根据传入的数据节点中的 `size`  字段计算。若即不指定,节点中也没有 `size`,则默认大小为 10 | +| nodeSpacing | Number / Function | 示例 1 : 10
示例 2 : 
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | img
`preventOverlap` 为 `true` 时生效,防止重叠时节点边缘间距的最小值。可以是回调函数,为不同节点设置不同的最小间距,如示例 2 所示
| +| alphaDecay | Number | 0.03 | 0.028 | 迭代阈值的衰减率。范围 [0, 1],0.028 对应迭代数为 300 | +| alphaMin | Number | 0.03 | 0.001 | 停止迭代的阈值 | +| alpha | Number | 0.1 | 0.3 | 当前阈值 | +| collideStrength | Number | 0.8 | 1 | 防止重叠的力强度,范围 [0, 1] | +| clustering | Boolean | false | false | 是否按照聚类信息布局 | +| clusterNodeStrength | Number | -1 | -0.8 | 聚类节点作用力。负数代表斥力 | +| clusterEdgeStrength | Number | 0.1 | 0.2 | 聚类边作用力 | +| clusterEdgeDistance | Number | 100 | 50 | 聚类边长度 | +| clusterNodeSize | Number | 10 | 15 | 聚类节点大小 / 直径,直径越大,越分散 | +| clusterFociStrength | Number | 0.8 | 0.5 | 用于 foci 的力 | +| forceSimulation | Object | | null | 自定义 force 方法,若不指定,则使用 d3 的方法。 | +| onTick | Function | | {} | 每一次迭代的回调函数 | +| onLayoutEnd | Function | | {} | 布局完成后的回调函数 | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | + +### Fruchterman + +img + +
**描述**:Fruchterman 布局,一种力导布局。当你希望固定某个节点的位置,不受力的影响时,可以在该节点数据中配置 `fx` 与 `fy` 作为固定的坐标。[Fruchterman 布局固定被拖拽节点位置的 Demo](/zh/examples/net/fruchtermanLayout#fructhermanFix)。 +
**API**:[Fruchterman API](/zh/docs/api/graphLayout/fruchterman) +
**参数**: + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| maxIteration | Number | 1000 | 1000 | 最大迭代次数 | +| gravity | Number | 10 | 10 | 重力大小,影响布局的紧凑程度 | +| speed | Number | 1 | 1 | 每次迭代节点移动的速度。速度太快可能会导致强烈震荡 | +| clustering | Boolean | false | false | 是否按照聚类布局 | +| clusterGravity | Number | 30 | 10 | 聚类内部的重力大小,影响聚类的紧凑程度 | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | +| gpuEnabled | Boolean | true / false | false | 是否启用 GPU 并行计算,G6 4.0 支持 | + +### Circular + +img +img +img + +
**描述**:环形布局。
**API**:[Circular API](/zh/docs/api/graphLayout/circular)
**参数**: + +| 参数名 | 类型 | 示例/可选值 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| radius | Number | 50 | null | 圆的半径。若设置了 `radius`,则 `startRadius` 与 `endRadius` 不生效 | +| startRadius | Number | 10 | null | 螺旋状布局的起始半径 | +| endRadius | Number | 100 | null | 螺旋状布局的结束半径 | +| clockwise | Boolean | true | true | 是否顺时针排列 | +| divisions | Number | 3 | 1 | 节点在环上的分段数(几个段将均匀分布),在 `endRadius - startRadius != 0` 时生效 | +| ordering | String | null | 'topology' | 'degree' | null | 节点在环上排序的依据。默认 null 代表直接使用数据中的顺序。'topology' 按照拓扑排序。'degree' 按照度数大小排序 | +| angleRatio | Number | 1 | 1 | 从第一个节点到最后节点之间相隔多少个 2\*PI | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | + +### Radial + +img + +
**描述**:辐射状布局。
**API**:[Radial API](/zh/docs/api/graphLayout/radial)
**参数**: + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| linkDistance | Number | 50 | 50 | 边长 | +| maxIteration | Number | 1000 | 1000 | 停止迭代到最大迭代数 | +| focusNode | String / Object | 'node1' | null | 中心点,默认为数据中第一个节点。可以传入节点 id 或节点本身。 | +| unitRadius | Number | 10 | 100 | 每一圈距离上一圈的距离。默认填充整个画布,即根据图的大小决定 | +| preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 `nodeSize` ,只有设置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测。

若未设置 `nodeSize`,则将会根据数据中节点的 `size` 字段数值进行碰撞检测计算。若二者皆未设置,则以节点大小为 `10` 进行计算。 | +| maxPreventOverlapIteration | Number | 500 | 200 | 防止重叠步骤的最大迭代次数 | +| nodeSize | Number | 10 | 10 | 节点大小(直径)。用于防止节点重叠时的碰撞检测。

若未设置则使用数据中节点的 `size` 字段数值进行碰撞检测计算。若二者皆未设置,则以节点大小为 `10` 进行计算。 | +| nodeSpacing
| Number / Function | 示例 1 : 10
示例 2 : 
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | img
`preventOverlap` 为 `true` 时生效,防止重叠时节点边缘间距的最小值。可以是回调函数,为不同节点设置不同的最小间距,如示例 2 所示
| +| strictRadial | Boolean | true | false | 是否必须是严格的 radial 布局,即每一层的节点严格布局在一个环上。`preventOverlap` 为 `true` 时生效。详见 [Radial-strictRadial API](/zh/docs/api/graphLayout/radial#layoutcfgstrictradial)
- 当 `preventOverlap` 为 `true`,且 `strictRadial` 为 `false` 时,有重叠的节点严格沿着所在的环展开,但在一个环上若节点过多,可能无法完全避免节点重叠。
- 当 `preventOverlap` 为 `true`,且 `strictRadial` 为 `true`  时,允许同环上重叠的节点不严格沿着该环布局,可以在该环的前后偏移以避免重叠。
| +| sortBy | String | 'data' / 'cluster' | undefined | 同层节点布局后相距远近的依据。默认 `undefined` ,表示根据数据的拓扑结构(节点间最短路径)排布,即关系越近/点对间最短路径越小的节点将会被尽可能排列在一起;`'data'` 表示按照节点在数据中的顺序排列,即在数据顺序上靠近的节点将会尽可能排列在一起;也可以指定为节点数据中的某个字段名,例如 `'cluster'`、`'name'` 等(必须在数据中存在) | +| sortStrength | Number | 10 | 10 | 同层节点根据 `sortBy` 排列的强度,数值越大,`sortBy` 指定的方式计算出距离越小的越靠近。`sortBy` 不为 `undefined` 时生效 | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | + +### MDS + +img
**描述**:高维数据降维算法布局。
**API**:[MDS API](/zh/docs/api/graphLayout/mds)
**参数**: + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| linkDistance | Number | 50 | 50 | 边长 | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | + +### Dagre + +img
**描述**:层次布局。
**API**:[Dagre API](/zh/docs/api/graphLayout/dagre)
**参数**: + +| 参数名 | 类型 | 示例/可选值 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| rankdir | String | 'TB' / 'BT' / 'LR' / 'RL' | 'TB' | layout 的方向。T:top;B:bottom;L:left;R:right | +| align | String | 'UL' / 'UR' / 'DL' / 'DR' / undefined | undefined | 节点对齐方式。默认值是 `undefined`,代表对齐到中心。U:upper;D:down;L:left;R:right | +| nodesep | Number | 40 | 50 | 在 `rankdir` 为 `'TB'` 或 `'BT'` 时代表节点水平间距(px);在 `rankdir` 为 `'LR'` 或 `'RL'` 时代表节点的竖直间距。优先级低于 `nodesepFunc` | +| ranksep | Number | 40 | 50 | 层间距(px)。在`rankdir` 为 `'TB'` 或 `'BT'` 时是竖直方向相邻层间距;在`rankdir` 为 `'LR'` 或 `'RL'` 时代表水平方向相邻层间距。优先级低于 `ranksepFunc` | +| nodesepFunc

| Function | d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | undefined | 节点水平间距(px)的回调函数,通过该参数可以对不同节点设置不同的节点间距。在`rankdir` 为 'TB' 或 'BT' 时是节点的水平间距;在`rankdir` 为 'LR' 或 'RL' 时是节点的竖直间距。优先级高于 `nodesep`,即若设置了 `nodesepFunc`,则 `nodesep` 不生效 | +| ranksepFunc

| Function | d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | undefined | 层间距(px)的回调函数,通过该参数可以对不同节点设置不同的层间距。在`rankdir` 为 'TB' 或 'BT' 时是竖直方向相邻层间距;在`rankdir` 为 'LR' 或 'RL' 时代表水平方向相邻层间距。优先级高于 `ranksep`,即若设置了 `ranksepFunc`,则 `ranksep` 不生效 | +| controlPoints | Boolean | true | true | 是否保留布局连线的控制点 | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | +| sortByCombo | Boolean | true / false | false | 同一层节点是否根据每个节点数据中的 `comboId` 进行排序,以防止 combo 重叠 | + +### Concentric + +img
注:该算法参考 cytoscape.js,遵守 MIT 开源协议。
**描述**:同心圆布局。
**API**:[Concentric API](/zh/docs/api/graphLayout/concentric)
**参数**: + +| 参数名 | 类型 | 示例/可选值 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| nodeSize | Number | 30 | 30 | 节点大小(直径)。用于防止节点重叠时的碰撞检测 | +| minNodeSpacing | Number | 10 | 10 | 环与环之间最小间距,用于调整半径 | +| preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 `nodeSize` ,只有设置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测。若未设置 `nodeSize` ,则将根据节点数据中的 `size` 进行碰撞检测。若二者都未设置,则默认以 30 为节点大小进行碰撞检测 | +| sweep | Number | Math.PI | undefined | 第一个节点与最后一个节点之间的弧度差 | +| equidistant | Boolean | false | false | 环与环之间的距离是否相等 | +| startAngle | Number | 3.14 | 3 / 2 \* Math.PI | 开始放置节点的弧度 | +| clockwise | Boolean | false | false | 是否按照顺时针顺序 | +| maxLevelDiff | Number | 0.5 | undefined | 每一层同心值的求和。若为 undefined,则将会被设置为 maxValue / 4 ,其中 maxValue 为最大的排序依据的属性值。例如,若 sortBy='degree',则 maxValue 为所有节点中度数最大的节点的度数 | +| sortBy | String | 'degree' / 'property1' / 'weight' / ... | undefined | 指定的节点排序的依据(节点属性名)。该属性值高的放在中心。如果是 `sortBy` 为 `undefined` 则会计算节点度数,度数最高的放在中心。
| +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | + +### Grid + +img
注:该算法参考 cytoscape.js,遵守 MIT 开源协议。
**描述**:网格布局。
**API**:[Grid API](/zh/docs/api/graphLayout/grid)
**参数**: + +| 参数名 | 类型 | 示例/可选值 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| begin | Array | [ 0, 0 ] | [ 0, 0 ] | 网格开始位置(左上角) | +| preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 `nodeSize` ,只有设置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测。若未设置 `nodeSize` ,则将根据节点数据中的 `size` 进行碰撞检测。若二者都未设置,则默认以 30 为节点大小进行碰撞检测 | +| preventOverlapPadding | Number | 10 | 10 | 避免重叠时节点的间距 padding。`preventOverlap` 为 `true` 时生效 | +| nodeSize | Number | 30 | 30 | 节点大小(直径)。用于防止节点重叠时的碰撞检测 | +| condense | Boolean | false | false | 为 `false` 时表示利用所有可用画布空间,为 `true` 时表示利用最小的画布空间 | +| rows | Number | 5 | undefined | 网格的行数,为 undefined 时算法根据节点数量、布局空间、`cols`(若指定)自动计算 | +| cols | Number | 5 | undefined | 网格的列数,为 undefined 时算法根据节点数量、布局空间、`rows`(若指定)自动计算 | +| sortBy | String | 'degree' / 'property1' / 'weight' / ... | 'degree' | 指定排序的依据(节点属性名),数值越高则该节点被放置得越中心。若为 undefined,则会计算节点的度数,度数越高,节点将被放置得越中心 | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | + +### Combo Force + +img
**API**:[Combo Force API](/zh/docs/api/graphLayout/comboForce)
**参数**: + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| maxIteration | Number | 100 | 100 | 最大迭代次数 | +| linkDistance | Number / Function | 示例 1: 50 
示例 2:
d => {
  // d 是一条边
  if (d.id === 'edge1') {
    return 100;
  }
  return 50;
} | 10 | 边长。可以使用回调函数的形式对不同对边定义不同边长(如示例 2) | +| nodeStrength | Number / Function | 示例 1: 10 
示例 2:
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 10;
  }
  return 30;
} / null | 30 | 节点作用力 | +| edgeStrength | Number / Function | 示例 1: 1 
示例 2:
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 10;
  }
  return 1;
} | 0.2 | 边的作用力 | +| preventOverlap | Boolean | false | false | 是否防止节点之间以及 combo 之间的重叠,若开启,则 `preventNodeOverlap` 与 `preventComboOverlap` 将均被开启。详见 `preventNodeOverlap` 与 `preventComboOverlap` 介绍 | +| preventNodeOverlap | Boolean | false | true | 是否防止节点之间的重叠。必须配合下面属性 `nodeSize` 或节点数据中的 `size` 属性,只有在数据中设置了 `size` 或在该布局中配置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测 | +| preventComboOverlap | Boolean | false | true | 是否防止 combo 之间的重叠 | +| collideStrength | Number | 0.1 | undefined | 统一设置防止节点之间以及 combo 之间重叠的力强度,范围 [0, 1]。若 `collideStrength` 不为 `undefined`,则 `nodeCollideStrength` 与 `comboCollideStrength` 将均被设置为统一的值 | +| nodeCollideStrength | Number | 0.4 | 0.5 | 设置防止节点之间重叠的力强度,范围 [0, 1] | +| comboCollideStrength | Number | 0.4 | 0.5 | 防止 combo 之间重叠的力强度,范围 [0, 1] | +| nodeSize | Array / Number | 10 | 10 | 节点大小(直径)。用于碰撞检测。若不指定,则根据传入的节点的 size 属性计算。若即不指定,节点中也没有 `size`,则默认大小为 `10` | +| nodeSpacing | Number / Function | 示例 1 : 10
示例 2 : 
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | img
`preventNodeOverlap` 或 `preventOverlap` 为 `true` 时生效, 防止重叠时节点边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距, 如示例 2 所示
| +| comboSpacing

| Number / Function | 示例 1 : 10
示例 2 : 
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | `preventComboOverlap` 或 `preventOverlap` 为 `true` 时生效, 防止重叠时 combo 边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距, 如示例 2 所示
| +| comboPadding

| Number / Function | 示例 1 : 10
示例 2 : 
d => {
  // d 是一个节点
  if (d.id === 'node1') {
    return 100;
  }
  return 10;
} | 0 | Combo 内部的 padding 值,不用于渲染,仅用于计算力。推荐设置为与视图上 combo 内部 padding 值相同的值
| +| alphaDecay | Number | 0.03 | 0.028 | 迭代阈值的衰减率。范围 [0, 1],0.028 对应迭代数为 300 | +| alphaMin | Number | 0.03 | 0.001 | 停止迭代的阈值 | +| alpha | Number | 0.1 | 1 | 当前阈值 | +| onTick | Function | | {} | 每一次迭代的回调函数 | +| onLayoutEnd | Function | | {} | 布局完成后的回调函数 | +| gravity | Number | | 10 | 重力的大小,影响布局的紧凑程度 | +| comboGravity | Number | | 30 | 每个 combo 内部的重力大小,影响聚类的紧凑程度 | +| optimizeRangeFactor | Number | | 1 | 优化计算性能,两节点间距超过 `optimizeRangeFactor * width` 则不再计算斥力和重叠斥力。通过合理设置该参数可以较少计算量 | +| depthAttractiveForceScale | Number | | 0.5 | 根据边两端节点层级差距的调整引力的系数的因子,取值范围 [0, 1]。层级差距越大,引力越小 | +| depthRepulsiveForceScale | Number | | 2 | 根据边两端节点层级差距的调整斥力系数的因子,取值范围 [1, Infinity]。层级差距越大,斥力越大 | +| velocityDecay | Number | 0.4 | 0.6 | 每个迭代节点运动速度衰减参数 | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | + +### Combo Combined + +img
**API**:[Combo Combined API](/zh/docs/api/graphLayout/comboCombined)
**参数**: + +| 参数名 | 类型 | 示例 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 | +| nodeSize | Array / Number | 10 | 10 | 节点大小(直径)。用于碰撞检测。若不指定,则根据传入的节点的 size 属性计算。若即不指定,节点中也没有 `size`,则默认大小为 `10` | +| spacing | Number / Function | 10 | 0 | `preventNodeOverlap` 或 `preventOverlap` 为 `true` 时生效, 防止重叠时节点/ combo 边缘间距的最小值。可以是回调函数, 为不同节点设置不同的最小间距 | +| comboPadding | Number / Function | 10 | 10 | Combo 内部的 padding 值,不用于渲染,仅用于计算力。推荐设置为与视图上 combo 内部 padding 值相同的值 | +| outerLayout | Object | GForce 实例 | ForceAtlas2 实例 | 最外层的布局算法,需要使用同步的布局算法,默认为 gForce。具体参数详见被使用布局的文档 | +| innerLayout | Object | Concentric 实例 | Grid 实例 | combo 内部的布局算法,默认为 concentric。具体参数详见被使用布局的文档 | +| workerEnabled | Boolean | true / false | false | 是否启用 web-worker 以防布局计算时间过长阻塞页面交互 | \ No newline at end of file diff --git a/packages/site/docs/manual/middle/layout/layout-mechanism.en.md b/packages/site/docs/manual/middle/layout/layout-mechanism.en.md new file mode 100644 index 0000000000..42ecd3a517 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/layout-mechanism.en.md @@ -0,0 +1,125 @@ +--- +title: Layout Transformation Mechanism +order: 2 +--- + +G6 provides two layout transformations: + +- `updateLayout(params)`: Layout methods and configurations transformation; +- `changeData()`: Data transformation. + +### Layout Methods and Configuration Transformation + +**Interface Definition:** + +```javascript +/** + * Change the layout or its configurations + * @param {String | object} cfg New layout configurations + * If the cfg is a String or an object with the property type, and the type is different from the current layout method, the layout method will be changed into the new one; + * Only change the configurations for the current layout otherwise + */ +updateLayout(cfg); +``` + +**Change the Layout Method:**
If the `cfg` is a `String` or an object with property `type`, and the type is different from the current layout method, the layout method will be changed into the new one; + +**Change the Configurations Only:**
If the `cfg` is an object without property `type`, or the `type` is the same as the current layout method, only the configurations for the current layout will be changed. + +### Data Transformation + +**Interface Definition:** + +```javascript +/** + * Change the source data, render the graph with new data + * @param {object} data source data + * @return {object} this + */ +changeData(data); +``` + +### Transformation Example + +#### Expected Effect + +In the first stage, the graph is arranged by random layout. Transform to force layout with node overlappings after 2000ms, force layout without node overlappings after 4000ms, change data to `data2` after 6000ms.
img + +#### Complete Code + +```html + + + + + Tutorial Layout Demo + + +
+ + + + + +``` diff --git a/packages/site/docs/manual/middle/layout/layout-mechanism.zh.md b/packages/site/docs/manual/middle/layout/layout-mechanism.zh.md new file mode 100644 index 0000000000..b01166f086 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/layout-mechanism.zh.md @@ -0,0 +1,125 @@ +--- +title: 布局的切换机制 +order: 2 +--- + +G6 提供了两种关于布局的切换机制: + +- `updateLayout(params)`:布局方法或参数的切换; +- `changeData()`:数据的切换。 + +## 布局方法或参数切换 + +**接口定义:** + +```javascript +/** + * 更换布局或布局参数 + * @param {String | object} cfg 新布局配置项 + * 若 cfg 为 String 或含有 type 字段,且与之前的布局方法不同时将会更换布局 + * 否则只是更新原有布局的参数 + */ +updateLayout(cfg); +``` + +**布局方法切换:**
若参数  `cfg` 为 `String` 或是含有 `type` 字段的对象,且与之前的布局方法名不同时将会更换布局。 + +**布局参数切换:**
若参数  `cfg`  是对象且其中不含有 `type` 字段,或指定的布局方法名称与之前的布局方法相同,则保持原有布局方法,仅更新该布局的参数。 + +## 数据切换 + +**接口定义:** + +```javascript +/** + * 更改源数据,根据新数据重新渲染视图 + * @param {object} data 源数据 + * @return {object} this + */ +changeData(data); +``` + +## 切换示例 + +### 期待效果 + +初始化时使用默认 random 布局,2000 ms 后更换为允许节点重叠的 force 布局,4000 ms 后更换为不允许节点重叠的 force 布局,6000 ms 后更换数据为 `data2`。
img + +### 完整代码 + +```html + + + + + Tutorial Layout Demo + + +
+ + + + + +``` diff --git a/packages/site/docs/manual/middle/layout/sub-layout-pipe.en.md b/packages/site/docs/manual/middle/layout/sub-layout-pipe.en.md new file mode 100644 index 0000000000..0ba27a37e7 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/sub-layout-pipe.en.md @@ -0,0 +1,53 @@ +--- +title: Sublayout Pipeline +order: 4 +--- + +## Sublayout Pipeline + +**Supports by v4.3.0 and latter versions**. Sublayout pipeline supports several sublayouts on different subgraphs by configuring `Graph.layout`. + +img + +### Usage + +You can configure `layout.pipes` array when initializing the graph instance. Each item in the array is a sublayout pipe, and it contains the infomation about the layout type(`type`), configurations for this layout type, and node filtering function (`nodesFilter`). NOTICE that, if some nodes belong to several sublayouts in the same time, the result positions of these nodes will follow the last sublayout. + +The format of the `layout.pipes`: + +```javascript +type Pipes = + { + // the name of the layout method for this subgraph + type: 'random' | 'radial' | 'mds' | 'circular' | 'fruchterman' | 'force' | 'gForce' | 'dagre' | 'concentric' | 'grid' | 'forceAtlas2', + // node filtering function, the parameter is the node data, and it returns a boolean to indicate if the node belongs to this subgraph + nodesFilter: (node: NodeData) => boolean; + ... // the configurations for this layout method, refer to the docs for different layout method pls + }[]; +``` + +Usage demo: + +```javascript +// configure the layout.pipes when initializing the graph instance +const graph = new G6.Graph({ + // ... // other graph configurations + layout: { + pipes: [ + { + // the name of the layout method for this subgraph + type: 'circular', + // indicate if the node belongs to the subgraph + nodesFilter: (node) => node.subGraphId === '1', + // ... other configurations for this layout method + }, + { + type: 'grid', + nodesFilter: (node) => node.subGraphId === '2', + // other configurations for this layout method + begin: [100, 0], + } + ] + }, +}); +``` diff --git a/packages/site/docs/manual/middle/layout/sub-layout-pipe.zh.md b/packages/site/docs/manual/middle/layout/sub-layout-pipe.zh.md new file mode 100644 index 0000000000..57e3f7a2c0 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/sub-layout-pipe.zh.md @@ -0,0 +1,54 @@ +--- +title: 流水线子图布局 +order: 4 +--- + +## 流水线子图布局 + +**v4.3.0 新增**,支持在 Graph.layout 中同时配置多个子图布局。 + +img + + +### 使用方法 + +在实例化图时配置 layout.pipes 数组,指定多个子图布局的布局类型(`type`)、布局参数、节点过滤函数(`nodesFilter`)。值得注意的是,若某些节点同时属于不同的子图(即这些节点在不同的子图的 `nodesFilter` 配置都返回为 true),则这些节点位置的计算将按照 pipes 数组顺序后者覆盖前者。 + +`layout.pipes` 的数据类型如下: + +```javascript +type Pipes = + { + // 该子图所使用的布局类型 + type: 'random' | 'radial' | 'mds' | 'circular' | 'fruchterman' | 'force' | 'gForce' | 'dagre' | 'concentric' | 'grid' | 'forceAtlas2', + // 节点的筛选器,参数为节点数据,返回布尔值代表该节点是否在该子图中 + nodesFilter: (node: NodeData) => boolean; + ... // 布局对应的参数,详见各个布局的参数 + }[]; +``` + +使用示例: + +```javascript +// 在实例化图时配置 layout.pipes +const graph = new G6.Graph({ + // ... // 其他配置项 + layout: { + pipes: [ + { + // 该子图所使用的布局类型 + type: 'circular', + // 根据节点的某个字段判断是否属于该子图 + nodesFilter: (node) => node.subGraphId === '1', + // ... 可配置该 circular 布局的参数,详见各布局文档 + }, + { + type: 'grid', + nodesFilter: (node) => node.subGraphId === '2', + // 该 grid 布局的其他参数 + begin: [100, 0], + } + ] + }, +}); +``` diff --git a/packages/site/docs/manual/middle/layout/sub-layout.en.md b/packages/site/docs/manual/middle/layout/sub-layout.en.md new file mode 100644 index 0000000000..189353ea7b --- /dev/null +++ b/packages/site/docs/manual/middle/layout/sub-layout.en.md @@ -0,0 +1,31 @@ +--- +title: Sub-graph Layout +order: 3 +--- + +## Subgraph Layout + +At present, the subgraph layout mechanism is independent to the graph layout. You can instantiate the layout method and load the data of subgraph onto the layout instance. This mechanism allows users to utilize G6's layout algorithms to calculate the node positions, and render the graph with another rendering engine. + +**v4.3.0 newly support subgraph layout pipeline**, which allows several subgraph layouts executed in pipeline configured on Graph.layout. Refer to[Subgraph Layout Piepeline](/en/docs/manual/middle/layout/sub-layout-pipe). + +### Usage + +```javascript +// Instantiate the Layout +const subgraphLayout = new G6.Layout['force']({ + center: [500, 450], +}); + +// Initialize the layout with sugbraph data +subgraphLayout.init({ + nodes: subGraphNodes, + edges: subGraphEdges, +}); + +// Execute the layout +subgraphLayout.execute(); + +// Update the node positions after subgraph layout +graph.positionsAnimate(); +``` diff --git a/packages/site/docs/manual/middle/layout/sub-layout.zh.md b/packages/site/docs/manual/middle/layout/sub-layout.zh.md new file mode 100644 index 0000000000..f930541779 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/sub-layout.zh.md @@ -0,0 +1,31 @@ +--- +title: 子图布局 +order: 3 +--- + +## 子图布局 + +目前,子图布局独立与全局布局的思路,与 graph 不挂钩,直接使用实例化布局方法的方式,灌入子图数据,通过布局将位置写到相应数据中。这种机制还可供外部的全局布局使用,即使不用 G6 渲染,也可以计算节点布局后的位置。 + +**v4.3.0 新增流水线子图布局**,支持在 Graph.layout 中同时配置多个子图布局。详见[流水线子图布局教程](/zh/docs/manual/middle/layout/sub-layout-pipe)。 + +### 使用方法 + +```javascript +// 实例化布局 +const subgraphLayout = new G6.Layout['force']({ + center: [500, 450], +}); + +// 初始化布局,灌入子图数据 +subgraphLayout.init({ + nodes: subGraphNodes, + edges: subGraphEdges, +}); + +// 执行布局 +subgraphLayout.execute(); + +// 图实例根据数据更新节点位置 +graph.positionsAnimate(); +``` diff --git a/packages/site/docs/manual/middle/layout/tree-graph-layout.en.md b/packages/site/docs/manual/middle/layout/tree-graph-layout.en.md new file mode 100644 index 0000000000..9f9d25cd74 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/tree-graph-layout.en.md @@ -0,0 +1,101 @@ +--- +title: TreeGraph Layout +order: 1 +--- + +## Introduction + +Graph layouts are the algorithms arranging the node positions to obtain a understandable visualizaiton. According to the differences of data strucutre, the layouts can be categorized into: general graph layout and tree graph layout. There are several layout algorithms for them respectively. By utilizing the built-in layouts, [Translating the layouts and their configurations, translating the data](/en/docs/manual/middle/layout/layout-mechanism) can be achieved. Besides, G6 provides the [Web-Worker](/en/docs/manual/middle/layout/webworker) for general graph layout in case layout calculation takes too long to block page interaction. + +Besides, G6 supports [Custom Layout](/en/docs/manual/middle/layout/custom-layout) mechanism for users to design their own layout algorithm. + +In fact, 'layout' is a free mechanism in G6. The built-in layouts only calculate and manipulate the `x` and `y` in node data. In other word, users can assign `x` and `y` to nodes by any other ways including the algorithms from the third-party libraries. Once G6 find the `x` and `y` information on data, it will render the graph according to it. + +In order to handle the tree data structure, G6 extends Graph to TreeGraph. Refer to: [TreeGraph API](/en/docs/api/treeGraphLayout/guide). TreeGraph is appropriate for visualizing hierarchy data. In this ducoment, we will introduce the TreeGraph layout algorithms in detail. + +## TreeGraph Layouts Overview + +- [CompactBox Layout](#compactbox); +- [Dendrogram Layout](#dendrogram): Arrange the leaves on the same level; +- [Indented Layout](#indented); +- [Mindmap Layout](#mindmap). + +## Configure the TreeGraph + +Similar to Graph, assign `layout` to Graph instance to set the layout for a TreeGraph. The [Expand/Collapse](/en/docs/manual/middle/states/defaultBehavior/#collapse-expand) behavior can be assigned to the TreeGraph by `modes`. + +```javascript +const graph = new G6.TreeGraph({ + container: 'mountNode', + modes: { + default: [ + { + // Assign the collapse/expand behavior + type: 'collapse-expand', + }, + 'drag-canvas', + ], + }, + // Assign the layout + layout: { + type: 'dendrogram', // Layout type + direction: 'LR', // Layout direction is from the left to the right. Options: 'H' / 'V' / 'LR' / 'RL' / 'TB' / 'BT' + nodeSep: 50, // The distance between nodes + rankSep: 100, // The distance between adjacent levels + }, +}); +``` + +## Layouts for TreeGraph + +### compactBox + +**Description**: CompactBox is the default layout for TreeGraph. It will consider the bounding box of each node when layout.
img
**API**: [CompactBox API](/en/docs/api/treeGraphLayout/compactBox)
**Configuration**: + +| Name | Type | Example/Options | Default | Description | +| --- | --- | --- | --- | --- | +| direction | String | 'TB' / 'BT' / 'LR' / 'RL' / 'H' / 'V' | 'LR' | The direction of layout.
- TB —— Root is on the top, layout from the top to the bottom
- BT —— Root is on the bottom, layout from the bottom to the top
img     img
(Left)TB. (Right)BT.
- LR —— Root is on the left, layout from the left to the right
- RL —— Root is on the right, layout from the right to the left
img             img
(Left)LR. (Right)RL.
- H —— Root is on the middle, layout in horizontal symmetry.
- V —— Root is on the middle, layout in vertical symmetry.
img          img
> (Left)H. (Right)V. | +| getId | Function | (d) => {
  // d is a node
  return d.id + 'node';
} | undefined | Sets the id for each node | +| getHeight | Function | (d) => {
  // d is a node
  return 10;
} | undefined | The height of each node | +| getWidth | Function | (d) => {
  // d is a node
  return 20;
} | undefined | he width of each node | +| getVGap | Function | (d) => {
  // d is a node
  return 100;
} | undefined | The vertical separation of nodes | +| getHGap | Function | (d) => {
// d is a node
  return 50;
} | undefined | The horizontal separation of nodes | +| radial | Boolean | true | false | If layout the graph in radial style. If `radial` is `true`, we recommend to set `direction` to `'LR'` or `'RL'`: img | + +### dendrogram + +**Description**: Arranges all the leaves on the same level. It is appropriate for hierarchical clustering. It does not consider the node size, which will be regarded as 1 px.
img
**API**: [Dendrogram API](/en/docs/api/treeGraphLayout/dendrogram)
**Configuration**: + +| Name | Type | Example/Options | Default | Description | +| --- | --- | --- | --- | --- | +| direction | String | 'TB' / 'BT' / 'LR' / 'RL' / 'H' / 'V' | 'LR' | The direction of layout.
- TB —— Root is on the top, layout from the top to the bottom
- BT —— Root is on the bottom, layout from the bottom to the top
imgimg
> (Left)TB. (Right)BT.
- LR —— Root is on the left, layout from the left to the right
- RL —— Root is on the right, layout from the right to the left
imgimg
> (Left)LR. (Right)RL.
- H —— Root is on the middle, layout in horizontal symmetry.
- V —— Root is on the middle, layout in vertical symmetry.
imgimg
> (Left)H. (Right)V. | +| nodeSep | Number | 50 | 0 | Node separation | +| rankSep | Number | 100 | 0 | Level separation | +| radial | Boolean | true | false | Wheter layout the graph in radial style. If `radial` is `true`, we recommend to set `direction` to `'LR'` or `'RL'`:
img | + +### indented + +**Description**: Indented layout represents the hierarchy by indent between them. Each node will take a row/column. It is appropriate for file directory.
img + +**API**: [Indented API](/en/docs/api/treeGraphLayout/indented)
**Configuration**: + +| Name | Type | Example/Options | Default | Description | +| --- | --- | --- | --- | --- | +| direction | String | 'LR' / 'RL' / 'H' | 'LR' | layout direction
'LR' —— Root is on the left, layout from the left to the right(left image below)
'RL' —— Root is on the right, layout from the right to the left(center image below)
'H' —— Root is on the middle, layout in horizontal symmetry(right image below)
indented1indented2indented3 | +| indent | Number | 80 | 20 | Colunm separation | +| getHeight | Function | (d) => {
  // d is a node
  return 10;
} | undefined | The height of each node | +| getWidth | Function | (d) => {
  // d is a node
  return 20;
} | undefined | The width of each node | +| getSide | Function | (d) => {
  // d is a node
  return 'left';
} | undefined | The callback function of node position(left or right of root node). Only affects the nodes which are connected to the root node directly. And the descendant nodes will be placed according to it | + +### mindmap + +**Description**: Mindmap arranged the nodes with same depth on the same level. Different from compactBox, it does not consider the size of nodes while doing layout.
img
**API**: [Mindmap API](/en/docs/api/treeGraphLayout/mindmap)
**Configuration**: + +| Name | Type | Example/Options | Default | Description | +| --- | --- | --- | --- | --- | +| direction | String | 'H' / 'V' | 'H' | layout direction
- H: Root is on the middle, layout in horizontal symmetry.
img
- V: Root is on the middle, layout in vertical symmetry.
img | +| getHeight | Function | (d) => {
  // d is a node
  return 10;
} | undefined | The height of each node | +| getWidth | Function | (d) => {
  // d is a node
  return 20;
} | undefined | The width of each node | +| getVGap | Function | (d) => {
  // d is a node
  return 100;
} | 18 | The vertical separation of nodes | +| getHGap | Function | (d) => {
  // d is a node
  return 50;
} | 18 | The horizontal separation of nodes | +| getSide | String | Function | (d) => {
  // d is a node
  return 'left';
} / 'right' | The callback function of node position(left or right of root node). Only affects the nodes which are connected to the root node directly. And the descendant nodes will be placed according to it | diff --git a/packages/site/docs/manual/middle/layout/tree-graph-layout.zh.md b/packages/site/docs/manual/middle/layout/tree-graph-layout.zh.md new file mode 100644 index 0000000000..fae1c9d82f --- /dev/null +++ b/packages/site/docs/manual/middle/layout/tree-graph-layout.zh.md @@ -0,0 +1,101 @@ +--- +title: 树图布局 Layout +order: 1 +--- + +## 简介 + +图布局是指图中节点的排布方式,根据图的数据结构不同,布局可以分为两类:一般图布局、树图布局。G6 为这两类图都内置了一些常用的图布局算法。使用内置的图布局可以完成[布局的参数、方法、数据的切换](/zh/docs/manual/middle/layout/layout-mechanism)等。G6 还提供了一般图布局的 [Web-Worker 机制](/zh/docs/manual/middle/layout/webworker),在大规模图布局中使用该机制可以使布局计算不阻塞页面。 + +除了内置布局方法外,一般图布局还支持 [自定义布局](/zh/docs/manual/middle/layout/custom-layout) 机制。 + +事实上,G6 的布局是自由的,内置布局算法仅仅是操作了数据中节点的 `x` 和 `y` 值。因此,除了使用内置布局以及自定义的一般图布局外,用户还可以使用外部图布局算法,计算节点位置后赋值到数据中节点的 `x` 和 `y` 字段上,G6 便可以根据该位置信息进行绘制。 + +由于树图特殊性,G6 扩展出了  TreeGraph ,详细文档请见:[TreeGraph](/zh/docs/api/treeGraphLayout/guide) API。树布局是一种能很好展示有一定层次结构数据的布局方式。推荐使用 G6.TreeGraph 实现。本文将逐一介绍内置的树图布局算法,及其使用方式。 + +## 树图 TreeGraph 布局方法总览 + +- [CompactBox Layout](#compactbox):紧凑树布局; +- [Dendrogram Layout](#dendrogram):树状布局(叶子节点布局对齐到同一层); +- [Indented Layout](#indented):缩进布局; +- [Mindmap Layout](#mindmap):脑图布局。 + +## 配置树图布局 + +与一般图 Graph 配置方法相似,通过实例化图时配置 `layout` 属性设置树的布局,还可以通过 `modes` 属性为树配置 [展开/收缩行为](/zh/docs/manual/middle/states/defaultBehavior/#collapse-expand)。以下代码声明了一个实例,定义了布局为从左到右结构的基础树图,并且定义了展开收缩行为。 + +```javascript +const graph = new G6.TreeGraph({ + container: 'mountNode', + modes: { + default: [ + { + // 定义展开/收缩行为 + type: 'collapse-expand', + }, + 'drag-canvas', + ], + }, + // 定义布局 + layout: { + type: 'dendrogram', // 布局类型 + direction: 'LR', // 自左至右布局,可选的有 H / V / LR / RL / TB / BT + nodeSep: 50, // 节点之间间距 + rankSep: 100, // 每个层级之间的间距 + }, +}); +``` + +## 树图布局方法 + +### compactBox + +**描述**:紧凑树布局。从根节点开始,同一深度的节点在同一层,并且布局时会将节点大小考虑进去。
img
**API**:[CompactBox API](/zh/docs/api/treeGraphLayout/compactBox)
**参数**: + +| 参数名 | 类型 | 示例/可选值 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| direction | String | 'TB' / 'BT' / 'LR' / 'RL' / 'H' / 'V' | 'LR' | layout 的方向。
- TB —— 根节点在上,往下布局
- BT —— 根节点在下,往上布局
img     img
(左)TB。(右)BT。
- LR —— 根节点在左,往右布局
- RL —— 根节点在右,往左布局
img             img
(左)LR。(右)RL。
- H —— 根节点在中间,水平对称布局
- V —— 根节点在中间,垂直对称布局
img          img
> (左)H。(右)V。 | +| getId | Function | (d) => {
  // d 是一个节点
  return d.id + 'node';
} | undefined | 节点 id 的回调函数 | +| getHeight | Function | (d) => {
  // d 是一个节点
  return 10;
} | undefined | 节点高度的回调函数 | +| getWidth | Function | (d) => {
  // d 是一个节点
  return 20;
} | undefined | 节点宽度的回调函数 | +| getVGap | Function | (d) => {
  // d 是一个节点
  return 100;
} | undefined | 节点纵向间距的回调函数 | +| getHGap | Function | (d) => {
// d 是一个节点
  return 50;
} | undefined | 节点横向间距的回调函数 | +| radial | Boolean | true | false | 是否按照辐射状布局。若 `radial` 为 `true`,建议 `direction` 设置为 `'LR'` 或 `'RL'`:img | + +### dendrogram + +**描述**:生态树布局。不管数据的深度多少,总是叶节点对齐。不考虑节点大小,布局时将节点视为 1 个像素点。
img
**API**:[Dendrogram API](/zh/docs/api/treeGraphLayout/dendrogram)
**参数**: + +| 参数名 | 类型 | 示例/可选值 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| direction | String | 'TB' / 'BT' / 'LR' / 'RL' / 'H' / 'V' | 'LR' | layout 的方向。
- TB —— 根节点在上,往下布局
- BT —— 根节点在下,往上布局
imgimg
> (左)TB。(右)BT。
- LR —— 根节点在左,往右布局
- RL —— 根节点在右,往左布局
imgimg
> (左)LR。(右)RL。
- H —— 根节点在中间,水平对称布局
- V —— 根节点在中间,垂直对称布局
imgimg
> (左)H。(右)V。 | +| nodeSep | Number | 50 | 0 | 节点间距 | +| rankSep | Number | 100 | 0 | 层与层之间的间距 | +| radial | Boolean | true | false | 是否按照辐射状布局。若 `radial` 为 `true`,建议 `direction` 设置为 `'LR'` 或 `'RL'`:
img | + +### indented + +**描述**:缩进树布局。每个元素会占一行/一列。
img + +**API**:[Indented API](/zh/docs/api/treeGraphLayout/indented)
**参数**: + +| 参数名 | 类型 | 示例/可选值 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| direction | String | 'LR' / 'RL' / 'H' | 'LR' | layout 的方向。
'LR' —— 根节点在左,往右布局(下图左)
'RL' —— 根节点在右,往左布局(下图中)
'H' —— 根节点在中间,水平对称布局(下图右)
indented1indented2indented3 | +| indent | Number | 80 | 20 | 列间间距 | +| getHeight | Function | (d) => {
  // d 是一个节点
  return 10;
} | undefined | 节点高度的回调函数 | +| getWidth | Function | (d) => {
  // d 是一个节点
  return 20;
} | undefined | 节点宽度的回调函数 | +| getSide | Function | (d) => {
  // d 是一个节点
  return 'left';
} | undefined | 节点放置在根节点左侧或右侧的回调函数,仅对与根节点直接相连的节点有效,设置后将会影响被设置节点的所有子孙节点 | + +### mindmap + +**描述**:脑图布局。深度相同的节点将会被放置在同一层,与 compactBox 不同的是,布局不会考虑节点的大小。
img
**API**:[Mindmap API](/zh/docs/api/treeGraphLayout/mindmap)
**参数**: + +| 参数名 | 类型 | 示例/可选值 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| direction | String | 'H' / 'V' | 'H' | layout 的方向。
- H:horizontal(水平)—— 根节点的子节点分成两部分横向放置在根节点左右两侧
img
- V:vertical (竖直)—— 将根节点的所有孩子纵向排列
img | +| getHeight | Function | (d) => {
  // d 是一个节点
  return 10;
} | undefined | 节点高度的回调函数 | +| getWidth | Function | (d) => {
  // d 是一个节点
  return 20;
} | undefined | 节点宽度的回调函数 | +| getVGap | Function | (d) => {
  // d 是一个节点
  return 100;
} | 18 | 节点纵向间距的回调函数 | +| getHGap | Function | (d) => {
  // d 是一个节点
  return 50;
} | 18 | 节点横向间距的回调函数 | +| getSide | String | Function | (d) => {
  // d 是一个节点
  return 'left';
} / 'right' | 节点排布在根节点的左侧/右侧。若设置了该值,则所有节点会在根节点同一侧,即 direction = 'H' 不再起效。若该参数为回调函数,则可以指定每一个节点在根节点的左/右侧 | diff --git a/packages/site/docs/manual/middle/layout/webworker.en.md b/packages/site/docs/manual/middle/layout/webworker.en.md new file mode 100644 index 0000000000..8223636803 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/webworker.en.md @@ -0,0 +1,22 @@ +--- +title: Layout with Web-Worker +order: 5 +--- + +The layout algorithm costs a lot in large scale graph visualization. If you config the layout for a graph, the layout algorithm must be done before rendering. In some web applications, this process will block the page and the end users will not able to interact with other components of the page. To address this issue, G6 provids the Web-Worker for **General Graph**. You only need to assign `workerEnabled` to `true` when configuring the layout. For example: + +```javascript +const graph = new G6.Graph({ + ... // Other configurations for graph + layout: { // Object, layout methods and its configurations + type: 'fruchterman', + workerEnabled: true, // enable Web-Worker + // ... // other configurations for the layout + } +}); +``` + +Note: + +- TreeGraph layouts do not support Web-Worker; +- Sub-Graph layout mechanism do not support Web-Worker. diff --git a/packages/site/docs/manual/middle/layout/webworker.zh.md b/packages/site/docs/manual/middle/layout/webworker.zh.md new file mode 100644 index 0000000000..c9af72e654 --- /dev/null +++ b/packages/site/docs/manual/middle/layout/webworker.zh.md @@ -0,0 +1,23 @@ +--- +title: 使用 webworker 布局 +order: 5 +--- + +在大规模图可视化中,布局算法往往需要较大的计算量。若配置了布局,G6 需要首先完成布局才可以将图渲染出来。然而,在一些应用页面中,这一过程可能会阻塞页面的其他部分用户交互。为了让大规模图布局不阻塞页面,G6 为**一般图**布局提供了 Web-Worker 机制。只需要在配置布局时,将 `workerEnabled` 设置为 `true` 即可。如下: + +```javascript +const graph = new G6.Graph({ + // ... // 其他配置项 + layout: { + // Object,可选,布局的方法及其配置项,默认为 random 布局。 + type: 'fruchterman', + workerEnabled: true, // 开启 Web-Worker + // ... // 其他配置 + }, +}); +``` + +注意: + +- 树图不支持 Web-Worker 机制; +- 子图布局机制暂不支持 Web-Worker 机制。 diff --git a/packages/site/docs/manual/middle/overview.en.md b/packages/site/docs/manual/middle/overview.en.md new file mode 100644 index 0000000000..4e0894522c --- /dev/null +++ b/packages/site/docs/manual/middle/overview.en.md @@ -0,0 +1,62 @@ +--- +title: Overview of Main Concepts +order: 0 +--- + +G6 Main concepts overview + +#### Graph + +- Initialize + +#### Shape + +- Shape properties +- Shape & Key Shape +- Graphics Group +- Shape and Group Transformation + +#### Item + +- Node + - Built-in Node + - Custom Node + - AnchorPoints +- Edge + - Built-in Edge + - Custom Edge +- Combo + - Combo Overview + - Built-in Combo + - Custom Combo +- Advanced Style + - Background of the label + - Gradient color + - Fill with Texture + - Update Label +- Advanced Operation + - Update node or edge style + - Level adjustment + - Show/hide + - Lock/unlock + +#### Diagram layout + +- Built-in layout +- Custom layout + +#### Interactions and events + +- Monitoring and binding events +- Built-in interactive behavior +- Custom interaction behavior (optional reading) +- Interactive Mode + +#### Animation + +- Global animation +- Element animation + +#### Graph algorithm + +#### Plug-ins diff --git a/packages/site/docs/manual/middle/overview.zh.md b/packages/site/docs/manual/middle/overview.zh.md new file mode 100644 index 0000000000..ea76b575fd --- /dev/null +++ b/packages/site/docs/manual/middle/overview.zh.md @@ -0,0 +1,63 @@ +--- +title: G6 核心概念总览 +order: 0 +--- + +G6 Main concepts overview + +#### 图 Graph + +- 初始化和渲染 + +#### 图形(Shape)(选读) + +- 图形和属性 +- 关键图形(Key Shape) +- 图形分组(Shape group) +- 图形变换 (Transform) + +#### 图元素(节点、边、Combo) + +- 节点 + - 内置节点 + - 自定义节点 + - 节点的连接点 anchorPoints +- 边 + - 内置边 + - 自定义边 +- Combo + - 内置 Combo + - 自定义 Combo + - Combo 机制 + - 创建与拆分 Combo +- 高级样式 + - 设置元素背景 + - 设置元素渐变色 + - 设置纹理 + - 更新文本样式 +- 高级操作 + - 更新节点或边的样式 + - 层级调整 + - 显示隐藏 + - 锁定/解锁 + +#### 图布局 + +- 内置布局 +- 自定义布局 + +#### 交互与事件 + +- 监听与绑定事件 +- 内置交互行为 +- 自定义交互行为(选读) +- 交互模式 Mode + +#### 动画 + +- 全局动画 +- 元素动画 + +#### 图算法 + +#### 插件 diff --git a/packages/site/docs/manual/middle/plugins/Plugins.en.md b/packages/site/docs/manual/middle/plugins/Plugins.en.md new file mode 100644 index 0000000000..0c6d177596 --- /dev/null +++ b/packages/site/docs/manual/middle/plugins/Plugins.en.md @@ -0,0 +1,924 @@ +--- +title: Plugins +order: 0 +--- + +There are several plugins in G6 which can be used for G6's graph or other applications. + +- [Legend](#legend) *supported by v4.3.0 and later versions* +- [SnapLine](#snapline) *supported by v4.3.0 and later versions* +- [Grid](#grid) +- [Minimap](#minimap) +- [Edge Bundling](#edge-bundling) +- [Menu](#menu) +- [ToolBar](#toolbar) +- [TimeBar](#timebar) +- [Tooltip](#tooltip) +- [Fisheye](#fisheye-lens) +- [EdgeFilterLens](#edge-filter-lens) + +## Configure to Graph + +Instantiate the plugin and configure the minimap onto the instance of Graph: + +```javascript +// Instantialize the Grid plugin +const grid = new G6.Grid(); +// Instantialize the Minimap plugin +const minimap = new G6.Minimap(); +const graph = new G6.Graph({ + //... Other configurations + plugins: [grid, minimap], // Configure Grid and Minimap to the graph +}); +``` + +## Legend + +Legend is a built-in legend plugin for G6. It is useful for npde/edge type demonstration, and the end-users are able to interact with the legend to highlight and filter the items on the graph. *supported by v4.3.0 and later versions*. + +img + +### Configuration + +| Name | Type | Description | +| --- | --- | --- | +| data | GraphData | The data for the legend, not related to the data of the graph. The legend for nodes currently supports `'circle'`, `'rect'`, and `'ellipse'`. The legend for edges currently supports `'line'`, `'cubic'`, and `'quadratic'`. `type` for each data means the type of the legend item, and the `order` could be assigned to each node/edge data for ordering in a legend group | +| position | 'top' / 'top-left' / 'top-right' / 'right' / 'right-top' / 'right-bottom' / 'left' / 'left-top' / 'left-bottom' / 'bottom' / 'bottom-left' / 'bottom-right' | The relative of the position to the canvas. `'top'` by default, which means the legend area is on the top of the canvas | +| padding | number / number[] | The inner distance between the content of the legend to the border of the legend area. Array with four numbers means the padding to the top, right, bottom, and left responsively | +| margin | number / number[] | The outer distance between the legend area to the border of the canvas. Array with four numbers means the distance to the top, right, bottom, and left responsively. Only the top distance takes effect when `position:'top'`, situations for other `position` configurations are similar to it | +| offsetX | number | The x-axis offset for the legend area, it is useful when you want to adjust the position of the lenged slightly | +| offsetY | number | The y-axis offset for the legend area, it is useful when you want to adjust the position of the lenged slightly | +| containerStyle | ShapeStyle | The style for the background rect, the format is similar as [rect shape style](/en/docs/api/shapeProperties#rect) | +| horiSep | number | The horizontal seperation of the legend items | +| vertiSep | number | The vertical seperation of the legend items | +| layout | 'vertical' / 'horizontal' | The layout of the legend items. `'horizontal'` by default | +| align | 'center' / 'right' / 'left' | The alignment of the legend items. `'center'` by default | +| title | string | The title string for the legend, the style of the title could be configured by `titleConfig` | +| titleConfig | object | The style of the legend title, detail configurations are shown in following lines | +| titleConfig.position | 'center' / 'right' / 'left' | The alignment of the title to the legend content. `'center'` by default | +| titleConfig.offsetX | number | The x-axis offset for the legend title, it is useful when you want to adjust the position of the title slightly | +| titleConfig.offsetY | number | The y-axis offset for the legend title, it is useful when you want to adjust the position of the title slightly | +| titleConfig[key] | ShapeStyle | Other styles for the text, configurations are same as [text shape style](/en/docs/api/shapeProperties#text) | +| filter | object | Configurations for the graph item filtering while the end-user interacting with the legend items. Detials are shown in the following lines | +| filter.enable | boolean | Whether allow filtering the items in the main graph while the end-user interaction with the legend items. `false` by default | +| filter.multiple | boolean | Whether support active multiple types of legend items, `false` by default, which means only one type of legend item will be activated in the same time. If it is `true`, multiple items could be activated only when the `filter.trigger` is `'click'` | +| filter.trigger | 'click' / 'mouseenter' | The interaction way to the legend items. `click` by default, which means while the end-user clicking a legend item, the legend item and corresponding filtered items on the main graph will be activated | +| filter.legendStateStyles | { active?: ShapeStyle, inactive?: ShapeStyle | The state styles for the legend items while filtering, inluding `filter.legendStateStyles.active` and `filter.legendStateStyles.inactive`. The type of each one is `ShapeStyle`. Similar to the `nodeStateStyles` of Graph | +| filter.graphActiveState | string | The activate state name for the items on the main graph. When a lenged item is activated, the corresponding items of the main graph will be set to `filter.graphActiveState`, `'active'` by default. And you should assign the state style for this state name on Graph | +| filter.graphInactiveState | string | The inactivate state name for the items on the main graph. When a lenged item is inactivated, the corresponding items of the main graph will be set to `filter.graphInactiveState`, `'inactive'` by default. And you should assign the state style for this state name on Graph | +| filter.filterFunctions | { [key: string]: (d) => boolean; } | Since the data of the legend is not related to the main graph, you should configure filtering functions for each legend item type. The `key` is corresponding to the `type` of the legend item, and the value is a function. For the function, the parameter is the item data of the main graph, and the return value is a boolean which means whether the item of the main graph should be activated | + + +## SnapLine + +SnapLine is a built-in components in G6. *supported by v4.3.0 and later versions*. + +### Configuration + +| Name | Type | Required | Description | +| ------------- | --------------------------------------------- | -------- | --------------------- | +| line | ShapeStyle | false | the style of SnapLine | +| itemAlignType | boolean、'horizontal' 、'vertical'、'center'; | false | the type of SnapLine | + +## Grid + +Grid plugin draws grids on the canvas. + +img + +Use the code in [Configure to Graph](#configure-to-graph) to instantiate grid plugin with the following configurations. + +### Configuration + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ------------------------------------------ | +| img | Srting | false | base64 formatted string for the grid image | + +## Minimap + +Minimap is a tool for quick preview and exploration on large graph. + +img + +It can be configured to adjust the styles and functions. + +### Configuration + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| container | Object | false | The DOM container of Minimap. The plugin will generate a new one if `container` is not defined | +| className | String | false | The className of the DOM element of the Minimap | +| viewportClassName | String | false | The className of the DOM element of the view port on the Minimap | +| type | String | false | Render type. Options: `'default'`: Render all the graphics shapes on the graph; `'keyShape'`: Only render the keyShape of the items on the graph to reach better performance; `'delegate'`: Only render the delegate of the items on the graph to reach better performance. Performance: `'default'` < `'keyShape'` < `'delegate'`. `'default'` by default | +| size | Array | false | The size of the Minimap | +| delegateStyle | Object | false | Takes effect when `type` is `'delegate'`. The style of the delegate of the items on the graph | +| hideEdge | Boolean | false | **Supported by v4.7.16** Whether to hide the edges on minimap to enhance the performance | + + +The `delegateStyle` has the properties: + +| Name | Type | Required | Description | +| ----------- | ------ | -------- | ----------------------- | +| fill | String | false | Filling color | +| stroke | String | false | Stroke color | +| lineWidth | Number | false | The width of the stroke | +| opacity | Number | false | Opacity | +| fillOpacity | Number | false | Filling opacity | + +## Edge Bundling + +In complex graph with large number of edges, edge bundling can help you to improve the visual clutter. + +img + +> Edge bundling on American airline graph. Demo Link. Demo Document. + +The edge bundling plugin can be configured to adjust the styles and functions. + +### Configuration + +| Name | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| K | Number | false | 0.1 | The strength of the bundling | +| lambda | Number | false | 0.1 | The initial step length | +| divisions | Number | false | 1 | The initial number of division on each edge. It will be multipled by `divRate` in each cycle | +| divRate | Number | false | 2 | The rate of the divisions increasement. Large number means smoother result, but the performance will be worse when the number is too large | +| cycles | Number | false | 6 | The number of outer interations | +| iterations | Number | false | 90 | The initial number of inner interations. It will be multiplied by `iterRate` in each cycle | +| iterRate | Number | false | 0.6666667 | The rate of the iterations decreasement | +| bundleThreshold | Number | false | 0.6 | The edge similarity threshold for bundling. Large number means the edges in one bundle have smaller similarity, in other words, more edges in one bundle | + +## Menu + +Menu is used to configure the right-click menu on the node. + +### Configuration + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| className | string | null | the class name of the menu dom | +| getContent | (evt?: IG6GraphEvent, graph?: IGraph) => HTMLDivElement / string | img | the menu content,supports DOM or string | +| handleMenuClick | (target: HTMLElement, item: Item, graph?: IGraph) => void | undefined | the callback function when click the menu | +| shouldBegin | (evt: G6Event) => boolean | undefined | Whether allow the tooltip show up. You can return true or false according to the content of the `evt.item` (current item of the event) or `evt.target` (current shape of the event) | +| offsetX | number | 6 | the offset of tooltip along x axis, the padding of the parent container should be take into consider | +| offsetY | number | 6 | the offset of tooltip along y axis, the padding of the parent container should be take into consider | +| itemTypes | string[] | ['node', 'edge', 'combo'] | the item types that allow the tooltip show up. e.g. if you only want the node tooltip, set the `itemTypes` to be ['node'] | +| trigger | 'click' / 'contextmenu' | 'contextmenu' | the trigger for the menu, `'contextmenu'` by default, which means the menu will show up when the end user right click on some item. `'click'` means left click. *'click' is supported by v4.3.2 and later versions* | + +### Usage + +Use G6 build-in menu by default. + +```javascript +// Instantiate Menu plugin +const menu = new G6.Menu(); +const graph = new G6.Graph({ + //... other Configuration + plugins: [menu], +}); +``` + +#### DOM Menu + +```javascript +const menu = new G6.Menu({ + offsetX: 10, + offsetY: 20, + itemTypes: ['node'], + getContent(e, graph) { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = `
    +
  • menu01
  • +
  • menu01
  • +
  • menu01
  • +
  • menu01
  • +
  • menu01
  • +
` + return outDiv + }, + handleMenuClick(target, item, graph) { + console.log(target, item, graph) + }, +}); + +const graph = new G6.Graph({ + //... other Configuration + plugins: [menu], // the Menu plugin +}); +``` + +#### String Menu + +```javascript +const menu = new G6.Menu({ + getContent(e) { + return `
    +
  • menu02
  • +
  • menu02
  • +
  • menu02
  • +
  • menu02
  • +
  • menu02
  • +
`; + }, + handleMenuClick(target, item) { + console.log(target, item) + }, +}); + +const graph = new G6.Graph({ + //... other Configuration + plugins: [menu], // The Menu plugin +}); +``` + +## ToolBar + +ToolBar has the following operations by default: + +- Undo; +- Redo; +- Zoom-in; +- Zoom-out; +- Fit the View; +- Actual Size. + +### Configuration + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | The container of the ToolBar. It will take use the DOM of the canvas by default | +| className | string | null | The class name of the sub DOM nodes of the ToolBar | +| getContent | (graph?: IGraph) => HTMLDivElement / string | img | The content of the ToolBar | +| handleClick | (code: string, graph: IGraph) => void | undefined | The callback functions for the icons of the ToolBar | +| position | Point | null | The position of the ToolBar | + +### Usage + +#### Default Usage + +ToolBar provides some default operations above. + +```javascript +const toolbar = new G6.ToolBar(); + +const graph = new G6.Graph({ + //... Other configurations + plugins: [toolbar], // Use the ToolBar plugin +}); +``` + +#### Custom ToolBar by String + +```javascript +const tc = document.createElement('div'); +tc.id = 'toolbarContainer'; +document.body.appendChild(tc); + +const toolbar = new G6.ToolBar({ + container: tc, + getContent: () => { + return ` +
    +
  • Add Node
  • +
  • Undo
  • +
+ ` + }, + handleClick: (code, graph) => { + if (code === 'add') { + graph.addItem('node', { + id: 'node2', + label: 'node2', + x: 300, + y: 150 + }) + } else if (code === 'undo') { + toolbar.undo() + } + } +}); + +const graph = new G6.Graph({ + //... Other configurations + plugins: [toolbar], // Use the ToolBar plugin +}); +``` + +#### Custom ToolBar by DOM + +```javascript +const toolbar = new G6.ToolBar({ + getContent: () => { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = `
    +
  • example 01
  • +
  • example 02
  • +
  • example 03
  • +
  • example 04
  • +
  • example 05
  • +
` + return outDiv + }, + handleClick: (code, graph) => { + + } +}); + +const graph = new G6.Graph({ + //... Other configurations + plugins: [toolbar], // Use the ToolBar plugin +}); +``` + + +## ToolTip + +ToolTip helps user to explore detail infomations on the node and edge. Do note that, This Tooltip Plugins will replace the tooltip in the built-in behavior after G6 4.0. + +### Configuration + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| className | string | null | Tge class name of the tooltip's container | +| container | HTMLDivElement | null | The container of the Tooltip. The canvas DOM will be used by default | +| getContent | (evt?: IG6GraphEvent) => HTMLDivElement / string | img | The content of the Tooltip | +| shouldBegin | (evt: G6Event) => boolean | undefined | Whether allow the tooltip show up. You can return true or false according to the content of the `evt.item` (current item of the event) or `evt.target` (current shape of the event) | +| offsetX | number | 6 | the offset of tooltip along x axis, the padding of the parent container should be take into consider | +| offsetY | number | 6 | the offset of tooltip along y axis, the padding of the parent container should be take into consider | +| itemTypes | string[] | ['node', 'edge', 'combo'] | the item types that allow the tooltip show up. e.g. if you only want the node tooltip, set the `itemTypes` to be ['node'] | + +### Usage + +The content of the Tooltip is the type and id of the item by default. Users are free to custom the content of the Tooltip by configuring `getContent`: + +#### Dom Tooltip + +```javascript +const tooltip = new G6.Tooltip({ + offsetX: 10, + offsetY: 20, + getContent(e) { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = ` +

自定义tooltip

+
    +
  • Label: ${e.item.getModel().label || e.item.getModel().id}
  • +
` + return outDiv + }, + itemTypes: ['node'] +}); + + +const graph = new G6.Graph({ + //... Other configurations + plugins: [tooltip], // Use Tooltip plugin +}); +``` + +#### String Tooltip + +```javascript +const tooltip = new G6.Tooltip({ + getContent(e) { + return `
+ +
`; + }, +}); + +const graph = new G6.Graph({ + //... Other configurations + plugins: [tooltip], // Use Tooltip plugin +}); +``` + +## Fisheye Lens + +Fisheye is designed for focus_context exploration, it keeps the context and the relationships between context and the focus while magnifing the focus area. + +### Configuration + +| Name | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| trigger | 'drag' / 'mousemove' / 'click' | false | 'mousemove' | The trigger for the lens | +| d | Number | false | 1.5 | Magnify coefficient. Larger the value, larger the focus area will be magnified | +| r | Number | false | 300 | The radius of the focus area | +| delegateStyle | Object | false | { stroke: '#000', strokeOpacity: 0.8, lineWidth: 2, fillOpacity: 0.1, fill: '#ccc' } | The style of the lens's delegate | +| showLabel | Boolean | false | false | If the label is hidden, whether to show the label of nodes inside the focus area | +| maxR | Number | The height of the graph | The maximum radius scaled by the wheel | +| minR | Number | 0.05 \* The height of the graph | The minimum radius scaled by the wheel | +| maxD | Number | 5 | when `trigger` is `'mousemove'` or `'click'`, minimap allow users to adjust the magnifying coefficient `d` by dragging left / right on the lens. `maxD` is the maximum magnifying coefficient that limits this interaction. The suggested range for `maxD` is [0, 5]. Note that updating the configurations by `minimap.updateParam` will not be limited by `maxD` | +| minD | Number | 0 | when `trigger` is `'mousemove'` or `'click'`, minimap allow users to adjust the magnifying coefficient `d` by dragging left / right on the lens. `minD` is the minimum magnifying coefficient that limits this interaction. The suggested range for `minD` is [0, 5]. Note that updating the configurations by `fisheye.updateParams` will not be limited by `minD` | +| scaleRBy | 'wheel'/'drag'/'unset'/undefined | false | 'unset' | The trigger for end users to scale the range of the lens | +| scaleDBy | 'wheel'/'drag'/'unset'/undefined | false | 'unset' | The trigger for end users to scale the magnification factor of the lens | +| showDPercent | Boolean | false | true | Whether show the percent of current magnification factor on the bottom of the lens, where the percent is about the D, minD, and maxD | + +### Member Function + +#### updateParams(cfg) + +Update partial of the configurations of the fisheye instance, including `trigger`, `d`, `r`, `maxR`, `minR`, `maxD`, `minD`, `scaleDBy`, and `scaleRBy`. E.g. + +```javascript +const fisheye = new G6.Fisheye({ + trigger: 'mousemove' +}); + +... // Other operations + +fisheye.updateParams({ + d: 2, + r: 500, + // ... +}) +``` + +### Usage + +```javascript +const fisheye = new G6.Fisheye({ + trigger: 'mousemove', + d: 1.5, + r: 300, + delegateStyle: clone(lensDelegateStyle), + showLabel: false +}); + +const graph = new G6.Graph({ + //... Other graph configurations + plugins: [fisheye], // configuring fisheye plugin +}); +``` + +## Edge Filter Lens + +Edge Filter Lens is designed for edge filtering, the desired edges will be kept inside the lens while the others will be hidden. + +### Configuration + +| Name | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| trigger | 'drag' / 'mousemove' / 'click' | false | 'mousemove' | The trigger for the lens | +| type | 'one' / 'both' / 'only-source' / 'only-target' | false | 'both' | Simple filtering conditions related to the end nodes. `'one'`: show the edge whose one or more end nodes are inside the filter lens; `'both'`: show the edge whose both end nodes are inside the lens; `'only-source'`: show the edge whose source node is inside the lens and target node is not; `'only-target'`: show the edge whose target node is inside the lens and source node is not. More complicated conditions can be defined by the `shouldShow` | +| shouldShow | (d?: unknown) => boolean | false | undefined | The custom conditions for filtering. The parameter `d` is the data of each edge, you can return boolean value according to the data, where `true` means show. | +| r | Number | false | 60 | The radius of the filter area | +| delegateStyle | Object | false | { stroke: '#000', strokeOpacity: 0.8, lineWidth: 2, fillOpacity: 0.1, fill: '#ccc' } | The style of the lens's delegate | +| showLabel | 'edge' / 'node' / 'both' | false | 'edge' | If the label is hidden, whether to show the label of nodes inside the focus area | +| maxR | Number | The height of the graph | The maximum radius scaled by the wheel | +| minR | Number | 0.05 \* The height of the graph | The minimum radius scaled by the wheel | +| scaleRBy | 'wheel'/'drag'/'unset'/undefined | false | 'unset' | The trigger for end users to scale the range of the lens | + +### Member Function + +#### updateParams(cfg) + +Update partial of the configurations of the filter lens instance, including `trigger`, `type`, `r`, `maxR`, `minR`, `shouldShow`, `showLabel`, and `scaleRBy`. E.g. + +```javascript +const filterLens = new G6.EdgeFilterLens({ + trigger: 'drag' +}); + +... // Other operations + +filterLens.updateParams({ + r: 500, + // ... +}) +``` + +### Usage + +```javascript +const filterLens = new G6.EdgeFilterLens({ + trigger: 'mousemove', + r: 300, + shouldShow: d => { + return d.size > 10; + } +}); + +const graph = new G6.Graph({ + //... Other graph configurations + plugins: [filterLens], // configuring edge filter lens plugin +}); +``` +## TimeBar + +There are three types of built-in TimeBar in G6: + +- Time bar with a line chart as background; +- Simple time bar; +- Time bar with descrete ticks. + +All the three types of timebar supports play, fast forward, and fast backward. + + +
Time bar with a line chart as background
+ + +
Simple time bar
+ + +
Time bar with descrete ticks
+ +
Refer to the demos [HERE](https://g6.antv.antgroup.com/en/examples/tool/timebar#timebar)
+ +### Common Usage + +Same to other plugins of G6, the users can initiate the TimeBar and assign it to the graph as: + +```javascript +import G6 from '@antv/g6'; + +const timebar = new G6.TimeBar({ + width: 500, + height: 150, + padding: 10, + type: 'trend', + trend: { + data: timeBarData, + }, +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [timebar], +}); +``` + +
If you want to use the TimeBar with line chart, assign the `type` to be `trend` when instantiating the TimeBar, which results in: + + + +
Assigning the `type` to be `simple` results in: + + + +
And assigning the `type` to be `tick` results in a TimeBar with descrete ticks. Note that it is different from the above two types of TimeBar, \*\*The TimeBar with decrete ticks is configured with the `tick` object but not the `trend` object. + +```javascript +const timebar = new G6.TimeBar({ + width, + height: 150, + type: 'tick', + tick: { + data: timeBarData, + width, + height: 42, + tickLabelFormatter: d => { + const dateStr = `${d.date}`; + if ((count - 1) % 10 === 0) { + return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`; + } + return false; + }, + tooltipFomatter: d => { + const dateStr = `${d}`; + return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`; + }, + }, +}); +``` + + + + +### Event Listener + +TimeBar Plugin exposes several timing events. They could be listened by `graph.on('eventname', e => {})`. + +| Event Name | Description | +| --- | --- | +| valuechange | Emitted when the value range of the timebar is chaged. | +| timebarstartplay | Emitted when the timeline starts to play. | +| timebarendplay | Emitted when the timeline ends playing. | + +### Definition of the Configurations + +#### Definition of the Interfaces + +The complete interfaces for the TimeBar is shown below: + +```javascript +interface TimeBarConfig extends IPluginBaseConfig { + // position size + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + readonly padding?: number; + + readonly type?: 'trend' | 'simple' | 'tick'; + // the configuration for the TimeBar with line chart and simple TimeBar, takes effect whtn the type is 'trend' or 'simple' + readonly trend?: TrendConfig; + + // the configurations for the two sliders + readonly slider?: SliderOption; + + // when the type is 'tick', it is the configuration for the TimeBar with descrete ticks + // when the type is 'trend' or 'simpe', it is the configuration for the time tick labels under the timeBar + readonly tick?: TimeBarSliceOption | TickCfg; + + // the buttons for play, fast forward, and back forward + readonly controllerCfg?: ControllerCfg; + + // [Supported from v4.5.1] the CSS style for the DOM container of the timebar + readonly containerCSS?: Object; + + // [Supported from v4.5.1] the item types that will be filtered by the timebar. e.g. ['node', 'edge']. The default value is ['node'] + readonly filterItemTypes?: string[]; + + // [Deprecated from v4.5.1, replaced by filterItemTypes] whether to consider the edge filtering. If it is false, only filter the nodes and the edges whose end nodes are filtered out while the selected range of the timeBar is changed. If it is true, there should be `date` properties on the edges data, and the timeBar will filter the edges which is not in the selected range in the same time + readonly filterEdge?: boolean; + + // [Supported from v4.5.1] whether filter the nodes and edges on the graph by graph.changeData, which means the data of the graph will be changed by the timebar. If it is false, the graph.hideItem and graph.showItem will be called to hide/show the nodes and edges instead of changeData + readonly changeData?: boolean; + + // the callback function after the time range is changed. When it is not assigned, the graph elements will be filtered after the time range is changed + rangeChange?: (graph: IGraph, minValue: string, maxValue: string) => void; + + // [Supported from v4.5.1] user returns the date value according to the data of a node or an edge + getDate?: (d: any) => number; + + // [Supported from v4.5.1] user returns the value according to the data of a node or an edge. The value is used to draw the trend line for timebar with type 'trend' + getValue?: (d: any) => number; + + // [Supported from v4.5.1] user returns true or false to decide whether to ignore the node or the edge while filtering. If it is true, the item with data model will be ignored. Or the item will be filtered according to the min and max date value + shouldIgnore?: (itemType: 'node' | 'edge', model: any, dateRage: { min: number, max: number }) => boolean; +} +``` + +#### The Parameters of the Interfaces + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | The DOM container of the TimeBar. By default, the plugin will create a container DOM with 'g6-component-timebar' as className | +| x | number | 0 | The beginning x position of the TimeBar plugin | +| y | number | 0 | The beginning y position of the TimeBar plugin | +| width | number | | **Requred**, the width of the TimeBar | +| height | number | | **Requred**, the height of the TimeBar | +| padding | number/number[] | 10 | The padding of the container of the TimeBar | +| type | 'trend' / 'simple' / 'tick' | trend | The type of the TimeBar, 'trend' by default | +| trend | TrendConfig | null | The configuration for the TimeBar with line chart and simple TimeBar, takes effect whtn the type is 'trend' or 'simple' | +| slider | SliderOption | null | The configurations for the two sliders | +| tick | TimeBarSliceOption / TickCfg | null | If the type is 'tick', it is the configuration for the TimeBar with descrete ticks. If it the type is 'trend' or 'simple', it is the configuration for the time tick labels under the timeBar | +| controllerCfg | ControllerCfg | null | The buttons for play, fast forward, and back forward | +| containerCSS | Object | null | [Supported from v4.5.1] The CSS style for the DOM container of the timebar | +| filterItemTypes | string[] | null | [Supported from v4.5.1] The item types that will be filtered by the timebar. e.g. ['node', 'edge']. The default value is ['node'] | +| filterEdge | boolean | false | [Deprecated from v4.5.1, replaced by filterItemTypes] Whether to consider the edge filtering. If it is false, only filter the nodes and the edges whose end nodes are filtered out while the selected range of the timeBar is changed. If it is true, there should be `date` properties on the edges data, and the timeBar will filter the edges which is not in the selected range in the same time | +| changeData | boolean | null | [Supported from v4.5.1] Whether filter the nodes and edges on the graph by graph.changeData, which means the data of the graph will be changed by the timebar. If it is false, the graph.hideItem and graph.showItem will be called to hide/show the nodes and edges instead of changeData | +| rangeChange | Function | null | The callback function after the time range is changed. When it is not assigned, the graph elements will be filtered after the time range is changed | +| getDate | (d: any) => number | null | [Supported from v4.5.1] User returns the date value according to the data of a node or an edge | +| getValue | (d: any) => number | null | [Supported from v4.5.1] User returns the value according to the data of a node or an edge. The value is used to draw the trend line for timebar with type 'trend' | +| shouldIgnore | (itemType: 'node' | 'edge', model: any, dateRage: { min: number, max: number }) => boolean | null | [Supported from v4.5.1] User returns true or false to decide whether to ignore the node or the edge while filtering. If it is true, the item with data model will be ignored. Or the item will be filtered according to the min and max date value | + +#### Interface for TrendConfig + +> Does not support the configurations for the style of the tick labels. + +```javascript +interface TrendConfig { + // The data + readonly data: { + date: string; + value: string; + }[]; + // The position and size + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + // The styles + readonly smooth?: boolean; + readonly isArea?: boolean; + readonly lineStyle?: ShapeStyle; + readonly areaStyle?: ShapeStyle; + readonly interval?: Interval; +} +``` + +#### Parameters of the TrendConfig + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| x | number | 0 | The beginning x position of the trend line chart | +| y | number | 0 | The beginning y position of the trend line chart | +| width | number | The width of the TimeBar | The width of the trend line chart of the TimeBar, we suggest to use the default value. If you wanna custom it, please assign the `width` of the slider in the same time | +| height | number | 28 when type='trend'
8 when type='simple' | The height of the TimeBar | The width of the trend line chart of the TimeBar, we suggest to use the default value. If you wanna custom it, please assign the `height` of the slider in the same time | +| smooth | boolean | false | Whether to show a smooth line on the trend line chart | +| isArea | boolean | false | Whether to show a area chart instead | +| lineStyle | ShapeStyle | null | The configurations for the style of the line in the line chart | +| areaStyle | ShapeStyle | null | The configuration for the style of the area in the chart when `isArea` is `true` | +| interval | Interval | null | The configuration for the style of the bars in the chart. When it is assigned, a mixed trend chart will take place. `Interval = { data: number[], style: ShapeStyle }`. Except the configurations in `ShapeStyle` for the style of the shapes in the bar charts, `barWidth` for the width of one bar is also configurable for `style` | + +#### Interfaces of SliderOption + +```javascript +export type SliderOption = Partial<{ + readonly width?: number; + readonly height?: number; + readonly backgroundStyle?: ShapeStyle; + readonly foregroundStyle?: ShapeStyle; + // The style of the sliders + readonly handlerStyle?: { + width?: number; + height?: number; + style?: ShapeStyle; + }; + readonly textStyle?: ShapeStyle; + // The start and end position for the sliders, which indicate the data range for the filtering. Ranges from 0 to 1 + readonly start: number; + readonly end: number; + // The labels for the sliders + readonly minText: string; + readonly maxText: string; +}>; +``` + +#### Parameters for the SliderOption + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| width | number | The width of the container of the TimeBar - 2 \* padding | The width of the background trend chart. We suggest to use the default value. If you wanna custom it, assign it the the `width` in the `trend` in the same time | +| height | number | 28 when type='trend'
8 when type='simple' | The height of the background trend chart. We suggest to use the default value. If you wanna custom it, assign it the the `height` in the `trend` in the same time | +| backgroundStyle | ShapeStyle | null | The configuration for the style of the background | +| foregroundStyle | ShapeStyle | null | The configuration for the style of the forground | +| handlerStyle | ShapeStyle | null | The configuration for the style of the two sliders | +| textStyle | ShapeStyle | null | The configuration for the style of the labels on the two sliders | +| start | number | 0.1 | The start position for the sliders, which indicate the start of the data range for the filtering. Ranges from 0 to `end` | +| end | number | 0.9 | The end position for the sliders, which indicate the end of the data range for the filtering. Ranges from `start` to 1 | +| minText | string | min | The label for the left slider | +| maxText | string | max | The label for the right slider | + +#### TimeBarSliceOption + +```javascript +export interface TimeBarSliceOption { + // position size + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + readonly padding?: number; + + // styles + readonly selectedTickStyle?: TickStyle; + readonly unselectedTickStyle?: TickStyle + readonly tooltipBackgroundColor?: string; + + readonly start?: number; + readonly end?: number; + + // data + readonly data: { + date: string; + value: string; + }[]; + + // custom the formatter function for the tick labels + readonly tickLabelFormatter?: (d: any) => string | boolean; + // custom the formatter function for the tooltip + readonly tooltipFomatter?: (d: any) => string; +} +``` + +#### Parameters for the TimeBarSliceOption + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| x | number | 0 | The beginning x position for the TimeBar | +| y | number | 0 | The beginning y position for the TimeBar | +| width | number | | **Requred**, the width of the TimeBar | +| height | number | | **Requred**, the height of the TimeBar | +| padding | number / number[] | 0 | The padding of the container of the TimeBar | +| selectedTickStyle | ShapeStyle | null | The style of the tick(s) which is(are) selected | +| unselectedTickStyle | ShapeStyle | null | The style of the tick(s) which is(are) unselected | +| tooltipBackgroundColor | ShapeStyle | null | The background style for the tooltip | +| start | number | 0.1 | The start position for the sliders, which indicate the start of the data range for the filtering. Ranges from 0 to `end` | +| end | number | 0.9 | The end position for the sliders, which indicate the end of the data range for the filtering. Ranges from `start` to 1 | +| data | any[] | [] | **Requred**, the data for the ticks | +| tickLabelFormatter | Function | null | The formatter function for customing the labels of the ticks | +| tooltipFomatter | Function | null | The formatter function for customing the tooltip | + +#### TickCfg + +```javascript +export interface TickCfg { + // the fomatter for the time tick labels + readonly tickLabelFormatter?: (d: any) => string | undefined; + // the shape style for the time tick labels. [Supported from v4.5.1] tickLabelStyle.rotate can be configured to controll the rotate of the tick label to avoid overlappings + readonly tickLabelStyle?: ShapeStyle; + // the shape style for the short vertical lines uppon the time tick labels + readonly tickLineStyle?: ShapeStyle; +} +``` + +#### Parameters for TickCfg + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| tickLabelFormatter | Function | null | The formatter function for customing the labels of the ticks | +| tickLabelStyle | ShapeStyle | {} | The shape style for the time tick labels. [Supported from v4.5.1] tickLabelStyle.rotate can be configured to controll the rotate of the tick label to avoid overlappings | +| tickLineStyle | ShapeStyle | {} | The shape style for the short vertical lines uppon the time tick labels | + +#### Interface of the ControllerCfg + +> Does not support for now + +> Does not support the style configuration for controller buttons + +> Does not support loop play + +```javascript +type ControllerCfg = Partial<{ + /** the begining position and the size of the controller, the width and height will not scale the sub-controllers but only affects the positions of them. To change the size of the sub-controllers, try ControllerCfg.scale or the scale in the style of sub-controller */ + readonly x?: number; + readonly y?: number; + readonly width: number; + readonly height: number; + /** the scale of the whole controller */ + readonly scale?: number; + /** the fill and stroke color of the background */ + readonly fill?: string; + readonly stroke?: string; + /** the font family for the whole controller, whose priority is lower than the fontFamily in the text style of each sub-controller */ + readonly fontFamily?: string; + + /** the play spped, means the playing time for 1 tick */ + readonly speed?: number; + /** whether play in loop */ + readonly loop?: boolean; + /** whether hide the 'time type controller' on the right-bottom */ + readonly hideTimeTypeController: boolean; + + /** style of the backward button. scale, offsetX, offsetY are also can be assigned to it to controll the size and position of the backward button */ + readonly preBtnStyle?: ShapeStyle; + + /** style of the forward button. scale, offsetX, offsetY are also can be assigned to it to controll the size and position of the forward button */ + readonly nextBtnStyle?: ShapeStyle; + + /** style of the play button. scale, offsetX, offsetY are also can be assigned to it to controll the size and position of the paly button */ + readonly playBtnStyle?: ShapeStyle; + + /** style of the 'speed controller'. scale, offsetX, offsetY are also can be assigned to it and each sub-styles to controll the size and position of the speed controller and sub-shapes*/ + readonly speedControllerStyle?: { + offsetX?: number, + offsetY?: number; + scale?: number + pointer?: ShapeStyle, + scroller?: ShapeStyle, + text?: ShapeStyle + }; + + /** style of the 'time type controller'. scale, offsetX, offsetY are also can be assigned to it and each sub-styles to controll the size and position of the speed controller and sub-shapes */ + readonly timeTypeControllerStyle?: { + offsetX?: number, + offsetY?: number; + scale?: number + check?: ShapeStyle, + box?: ShapeStyle, + text?: ShapeStyle + }; + /** [Supported from v4.5.1] The style of the background rect of the controller */ + readonly containerStyle?: ExtendedShapeStyle; + /** the text for the right-bottom switch controlling play with single time point or time range */ + readonly timePointControllerText?: string; + readonly timeRangeControllerText?: string +}> +``` + +#### Parameters for ControllerCfg + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| x | number | 0 | The beginning x position for the buttons group of the TimeBar | +| y | number | 0 | The beginning y position for the buttons group of the TimeBar | +| width | number | The width of the TimeBar | The width of the buttons group of the TimeBar, do not scale the sub-controllers but only affects the positions of them | +| height | number | 40 | The width of the buttons group of the TimeBar, do not scale the sub-controllers but only affects the positions of them | +| scale | number | 1 | The scale of the whole controller | +| speed | number | 1 | The play speed | +| loop | boolean | false | _Does not support for now_, whether play in loop | +| hideTimeTypeController | boolean | true | Whther hide the time type controller on the right bottom | +| fill | string | | The fillling color for the background of the controller | +| stroke | string | | The stroke color for the background of the buttons group | +| preBtnStyle | ShapeStyle | null | The style of the backward button. `scale`, `offsetX`, `offsetY` are also can be assigned to it to controll the size and position of the backward button | +| nextBtnStyle | ShapeStyle | null | The style of the forward button. `scale`, `offsetX`, `offsetY` are also can be assigned to it to controll the size and position of the forward button | +| playBtnStyle | ShapeStyle | null | The style of the play button. `scale`, `offsetX`, `offsetY` are also can be assigned to it to controll the size and position of the paly button | +| speedControllerStyle | { offsetX?: number, offsetY?: number, scale?: number, pointer?: ShapeStyle, text?: ShapeStyle, scroller?: ShapeStyle} | null | The style of the 'speed controller'. `scale`, `offsetX`, `offsetY` are also can be assigned to it and each sub-styles to controll the size and position of the speed controller and sub-shapes | +| timeTypeControllerStyle | { offsetX?: number, offsetY?: number, scale?: number, box?: ShapeStyle, check?: ShapeStyle, text?: ShapeStyle } | null | The style of the 'time type controller'. `scale`, `offsetX`, `offsetY` are also can be assigned to it and each sub-styles to controll the size and position of the speed controller and sub-shapes | +| containerStyle | ShapeStyle | {} | [Supported from v4.5.1] The style of the background rect of the controller | +| timePointControllerText | string | "单一时间" | The text for the right-bottom switch controlling play with single time point or time range | +| timeRangeControllerText | string | "时间范围" | The text for the right-bottom switch controlling play with single time point or time range | diff --git a/packages/site/docs/manual/middle/plugins/Plugins.zh.md b/packages/site/docs/manual/middle/plugins/Plugins.zh.md new file mode 100644 index 0000000000..09d9f9d1f1 --- /dev/null +++ b/packages/site/docs/manual/middle/plugins/Plugins.zh.md @@ -0,0 +1,994 @@ +--- +title: 使用组件 +order: 0 +--- + +G6 中支持插件提供了一些可插拔的组件,包括: + +- [Legend](#legend) *v4.3.0 起支持* +- [SnapLine](#snapline) *v4.3.0 起支持* +- [Grid](#grid) +- [Minimap](#minimap) +- [ImageMinimap](#image-minimap) +- [Edge Bundling](#edge-bundling) +- [Menu](#menu) +- [ToolBar](#toolbar) +- [TimeBar](#timebar) +- [Tooltip](#tooltip) +- [Fisheye](#fisheye) +- [EdgeFilterLens](#edge-filter-lens) + +## 配置方法 + +引入 G6 后,首先实例化需要使用的某插件对象。然后,在实例化图时将其配置到 `plugins` 中: + +```javascript +// 实例化 Grid 插件 +const grid = new G6.Grid(); +const minimap = new G6.Minimap(); +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [grid, minimap], // 配置 Grid 插件和 Minimap 插件 +}); +``` + +## Legend + +Legend 是 G6 内置的图例插件。用于说明图中不同类型的节点和边所代表的含义,并可以通过与图例的交互做简单的高亮和过滤。 *v4.3.0 起支持*。 + +img + +### 配置项 + +| 名称 | 类型 | 描述 | +| --- | --- | --- | +| data | GraphData | 图例的数据,与图数据格式相同。节点图例目前支持 `'circle'`,`'rect'`,和 `'ellipse'`,边图例目前支持 `'line'`、`'cubic'`、`'quadratic'`。通过指定每个数据项中的 `type` 字段以确定图例元素的类型,每个数据项中的 `order` 字段可用于同组图例的排序 | +| position | 'top' / 'top-left' / 'top-right' / 'right' / 'right-top' / 'right-bottom' / 'left' / 'left-top' / 'left-bottom' / 'bottom' / 'bottom-left' / 'bottom-right' | 图例在画布中的相对位置,默认为 `'top'`,代表在画布正上方 | +| padding | number / number[] | 图例区域内部内容到边框的距离,四位数组分别代表上、右、下、左边距 | +| margin | number / number[] | 图例区域与画布边界的距离,四位数组分别代表上、右、下、左边距。在 `position:'top'` 时只有上边距生效,其他情况类似 | +| offsetX | number | 图例区域离 `position` 对应的默认位置的 x 方向的偏移量,可被用于图例位置的微调 | +| offsetY | number | 图例区域离 `position` 对应的默认位置的 y 方向的偏移量,可被用于图例位置的微调 | +| containerStyle | ShapeStyle | 图例背景框的样式,格式与 [rect 图形的样式](/zh/docs/api/shapeProperties#矩形图形-rect)相同 | +| horiSep | number | 图例之间的水平间距 | +| vertiSep | number | 图例之间的竖直间距 | +| layout | 'vertical' / 'horizontal' | 图例的布局方式。默认为 `'horizontal'` 横向布局 | +| align | 'center' / 'right' / 'left' | 图例的对齐方式,可以是居中、右对齐、左对齐。默认为 `'center'` 居中 | +| title | string | 图例的标题文本内容,样式通过 `titleConfig` 设置 | +| titleConfig | object | 图例标题的样式,具体配置项如下 | +| titleConfig.position | 'center' / 'right' / 'left' | 图例标题的对齐方式,可以是居中、右对齐、左对齐。默认为 `'center'` 居中 | +| titleConfig.offsetX | number | 图例标题的 x 方向偏移,用于微调标题位置 | +| titleConfig.offsetY | number | 图例标题的 y 方向偏移,用于微调标题位置 | +| titleConfig[key] | ShapeStyle | 其他对于文本本身的样式,支持的内容与 [text 图形的样式](/zh/docs/api/shapeProperties#文本-text)相同 | +| filter | object | 通过图例的交互对主图元素进行过滤的配置项,具体配置如下 | +| filter.enable | boolean | 是否允许通过图例的交互对主图元素过滤,默认为 `false` | +| filter.multiple | boolean | 是否支持多种元素过滤,默认为 `false`,只有 `filter.trigger` 为 `'click'` 时方可多选图例 | +| filter.trigger | 'click' / 'mouseenter' | 触发主图元素过滤的图例交互方式,默认为 `click` | +| filter.legendStateStyles | { active?: ShapeStyle, inactive?: ShapeStyle | 在过滤时,图例本身的状态样式,包括 `filter.legendStateStyles.active` 和 `filter.legendStateStyles.inactive` 两种,每种的类型均为 ShapeStyle。类似图的 `nodeStateStyles` 配置 | +| filter.graphActiveState | string | 主图元素过滤时,被选中的主图元素的状态名,将寻找主图元素的对应的状态样式进行主图元素的更新。默认值为 `'active'` | +| filter.graphInactiveState | string | 主图元素过滤时,未被选中的主图元素的状态名,将寻找主图元素的对应的状态样式进行主图元素的更新。默认值为 `'inactive'` | +| filter.filterFunctions | { [key: string]: (d) => boolean; } | 由于图例的数据与主图解耦,因此需要配置每种图例对应的主图过滤函数,`key` 为图例数据的 `type`,值为函数,函数的参数为主图元素的数据,返回值为布尔型,代表是否被选中 | + +## SnapLine + +SnapLine 是 G6 内置的对齐线插件。 *v4.3.0 起支持*。 + +实例化时可以通过配置项调整 SnapLine 的样式和功能。 + +### 配置项 + +| 名称 | 类型 | 描述 | +| --- | --- | --- | +| line | ShapeStyle | 辅助线的样式 | +| itemAlignType | boolean、'horizontal' 、'vertical'、'center'; | 辅助线类型,true 表示全部 | + +## Grid + +Grid 插件在画布上绘制了网格。 + +img + +使用 [配置方法](#配置方法) 中代码实例化时可以通过配置项调整 Grid 的图片。 + +### 配置项 + +| 名称 | 类型 | 描述 | +| ---- | ------ | ---------------------------- | +| img | String | grid 图片,base64 格式字符串 | + +## Minimap + +Minimap 是用于快速预览和探索图的工具。 + +img + +实例化时可以通过配置项调整 Minimap 的样式和功能。 + +### 配置项 + +| 名称 | 类型 | 描述 | +| --- | --- | --- | +| container | Object | 放置 Minimap 的 DOM 容器。若不指定则自动生成 | +| className | String | 生成的 DOM 元素的 className | +| viewportClassName | String | Minimap 上视窗 DOM 元素的 className | +| type | String | 选项:`'default'`:渲染图上所有图形;`'keyShape'`:只渲染图上元素的 keyShape,以减少渲染成本;`'delegate'`:只渲染图上元素的大致图形,以降低渲染成本。渲染成本 `'default'` > `'keyShape'` > `'delegate'`。默认为 `'default'` | +| size | Array | Minimap 的大小 | +| delegateStyle | Object | 在 `type` 为 `'delegate'` 时生效,代表元素大致图形的样式 | +| hideEdge | Boolean | false | **v4.7.16 起支持** 控制 Minimap 上边的显示与隐藏,设置为 `true` 可在大规模图上大幅提升性能 | + +其中,delegateStyle 可以设置如下属性: + +| 名称 | 类型 | 描述 | +| ----------- | ------ | ---------- | +| fill | String | 填充颜色 | +| stroke | String | 描边颜色 | +| lineWidth | Number | 描边宽度 | +| opacity | Number | 透明度 | +| fillOpacity | Number | 填充透明度 | + +## Image Minimap + +由于 [Minimap](#minimap) 的原理是将主画布内容复制到 minimap 的画布上,在大数据量下可能会造成双倍的绘制效率成本。为缓解该问题,Image Minimap 采用另一种机制,根据提供的图片地址或 base64 字符串 `graphImg` 绘制 `` 代替 minimap 上的 canvas。该方法可以大大减轻两倍 canvas 绘制的压力。但 `graphImg` 完全交由 G6 的用户控制,需要注意主画布更新时需要使用 `updateGraphImg` 方法替换 `graphImg`。 + +img + +实例化时可以通过配置项调整 Image inimap 的样式和功能。 + +### 配置项 + +| 名称 | 类型 | 是否必须 | 描述 | +| --- | --- | --- | --- | +| graphImg | String | true | minimap 的图片地址或 base64 文本 | +| width | Number | false | minimap 的宽度。Image Minimap 的长宽比一定等于主图长宽比。因此,若设置了 `width`,则按照主画布容器长宽比确定 `height`,也就是说,`width` 的优先级高于 `height`。 | +| height | Number | false | minimap 的高度。Image Minimap 的长宽比一定等于主图长宽比。若未设置了 `width`,但设置了 `height`,则按照主画布容器长宽比确定 `width`;若设置了 `width` 则以 `width` 为准 | +| container | Object | false | 放置 Minimap 的 DOM 容器。若不指定则自动生成 | +| className | String | false | 生成的 DOM 元素的 className | +| viewportClassName | String | false | Minimap 上视窗 DOM 元素的 className | +| delegateStyle | Object | false | 在 `type` 为 `'delegate'` 时生效,代表元素大致图形的样式 | + +其中,`delegateStyle` 可以设置如下属性: + +| 名称 | 类型 | 描述 | +| ----------- | ------ | ---------- | +| fill | String | 填充颜色 | +| stroke | String | 描边颜色 | +| lineWidth | Number | 描边宽度 | +| opacity | Number | 透明度 | +| fillOpacity | Number | 填充透明度 | + +### API + +#### updateGraphImg(img) + +更新 minimap 图片。建议在主画布更新时使用该方法同步更新 minimap 图片。 + +参数: + +| 名称 | 类型 | 是否必须 | 描述 | +| ---- | ------ | -------- | -------------------------------- | +| img | String | true | minimap 的图片地址或 base64 文本 | + +### 用法 + +实例化 Image Minimap 插件时,`graphImg` 是必要参数。 + +```javascript +// 实例化 Image Minimap 插件 +const imageMinimap = new G6.ImageMinimap({ + width: 200, + graphImg: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*eD7nT6tmYgAAAAAAAAAAAABkARQnAQ' +}); +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [imageMinimap], // 配置 imageMinimap 插件 +}); + +graph.data(data); +graph.render() + +... // 一些主画布更新操作 +imageMinimap.updateGraphImg(img); // 使用新的图片(用户自己生成)替换 minimap 图片 + +``` + +## Edge Bundling + +在关系复杂、繁多的大规模图上,通过边绑定可以降低视觉复杂度。 + +img + +> 美国航线图边绑定。Demo 链接。该 Demo 教程。 + +实例化时可以通过配置项调整边绑定的功能。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| K | Number | 0.1 | 边绑定的强度 | +| lambda | Number | 0.1 | 算法的初始步长 | +| divisions | Number | 1 | 初始的切割点数,即每条边将会被切割成的份数。每次迭代将会被乘以 `divRate` | +| divRate | Number | 2 | 切割增长率,每次迭代都会乘以该数字。数字越大,绑定越平滑,但计算量将增大 | +| cycles | Number | 6 | 迭代次数 | +| iterations | Number | 90 | 初始的内迭代次数,每次外迭代中将会被乘以 `iterRate` | +| iterRate | Number | 0.6666667 | 迭代下降率 | +| bundleThreshold | Number | 0.6 | 判定边是否应该绑定在一起的相似容忍度,数值越大,被绑在一起的边相似度越低,数量越多 | + +## Menu + +Menu 用于配置节点上的右键菜单。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| className | string | null | menu 容器的 class 类名 | +| getContent | (evt?: IG6GraphEvent, graph?: IGraph) => HTMLDivElement / string | img | 菜单项内容,支持 DOM 元素或字符串 | +| handleMenuClick | (target: HTMLElement, item: Item, graph?: IGraph) => void | undefined | 点击菜单项的回调函数 | +| shouldBegin | (evt: G6Event) => boolean | undefined | 是否允许 menu 出现,可以根据 `evt.item`(当前鼠标事件中的元素) 或 `evt.target`(当前鼠标事件中的图形)的内容判断此时是否允许 menu 出现 | +| offsetX | number | 6 | menu 的 x 方向偏移值,需要考虑父级容器的 padding | +| offsetY | number | 6 | menu 的 y 方向偏移值,需要考虑父级容器的 padding | +| itemTypes | string[] | ['node', 'edge', 'combo'] | menu 作用在哪些类型的元素上,若只想在节点上显示,可将其设置为 ['node'] | +| trigger | 'click' / 'contextmenu' | 'contextmenu' | menu 出现的触发方式,默认为 `'contextmenu'`,即右击。`'click'` 代表左击。*v4.3.2 起支持 'click'* | + +### 用法 + +实例化 Menu 插件时,如果不传参数,则使用 G6 默认提供的值,只能展示默认的菜单项,不能进行任何操作。 + +```javascript +// 实例化 Menu 插件 +const menu = new G6.Menu(); +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [menu], // 配置 Menu 插件 +}); +``` + +#### DOM Menu + +```javascript +const menu = new G6.Menu({ + offsetX: 6, + offsetX: 10, + itemTypes: ['node'], + getContent(e, graph) { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = `
    +
  • 测试01
  • +
  • 测试01
  • +
  • 测试01
  • +
  • 测试01
  • +
  • 测试01
  • +
` + return outDiv + }, + handleMenuClick(target, item, graph) { + console.log(target, item, graph) + }, +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [menu], // 配置 Menu 插件 +}); +``` + +#### String Menu + +```javascript +const menu = new G6.Menu({ + getContent(evt) { + return `
    +
  • 测试02
  • +
  • 测试02
  • +
  • 测试02
  • +
  • 测试02
  • +
  • 测试02
  • +
`; + }, + handleMenuClick(target, item) { + console.log(target, item) + }, +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [menu], // 配置 Menu 插件 +}); +``` + +## ToolBar + +ToolBar 集成了以下常见的操作: + +- 重做; +- 撤销; +- 放大; +- 缩小; +- 适应屏幕; +- 实际大小。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | --- | +| container | HTMLDivElement | null | ToolBar 容器,如果不设置,则默认使用 canvas 的 DOM 容器 | +| className | string | null | ToolBar 内容元素的 class 类名 | +| getContent | (graph?: IGraph) => HTMLDivElement | string | img | ToolBar 内容,支持 DOM 元素或字符串 | +| handleClick | (code: string, graph: IGraph) => void | undefined | 点击 ToolBar 中每个图标的回调函数 | +| position | Point | null | ToolBar 的位置坐标 | + +### 用法 + +#### 默认用法 + +默认的 ToolBar 提供了撤销、重做、放大等功能。 + +```javascript +const toolbar = new G6.ToolBar(); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [toolbar], // 配置 ToolBar 插件 +}); +``` + +#### 使用 String 自定义 ToolBar 功能 + +```javascript +const tc = document.createElement('div'); +tc.id = 'toolbarContainer'; +document.body.appendChild(tc); + +const toolbar = new G6.ToolBar({ + container: tc, + getContent: () => { + return ` +
    +
  • 增加节点
  • +
  • 撤销
  • +
+ ` + }, + handleClick: (code, graph) => { + if (code === 'add') { + graph.addItem('node', { + id: 'node2', + label: 'node2', + x: 300, + y: 150 + }) + } else if (code === 'undo') { + toolbar.undo() + } + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [toolbar], // 配置 ToolBar 插件 +}); +``` + +#### 使用 DOM 自定义 ToolBar 功能 + +```javascript +const toolbar = new G6.ToolBar({ + getContent: () => { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = `
    +
  • 测试01
  • +
  • 测试02
  • +
  • 测试03
  • +
  • 测试04
  • +
  • 测试05
  • +
` + return outDiv + }, + handleClick: (code, graph) => { + + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [toolbar], // 配置 ToolBar 插件 +}); +``` + +## ToolTip + +ToolTip 插件主要用于在节点和边上展示一些辅助信息,G6 4.0 以后,Tooltip 插件将会替换 Behavior 中的 tooltip。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| className | string | null | tooltip 容器的 class 类名 | +| container | HTMLDivElement | null | Tooltip 容器,如果不设置,则默认使用 canvas 的 DOM 容器 | +| getContent | (evt?: IG6GraphEvent) => HTMLDivElement / string | img | tooltip 内容,支持 DOM 元素或字符串 | +| shouldBegin | (evt: G6Event) => boolean | undefined | 是否允许 tooltip 出现,可以根据 `evt.item`(当前鼠标事件中的元素) 或 `evt.target`(当前鼠标事件中的图形)的内容判断此时是否允许 tooltip 出现 | +| offsetX | number | 6 | tooltip 的 x 方向偏移值,需要考虑父级容器的 padding | +| offsetY | number | 6 | tooltip 的 y 方向偏移值,需要考虑父级容器的 padding | +| itemTypes | string[] | ['node', 'edge', 'combo'] | tooltip 作用在哪些类型的元素上,若只想在节点上显示,可将其设置为 ['node'] | + +### 用法 + +默认的 Tooltip 只展示元素类型和 ID,一般情况下都需要用户自己定义 Tooltip 上面展示的内容。 + +#### Dom Tooltip + +```javascript +const tooltip = new G6.Tooltip({ + offsetX: 10, + offsetY: 20, + getContent(e) { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = ` +

自定义tooltip

+
    +
  • Label: ${e.item.getModel().label || e.item.getModel().id}
  • +
` + return outDiv + }, + itemTypes: ['node'] +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [tooltip], // 配置 Tooltip 插件 +}); +``` + +#### String Tooltip + +```javascript +const tooltip = new G6.Tooltip({ + getContent(e) { + return `
+ +
`; + }, +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [tooltip], // 配置 Tooltip 插件 +}); +``` + +## Fisheye + +Fisheye 鱼眼放大镜是为 focus+context 的探索场景设计的,它能够保证在放大关注区域的同时,保证上下文以及上下文与关注中心的关系不丢失。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | --- | +| trigger | 'mousemove' / 'click' / 'drag' | 'mousemove' | 放大镜的触发事件 | +| d | Number | 1.5 | 放大系数,数值越大,放大程度越大 | +| r | Number | 300 | 放大区域的范围半径 | +| delegateStyle | Object | { stroke: '#000', strokeOpacity: 0.8, lineWidth: 2, fillOpacity: 0.1, fill: '#ccc' } | 放大镜蒙层样式 | +| showLabel | Boolean | false | 若 label 默认被隐藏,是否在关注区域内展示 label | +| maxR | Number | 图的高度 | 滚轮调整缩放范围的最大半径 | +| minR | Number | 0.05 \* 图的高度 | 滚轮调整缩放范围的最小半径 | +| maxD | Number | 5 | `trigger` 为 `'mousemove'` / `'click'` 时,可以在放大镜上左右拖拽调整缩放系数。maxD 指定了这种调整方式的最大缩放系数,建议取值范围 [0, 5]。若使用 `minimap.updateParam` 更新参数不受该系数限制 | +| minD | Number | 0 | `trigger` 为 `'mousemove'` / `'click'` 时,可以在放大镜上左右拖拽调整缩放系数。maxD 指定了这种调整方式的最小缩放系数,建议取值范围 [0, 5]。若使用 `minimap.updateParam` 更新参数不受该系数限制 | +| scaleRBy | 'wheel'/'drag'/'unset'/undefined | false | 'unset' | 终端用户调整放大镜范围大小的方式 | +| scaleDBy | 'wheel'/'drag'/'unset'/undefined | false | 'unset' | 终端用户调整放大镜缩放系数的方式 | +| showDPercent | Boolean | false | true | 是否在放大镜下方显示当前缩放系数的比例值(与 minD、maxD 相较) | + +### 成员函数 + +#### updateParams(cfg) + +用于更新该 FishEye 的部分配置项,包括 `trigger`,`d`,`r`,`maxR`,`minR`,`maxD`,`minD`,`scaleRBy`,`scaleDBy`。例如: + +```javascript +const fisheye = new G6.Fisheye({ + trigger: 'mousemove' +}); + +... // 其他操作 + +fisheye.updateParams({ + d: 2, + r: 500, + // ... +}) +``` + +### 用法 + +```javascript +const fisheye = new G6.Fisheye({ + trigger: 'mousemove', + d: 1.5, + r: 300, + delegateStyle: clone(lensDelegateStyle), + showLabel: false +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [fisheye], // 配置 fisheye 插件 +}); +``` + +## Edge Filter Lens + +EdgeFilterLens 边过滤镜可以将关注的边保留在过滤镜范围内,其他边将在该范围内不显示。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| trigger | 'drag' / 'mousemove' / 'click' | 'mousemove' | 过滤镜的触发事件 | +| type | 'one' / 'both' / 'only-source' / 'only-target' | 'both' | 根据边两端点作为边过滤的简单条件。`'one'`:边至少有一个端点在过滤镜区域内,则在该区域内显示该边;`'both'`:两个端点都在过滤区域内,则在该区域显示该边;`'only-source'`:只有起始端在过滤镜区域内,则在该区域显示该边;`'only-target'`:只有结束端在过滤区域内,则在该区域显示该边。更复杂的条件可以使用 `shouldShow` 指定 | +| shouldShow | (d?: unknown) => boolean | undefined | 边过滤的自定义条件。参数 `d` 为边每条边的数据,用户可以根据边的参数返回布尔值。返回 `true` 代表该边需要在过滤镜区域内显示,`false` 反之。 | +| r | Number | 60 | 过滤镜的范围半径 | +| delegateStyle | Object | { stroke: '#000', strokeOpacity: 0.8, lineWidth: 2, fillOpacity: 0.1, fill: '#ccc' } | 过滤镜蒙层样式 | +| showLabel | 'edge' / 'node' / 'both' | 'edge' | 若 label 默认被隐藏,是否在关注区域内展示对应元素类型的 label。'both' 代表节点和边的 label 都在过滤镜区域显示 | +| maxR | Number | 图的高度 | 滚轮调整过滤镜的最大半径 | +| minR | Number | 0.05 \* 图的高度 | 滚轮调整过滤镜的最小半径 | +| scaleRBy | 'wheel' / undefined | 'wheel' | 终端用户调整过滤镜大小的方式,undefined 代表不允许终端用户调整 | + +### 成员函数 + +#### updateParams(cfg) + +用于更新该过滤镜的部分配置项,包括 `trigger`,`type`,`r`,`maxR`,`minR`,`scaleRBy`,`showLabel`,`shouldShow`。例如: + +```javascript +const filterLens = new G6.EdgeFilterLens({ + trigger: 'drag' +}); + +... // 其他操作 + +filterLens.updateParams({ + r: 500, + // ... +}) +``` + +### 用法 + +```javascript +const filterLens = new G6.EdgeFilterLens({ + trigger: 'mousemove', + r: 300, + shouldShow: d => { + return d.size > 10; + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [filterLens], // 配置 filterLens 插件 +}); +``` + +## TimeBar + +[AntV G6](https://github.com/antvis/G6) 内置了三种形态的 TimeBar 组件: + +- 带有趋势图的 TimeBar 组件; +- 简易版的 TimeBar 组件; +- 刻度 TimeBar 组件。 + +并且每种类型的 TimeBar 组件都可以配合播放、快进、后退等控制按钮组使用。 + + +
趋势图 TimeBar 组件
+ + +
简易版 TimeBar 组件
+ + +
刻度 TimeBar 组件
+ +
在趋势图 TimeBar 基础上,我们可以通过配置数据,实现更加复杂的趋势图 TimeBar 组件,如下图所示。 + + + +
虽然 G6 提供了各种不同类型的 TimeBar 组件,但在使用的方式却非常简单,通过配置字段就可以进行区分。

关于 TimeBar 的使用案例,请参考[这里](https://g6.antv.antgroup.com/examples/tool/timebar#timebar)。
+ +### 使用 TimeBar 组件 + +使用 G6 内置的 TimeBar 组件,和使用其他组件的方式完全相同。 + +```javascript +import G6 from '@antv/g6'; + +const timebar = new G6.TimeBar({ + width: 500, + height: 150, + padding: 10, + type: 'trend', + trend: { + data: timeBarData, + }, +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [timebar], +}); +``` + +
通过上面的方式,我们就可以在图中使用 TimeBar 组件了,当实例化 TimeBar 时,type 参数值为 trend,表示我们实例化的是趋势图组件,效果如下图所示。 + + + +
当设置 type 为 simple 时,就可以使用简易版的 TimeBar。 + + + +
当设置 type 为 tick 时,表示我们要使用刻度 TimeBar 组件,但此时要注意的是,**刻度时间轴的配置项是通过 tick 对象配置而不是 trend 对象**,这也是刻度时间轴和趋势即简易时间轴不同的地方。 + +```javascript +const timebar = new G6.TimeBar({ + width, + height: 150, + type: 'tick', + tick: { + data: timeBarData, + width, + height: 42, + tickLabelFormatter: (d) => { + const dateStr = `${d.date}`; + if ((count - 1) % 10 === 0) { + return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`; + } + return false; + }, + tooltipFomatter: (d) => { + const dateStr = `${d}`; + return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`; + }, + }, +}); +``` + + + +### 事件监听 + +TimeBar 插件暴露除了几个时机事件,方便用户监听内部状态的变化。以下事件可通过 `graph.on('eventname', e => {})` 进行监听。 + +| 事件名称 | 描述 | +| --- | --- | +| valuechange | 时间轴的时间范围发生变化时触发 | +| timebarstartplay | 时间轴开始播放时触发 | +| timebarendplay | 时间轴播放结束时触发 | + +### 参数定义 + +#### 接口定义 + +完整的 TimeBar 的接口定义如下: + +```javascript +interface TimeBarConfig extends IPluginBaseConfig { + // position size + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + readonly padding?: number; + + readonly type?: 'trend' | 'simple' | 'tick'; + // 趋势图配置项 + readonly trend?: TrendConfig; + // 滑块、及前后背景的配置 + readonly slider?: SliderOption; + + // 当 type 是 tick 时,这是 tick 类型时间轴的配置项 + // 当 type 是 trend 或 simple 时,这是时间轴下方时间刻度文本的配置项 + readonly tick?: TimeBarSliceOption | TickCfg; + + // 控制按钮 + readonly controllerCfg?: ControllerCfg; + + // [v4.5.1 起支持] 容器的 CSS 样式 + readonly containerCSS?: Object; + + // [v4.5.1 起支持] 过滤的类型, ['node', 'edge'], 默认为 ['node'] + readonly filterItemTypes?: string[]; + + // [v4.5.1 起废弃,由 filterItemTypes 代替] 是否过滤边,若为 true,则需要配合边数据上有 date 字段,过滤节点同时将不满足 date 在选中范围内的边也过滤出去;若为 false,则仅过滤节点以及两端节点都被过滤出去的边 + readonly filterEdge?: boolean; + + // [v4.5.1 起支持] 是否通过 graph.changeData 改变图上数据从而达到筛选目的。若为 false 则将使用 graph.hideItem 和 graph.showItem 以隐藏/展示图上元素从而达到筛选目的 + readonly changeData?: boolean; + + // TimeBar 时间范围变化时的回调函数,当不定义该函数时,时间范围变化时默认过滤图上的数据 + rangeChange?: (graph: IGraph, minValue: string, maxValue: string) => void; + + // [v4.5.1 起支持] 用户根据节点/边数据返回对应时间值的方法 + getDate?: (d: any) => number; + + // [v4.5.1 起支持] 用户根据节点/边数据返回对应 value 的方法。value 用于在 type 为 trend 的时间轴上显示趋势线 + getValue?: (d: any) => number; + + // [v4.5.1 起支持] 在过滤图元素时是否要忽略某些元素。返回 true,则忽略。否则按照正常过滤逻辑处理 + shouldIgnore?: (itemType: 'node' | 'edge', model: any, dateRage: { min: number, max: number }) => boolean; +} +``` + +#### 接口参数说明 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | TimeBar 容器,如果不设置,则默认创建 className 为 g6-component-timebar 的 DOM 容器 | +| x | number | 0 | TimeBar 开始 x 坐标 | +| y | number | 0 | TimeBar 开始 y 坐标 | +| width | number | | **必选**,TimeBar 容器宽度 | +| height | number | | **必选**,TimeBar 高度 | +| padding | number/number[] | 10 | TimeBar 距离容器的间距值 | +| type | 'trend' / 'simple' / 'tick' | trend | 默认的 TimeBar 类型,默认为趋势图样式 | +| trend | TrendConfig | null | Timebar 中趋势图的配置项,当 type 为 trend 或 simple 时,该字段必选 | +| slider | SliderOption | null | TimeBar 组件背景及控制调节范围的滑块的配置项 | +| tick | TimeBarSliceOption / TickCfg | null | 当 type 是 tick 时,这是 tick 类型时间轴的配置项,该字段必须按;当 type 是 trend 或 simple 时,这是时间轴下方时间刻度文本的配置项 | +| controllerCfg | ControllerCfg | null | 控制按钮组配置项 | +| containerCSS | Object | null | [v4.5.1 起支持] 容器的 CSS 样式 | +| filterItemTypes | string[] | null | [v4.5.1 起支持] 过滤的类型, ['node', 'edge'], 默认为 ['node'] | +| filterEdge | boolean | false | [v4.5.1 起废弃,由 filterItemTypes 代替] 是否过滤边,若为 true,则需要配合边数据上有 date 字段,过滤节点同时将不满足 date 在选中范围内的边也过滤出去;若为 false,则仅过滤节点以及两端节点都被过滤出去的边 | +| changeData | boolean | null | [v4.5.1 起支持] 是否通过 graph.changeData 改变图上数据从而达到筛选目的。若为 false 则将使用 graph.hideItem 和 graph.showItem 以隐藏/展示图上元素从而达到筛选目的 | +| rangeChange | Function | null | TimeBar 时间范围变化时的回调函数,当不定义该函数时,时间范围变化时默认过滤图上的数据 | +| getDate | (d: any) => number | null | [v4.5.1 起支持] 用户根据节点/边数据返回对应时间值的方法 | +| getValue | (d: any) => number | null | [v4.5.1 起支持] 用户根据节点/边数据返回对应 value 的方法。value 用于在 type 为 trend 的时间轴上显示趋势线 | +| shouldIgnore | (itemType: 'node' | 'edge', model: any, dateRage: { min: number, max: number }) => boolean | null | [v4.5.1 起支持] 在过滤图元素时是否要忽略某些元素。返回 true,则忽略。否则按照正常过滤逻辑处理 | + +#### TrendConfig 接口定义 + +> 暂不支持刻度文本的样式配置 + +```javascript +interface TrendConfig { + // 数据 + readonly data: { + date: string; + value: string; + }[]; + // 位置大小 + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + // 样式 + readonly smooth?: boolean; + readonly isArea?: boolean; + readonly lineStyle?: ShapeStyle; + readonly areaStyle?: ShapeStyle; + readonly interval?: Interval; +} +``` + +#### TrendConfig 参数说明 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| x | number | 0 | 趋势图开始 x 坐标 | +| y | number | 0 | 趋势图开始 y 坐标 | +| width | number | TimeBar 容器宽度 | TimeBar 趋势图宽度,不建议自己设定,如果设定时需要同步设置 slider 中的 width 值 | +| height | number | type=trend:默认为 28
type=simple:默认为 8 | TimeBar 趋势图高度,不建议自己设定,如果设定时需要同步设置 slider 中的 height 值 | +| smooth | boolean | false | 是否是平滑的曲线 | +| isArea | boolean | false | 是否显示面积图 | +| lineStyle | ShapeStyle | null | 折线的样式配置 | +| areaStyle | ShapeStyle | null | 面积的样式配置项,只有当 isArea 为 true 时生效 | +| interval | Interval | null | 柱状图配置项,当配置了该项后,趋势图上会展现为混合图样式。`Interval = { data: number[], style: ShapeStyle }`,`style` 除 `ShapeStyle` 类型中图形的样式外,还可配置 `barWidth` 配置柱状图柱子的宽度。 | + +#### SliderOption 接口定义 + +```javascript +export type SliderOption = Partial<{ + readonly width?: number; + readonly height?: number; + readonly backgroundStyle?: ShapeStyle; + readonly foregroundStyle?: ShapeStyle; + // 滑块样式 + readonly handlerStyle?: { + width?: number; + height?: number; + style?: ShapeStyle; + }; + readonly textStyle?: ShapeStyle; + // 初始位置 + readonly start: number; + readonly end: number; + // 滑块文本 + readonly minText: string; + readonly maxText: string; +}>; +``` + +#### SliderOption 参数说明 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| width | number | TimeBar 容器宽度 - 2 \* padding | 趋势图背景框宽度,不建议自己设定,如果设定时要同步修改 trend 中 width 值 | +| height | number | 趋势图默认为 28
简易版默认为 8 | TimeBar 趋势图高度,不建议自己设定,如果设定时需要同步设置 trend 中的 height 值 | +| backgroundStyle | ShapeStyle | null | 背景样式配置项 | +| foregroundStyle | ShapeStyle | null | 前景色样式配置,即选中范围的样式配置项 | +| handlerStyle | ShapeStyle | null | 滑块的样式配置项 | +| textStyle | ShapeStyle | null | 滑块上文本的样式配置项 | +| start | number | 0.1 | 开始位置 | +| end | number | 0.9 | 结束位置 | +| minText | string | min | 最小值文本 | +| maxText | string | max | 最大值文本 | + +#### TimeBarSliceOption + +```javascript +export interface TimeBarSliceOption { + // position size + readonly x?: number; + readonly y?: number; + readonly width?: number; + readonly height?: number; + readonly padding?: number; + + // styles + readonly selectedTickStyle?: TickStyle; + readonly unselectedTickStyle?: TickStyle + readonly tooltipBackgroundColor?: string; + + readonly start?: number; + readonly end?: number; + + // 数据 + readonly data: { + date: string; + value: string; + }[]; + + // 自定义标签格式化函数 + readonly tickLabelFormatter?: (d: any) => string | boolean; + // 自定义 tooltip 内容格式化函数 + readonly tooltipFomatter?: (d: any) => string; +} +``` + +#### TimeBarSliceOption 参数说明 + +| 名称 | 类型 | 默认值 | 描述 | +| ---------------------- | ----------------- | ------ | ------------------------------ | +| x | number | 0 | 刻度 TimeBar 开始 x 坐标 | +| y | number | 0 | 刻度 TimeBar 开始 y 坐标 | +| width | number | | 必选,刻度 TimeBar 宽度 | +| height | number | | 必选,刻度 TimeBar 高度 | +| padding | number / number[] | 0 | 刻度 TimeBar 距离边界的间距 | +| selectedTickStyle | ShapeStyle | null | 选中刻度的样式配置项 | +| unselectedTickStyle | ShapeStyle | null | 未选中刻度的样式配置项 | +| tooltipBackgroundColor | ShapeStyle | null | tooltip 背景框配置项 | +| start | number | 0.1 | 开始位置 | +| end | number | 0.9 | 结束位置 | +| data | any[] | [] | 必选,刻度时间轴的刻度数据 | +| tickLabelFormatter | Function | null | 刻度的格式化回调函数 | +| tooltipFomatter | Function | null | tooltip 上内容格式化的回调函数 | + + +#### TickCfg 接口定义 + +```javascript +export interface TickCfg { + // 时间轴下方文本的格式化函数 + readonly tickLabelFormatter?: (d: any) => string | undefined; + // 时间轴下方文本的图形样式。[v4.5.1 起支持] 可配置 tickLabelStyle.rotate 以控制时间轴下方每个文本的旋转角度,可避免文本相互重叠 + readonly tickLabelStyle?: ShapeStyle; + // 时间轴下方文本上的竖线图形样式 + readonly tickLineStyle?: ShapeStyle; +} +``` + +#### TickCfg 参数说明 + +| Name | Type | Default Value | Description | +| --- | --- | --- | --- | +| tickLabelFormatter | Function | null | 时间轴下方文本的格式化函数 | +| tickLabelStyle | ShapeStyle | {} | 时间轴下方文本的图形样式。[v4.5.1 起支持] 可配置 tickLabelStyle.rotate 以控制时间轴下方每个文本的旋转角度,可避免文本相互重叠 | +| tickLineStyle | ShapeStyle | {} | 时间轴下方文本上方的竖线的图形样式 | + +#### ControllerCfg 接口定义 + +> 暂不支持 + +> 控制按钮暂不支持配置样式 + +> 不支持循环播放 + +```javascript +type ControllerCfg = Partial<{ + + /** 控制栏的起始位置以及宽高,width height 将不缩放内部子控制器,仅影响它们的位置分布。需要缩放请使用 scale */ + readonly x?: number; + readonly y?: number; + readonly width: number; + readonly height: number; + /** 控制栏缩放比例 */ + readonly scale?: number; + /** 控制器背景的颜色和描边色 */ + readonly fill?: string; + readonly stroke?: string; + /** 整个控制栏的字体样式,优先级低于各个子控制器的 text 内的 fontFamily */ + readonly fontFamily?: string; + + /** 播放速度,1 个 tick 花费时间 */ + readonly speed?: number; + /** 是否循环播放 */ + readonly loop?: boolean; + /** 是否隐藏右下角的 ’播放时间类型切换器‘ */ + readonly hideTimeTypeController: boolean; + + /** ‘上一帧’按钮的样式,同时可以为其配置 scale、offsetX、offsetY 单独控制该控制器的缩放以及平移 */ + readonly preBtnStyle?: ShapeStyle; + + /** ‘下一帧’按钮的样式,同时可以为其配置 scale、offsetX、offsetY 单独控制该控制器的缩放以及平移 */ + readonly nextBtnStyle?: ShapeStyle; + + /** ‘播放’ 与 ‘暂停’ 按钮的样式,同时可以为其配置 scale、offsetX、offsetY 单独控制该控制器的缩放以及平移 */ + readonly playBtnStyle?: ShapeStyle; + + /** ‘速度控制器’ 的样式,包括速度的指针、速度指示滚轮(横线)、文本的样式,同时可以为 speedControllerStyle 及其子图形样式配置 scale、offsetX、offsetY 单独控制该控制器及其子图形的缩放以及平移) */ + readonly speedControllerStyle?: { + offsetX?: number, + offsetY?: number; + scale?: number + pointer?: ShapeStyle, + scroller?: ShapeStyle, + text?: ShapeStyle + }; + + /** ‘播放时间类型切换器’ 的样式,包括 checkbox 的框、checkbox 的选中勾、文本的样式,同时可以为 timeTypeControllerStyle 及其子图形样式配置 scale、offsetX、offsetY 单独控制该控制器及其子图形的缩放以及平移 */ + readonly timeTypeControllerStyle?: { + offsetX?: number, + offsetY?: number; + scale?: number + check?: ShapeStyle, + box?: ShapeStyle, + text?: ShapeStyle + }; + /** [v4.5.1 起支持] 控制栏背景方框的样式 */ + readonly containerStyle?: ExtendedShapeStyle; + /** ‘播放时间类型切换器’单一文本时的文本,默认为‘单一时间’ */ + readonly timePointControllerText?: string; + /** ‘播放时间类型切换器’单一文本时的文本,默认为‘时间范围’ */ + readonly timeRangeControllerText?: string; +}> +``` + +#### ControllerCfg 参数说明 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| x | number | 0 | 控制栏开始 x 坐标 | +| y | number | 0 | 控制栏开始 y 坐标 | +| width | number | TimeBar 宽度 | 控制栏宽度,将不缩放内部子控制器,仅影响它们的位置分布 | +| height | number | 40 | 控制栏高度,将不缩放内部子控制器,仅影响它们的位置分布 | +| scale | number | 1 | 控制栏缩放比例 | +| speed | number | 1 | 播放速度 | +| loop | boolean | false | 暂不支持,是否循环播放 | +| hideTimeTypeController | boolean | true | 是否隐藏时间类型切换 | +| fill | string | | 控制栏背景框填充色 | +| stroke | string | | 整个控制栏的字体样式,优先级低于各个子控制器的 text 内的 fontFamily | +| preBtnStyle | string | null | 控制栏背景框边框色 | +| preBtnStyle | ShapeStyle | null | ‘上一帧’按钮的样式,同时可以为其配置 `scale`、`offsetX`、`offsetY` 单独控制该控制器的缩放以及平移 | +| nextBtnStyle | ShapeStyle | null | ‘下一帧’按钮的样式,同时可以为其配置 `scale`、`offsetX`、`offsetY` 单独控制该控制器的缩放以及平移 | +| playBtnStyle | ShapeStyle | null | ‘播放’ 与 ‘暂停’ 按钮的样式,同时可以为其配置 `scale`、`offsetX`、`offsetY` 单独控制该控制器的缩放以及平移 | +| speedControllerStyle | { offsetX?: number, offsetY?: number, scale?: number, pointer?: ShapeStyle, text?: ShapeStyle, scroller?: ShapeStyle} | null | ‘速度控制器’ 的样式,包括速度的指针、速度指示滚轮(横线)、文本的样式,同时可以为 `speedControllerStyle` 及其子图形样式配置 `scale`、`offsetX`、`offsetY` 单独控制该控制器及其子图形的缩放以及平移 | +| timeTypeControllerStyle | { offsetX?: number, offsetY?: number, scale?: number, box?: ShapeStyle, check?: ShapeStyle, text?: ShapeStyle } | null | ‘播放时间类型切换器’ 的样式,包括 checkbox 的框、checkbox 的选中勾、文本的样式,同时可以为 `timeTypeControllerStyle` 及其子图形样式配置 `scale`、`offsetX`、`offsetY` 单独控制该控制器及其子图形的缩放以及平移 | +| containerStyle | ShapeStyle | {} | 控制栏背景方框的样式 | +| timePointControllerText | string | "单一时间" | 右下角“单一时间”文本,默认为”单一时间“ | +| timePointControllerText | string | "时间范围" | 右下角“单一时间”文本,默认为”时间范围时间“ | diff --git a/packages/site/docs/manual/middle/plugins/autoZoomTooltip.en.md b/packages/site/docs/manual/middle/plugins/autoZoomTooltip.en.md new file mode 100644 index 0000000000..32ea7f0539 --- /dev/null +++ b/packages/site/docs/manual/middle/plugins/autoZoomTooltip.en.md @@ -0,0 +1,29 @@ +--- +title: How to Auto Zoom Tooltip & ContextMenu When Zooming Graph +order: 1 +--- + +When zooming the canvas, sometimes we hope that the Tooltip, ContextMenu and other components will also automatically follow the canvas zoom. In actual business requirements, we also encounter such a demand:[https://github.com/antvis/G6/issues/2111](https://github.com/antvis/G6/issues/2111)。
+ +### Tooltip auto zoom + +In G6, zooming the canvas is achieved through zoom-canvas Behavior. The wheelzoom event will be triggered during zooming. Therefore, we only need to listen to this event to allow Tooltip to zoom automatically. + +```javascript +graph.on('wheelzoom', (e) => { + e.stopPropagation(); + // className g6-component-tooltip by default + const tooltips = Array.from(document.getElementsByClassName('g6-component-tooltip')); + tooltips.forEach((tooltip) => { + if (tooltip && tooltip.style) { + tooltip.style.transform = `scale(${graph.getZoom()})`; + } + }); +}); +``` + +
Please refer to [here](https://codesandbox.io/s/test-tootip-zoom-zc5yn?file=/index.js) for the complete example. + +### ContextMenu auto zoom + +When the canvas is zoomed, the automatic zooming of ContextMenu is implemented in exactly the same way as Tooltip. You only need to modify the className of the Tooltip container to the className of the ContextMenu container. diff --git a/packages/site/docs/manual/middle/plugins/autoZoomTooltip.zh.md b/packages/site/docs/manual/middle/plugins/autoZoomTooltip.zh.md new file mode 100644 index 0000000000..51f0eb4c8e --- /dev/null +++ b/packages/site/docs/manual/middle/plugins/autoZoomTooltip.zh.md @@ -0,0 +1,29 @@ +--- +title: 缩放画布时 tooltip 和 ContextMenu 自动缩放 +order: 1 +--- + +当缩放画布的时候,有时候我们希望 Tooltip、ContextMenu 等组件也自动跟随画布缩放,在实际业务需求中,也遇到了这样的诉求:[https://github.com/antvis/G6/issues/2111](https://github.com/antvis/G6/issues/2111)。
+ +### Tooltip 自动缩放 + +G6 里面缩放画布是通过 zoom-canvas Behavior 实现的,缩放过程中会触发 wheelzoom 事件,因此,我们只需要监听该事件,就可以让 Tooltip 自动缩放。 + +```javascript +graph.on('wheelzoom', (e) => { + e.stopPropagation(); + // 这里的 className 根据实际情况而定,默认是 g6-component-tooltip + const tooltips = Array.from(document.getElementsByClassName('g6-component-tooltip')); + tooltips.forEach((tooltip) => { + if (tooltip && tooltip.style) { + tooltip.style.transform = `scale(${graph.getZoom()})`; + } + }); +}); +``` + +也可以根据我们的实际需要,控制 Tooltip 缩放的最大和最小比例。

完整的示例请参考[这里](https://codesandbox.io/s/test-tootip-zoom-zc5yn?file=/index.js)。
+ +### ContextMenu 自动缩放 + +当画布缩放时,ContextMenu 的自动缩放实现方式和 Tooltip 的完全一样,只需要将 Tooltip 容器的 className 名称修改为 ContextMenu 容器的 className 名即可。 diff --git a/packages/site/docs/manual/middle/states/bindEvent.en.md b/packages/site/docs/manual/middle/states/bindEvent.en.md new file mode 100644 index 0000000000..ec97a7db0e --- /dev/null +++ b/packages/site/docs/manual/middle/states/bindEvent.en.md @@ -0,0 +1,103 @@ +--- +title: Listener and Event +order: 0 +--- + +G6 manages events by combining the [Behavior](/en/docs/manual/middle/states/defaultBehavior) and [Mode](/en/docs/manual/middle/states/mode). Besides, G6 provides lots of listeners for single events and timing, which monitor the canvas/nodes/edges and the timing of function call. For more information about the events in G6, please refer to [Event API](/en/docs/api/Event). **All the events are mounted on the graph.** These events can be categorized into the following six levels: + +1. Global Events + +The global events will be triggered when it happens on the canvas DOM scope, e.g. `mousedown`, `mouseup`, `click`, `mouseenter`, `mouseleave`, and so on; + +```javascript +graph.on('click', (ev) => { + const shape = ev.target; + const item = ev.item; + if (item) { + const type = item.getType(); + } +}); +``` + +2. Canvas Events + +The canvas events will be triggered when it is happens on the blank area on the canvas, e.g. `canvas:mousedown`, `canvas:click`, and so on; + +```javascript +graph.on('canvas:click', (ev) => { + const shape = ev.target; + const item = ev.item; + if (item) { + const type = item.getType(); + } +}); +``` + +3. Item Events + +The events on nodes/edges/combos, e.g. `node:mousedown`, `edge:click`, `combo:click`, and so on. It is named as `type:eventName`. + +```javascript +graph.on('node:click', (ev) => { + const node = ev.item; // clicked node + const shape = ev.target; // clicked shape, you could do different things for different shapes to achieve local response on an item + // ... do sth +}); + +graph.on('edge:click', (ev) => { + const edge = ev.item; // clicked edge + const shape = ev.target; // clicked shape, you could do different things for different shapes to achieve local response on an item + // ... do sth +}); + +graph.on('combo:click', (ev) => { + const combo = ev.item; // clicked combo + const shape = ev.target; // clicked shape, you could do different things for different shapes to achieve local response on an item + // ... do sth +}); +``` + +4. Shape Events + +The events on rendering shapes of Node/Ede/Combo item, e.g. `circle-shape:mousedown`, `circle-shape:click` and so on. It is named as `shapeName:eventName`. It can be used for local response, similar to response according to the `target` in `graph.on('node:click', fn)`. + +About the shape's 'name': + - For buit-in Node/Edge/Combo type, you could know the `name` value by `graph.on('node:click', (ev) => console.log(ev.target.get('name')))` during developping. + - For custom Node/Edge/Combo type, assign `name` which is in the same level of `attrs` for `addShape` when custom items. **The value of `name` must be unique in a Node/Edge/Combo type.** + +The following demo binds click event listener for all the shapes named `circle-shape`: + +```javascript +graph.on('circle-shape:click', (ev) => { + const shape = ev.target; // clicked shape + // ... do sth +}); +``` + +5. Timing Events + +Timing Events are those happens before and after rendering, viewport changing, item adding/modifying/removing, data changing and so on. All the timing events are listed in [The Timing Events in G6](/en/docs/api/Event#timing-events). E.g. `beforeadditem`,`afteradditem` and so on: + - Before and node/edge/combo state changing: `beforerefreshitem` 与 `afterrefreshitem`; + - Before and after layout: `beforelayout` and `afterlayout`. + +The following demo binds a listener to handle layout finished. Notice that the listeners for events like `'afterlayout'` or `'afterrender'` should be binded before `graph.render()` or `graph.read()` to catch the events happen at first rendering. + +```javascript +graph.on('afterrender', (ev) => { + // ... do sth +}); +``` + +6. Custom Events + +G6 allows user to emit and handle any custom events, whichi is emiited by `graph.emit(customEventName: string, event: IG6GraphEvent)` anywhere. The first parameter is the custom event name, which can be any string. Before calling `emit`, bind listener for it `graph.on(customEventName: string, callback: Function)`. For example: + + +```javascript +graph.on('some-custom-event-name', (ev) => { + // ... do sth +}); +graph.emit('some-custom-event-name', { + // some params +}) +``` \ No newline at end of file diff --git a/packages/site/docs/manual/middle/states/bindEvent.zh.md b/packages/site/docs/manual/middle/states/bindEvent.zh.md new file mode 100644 index 0000000000..fbef00bfe3 --- /dev/null +++ b/packages/site/docs/manual/middle/states/bindEvent.zh.md @@ -0,0 +1,103 @@ +--- +title: 监听和绑定事件 +order: 0 +--- + +除了 [内置交互行为  Behavior](/zh/docs/manual/middle/states/defaultBehavior) 和 [交互模式 Mode](/zh/docs/manual/middle/states/mode) 搭配的事件管理方式外,G6 提供了直接的单个事件、时机的监听方法,可以监听画布、节点、边、以及各函数被调用的时机等。如果要了解 G6 支持的所有事件,请参考 [Event API](/zh/docs/api/Event)。**G6 上所有的事件都需要在 graph 上监听**。这些事件可以分为以下六个层次: + +1. 全局事件 + +只要在画布上范围内发生均会被触发,如 `mousedown`,`mouseup`,`click`,`mouseenter`,`mouseleave` 等。 + +```javascript +graph.on('click', (ev) => { + const shape = ev.target; + const item = ev.item; + if (item) { + const type = item.getType(); + } +}); +``` + +2. canvas 事件 + +只在 canvas 空白处被触发,如 `canvas:mousedown`,`canvas:click` 等,以`canvas:eventName` 为事件名称。 + +```javascript +graph.on('canvas:click', (ev) => { + const shape = ev.target; + const item = ev.item; + if (item) { + const type = item.getType(); + } +}); +``` + +3. 节点/边/combo 上的事件 + +例如 `node:mousedown`,`edge:click`, `combo:click` 等,以 `type:eventName` 为事件名称。 + +```javascript +graph.on('node:click', (ev) => { + const node = ev.item; // 被点击的节点元素 + const shape = ev.target; // 被点击的图形,可根据该信息作出不同响应,以达到局部响应效果 + // ... do sth +}); + +graph.on('edge:click', (ev) => { + const edge = ev.item; // 被点击的边元素 + const shape = ev.target; // 被点击的图形,可根据该信息作出不同响应,以达到局部响应效果 + // ... do sth +}); + +graph.on('combo:click', (ev) => { + const combo = ev.item; // 被点击 combo 元素 + const shape = ev.target; // 被点击的图形,可根据该信息作出不同响应,以达到局部响应效果 + // ... do sth +}); +``` + +4. 图形上的事件 + +指定图形上的事件,如 `circle-shape:mousedown`,`circle-shape:click` 等,以 `shapeName:eventName` 为事件名称。可用于绑定节点/边/combo 中对局部图形做出响应的场景。效果类似上文 `graph.on('node:click', fn)` 中通过 `target` 信息作出不同响应。 + +关于图形的 name: + - 内置节点/边/combo 上每个图形的名称在开发过程中可以通过 `graph.on('node:click', (ev) => console.log(ev.target.get('name')))` 得知; + - 自定义节点/边/combo 中通过 addShape 增加的图形,可添加与 attrs 平级的 name 字段指定任意(**注意:同元素类型中需要是唯一的**)字符串作为 name。 + +下面例子为图中所有 name 为 circle-shape 的图形绑定了 click 事件监听: + +```javascript +graph.on('circle-shape:click', (ev) => { + const shape = ev.target; // 被点击的图形 + // ... do sth +}); +``` + +5. 时机事件 + +时机事件指渲染、视口变换、元素增删改、数据变换等时机。所有时机事件详见 [G6 的时机事件列表](/zh/docs/api/Event#回调参数)。如:`beforeadditem`,`afteradditem`  等: + - 节点/边/Combo 状态改变时的事件:`beforerefreshitem` 与 `afterrefreshitem`; + - 布局时机:`beforelayout` 与 `afterlayout`。 + +下面例子为 graph 绑定了渲染完成时机的监听。时机事件中,afterrender、afterlayout 一类事件必须在 `graph.render()` 或 `graph.read()` 之前绑定,方可监听到首次渲染、布局完成后的相关事件。 + +```javascript +graph.on('afterrender', (ev) => { + // ... do sth +}); +``` + +6. 自定义事件 + +G6 允许用户自定义任意事件,可在任意位置通过 `graph.emit(customEventName: string, event: IG6GraphEvent)` 触发一个事件,第一个参数为自定义事件名称。在触发前,通过 `graph.on(customEventName: string, callback: Function)` 进行监听。例如: + + +```javascript +graph.on('some-custom-event-name', (ev) => { + // ... do sth +}); +graph.emit('some-custom-event-name', { + // some params +}) +``` \ No newline at end of file diff --git a/packages/site/docs/manual/middle/states/custom-behavior.en.md b/packages/site/docs/manual/middle/states/custom-behavior.en.md new file mode 100644 index 0000000000..3d7d644ffb --- /dev/null +++ b/packages/site/docs/manual/middle/states/custom-behavior.en.md @@ -0,0 +1,105 @@ +--- +title: Custom Behavior +order: 2 +--- + +G6 provides abundant [Built-in Behavior](/en/docs/manual/middle/states/defaultBehavior). Besides, you can custom your type of behaviors to satisfy the special requirements. + +In G6, we mainly take three scenarios into consideration: + +- Demonstrating the relational data; +- Modeling the visualization; +- Analyzing the graph. + +It is necessary to incorporate the interactions when the information is too complex to be understand in one glance: + +- Zooming a large graph; +- Utilizing the ttoltip to show the detail information of a node; +- Adding/removing/modifying/querying a graph item. + +Due to the complex and the diversity of the interactions in different scenarios and bussiness, we did not build all the interactions into G6: + +- Some systems require to add nodes by clicking a tool bar, some require toe add by dragging from a panel; +- Some scenarios add edges by dragging from an anchor point, some add by clicking the end nodes; +- Some edges are allowed to link to any node, some only can be linked to specific anchor points; +- Some users require to custom the process of activating and endding. +- ... + +We found the interactions are sundry and versatile. And the conflicts and configurations will make the users and developers collapse. Thus, G6 designs a set of simple and flexible implemention of interaction behavior. + +## The Life Cycle of Behavior + +To customize a Behavior, it is important to comprehend the life cycle of Behavior. Interaction Behaviors are related to the events from users, including the processes: + +- Bind the event; +- Activate the event; +- Keep the event; +- End the event; +- Remove the event. + +## registerBehavior + +You can customize a Behavior by `G6.registerBehavior`. The following code implements a custom Behavior named `'activate-node'`, which changes the state `active` of the clicked node to be `true`, and restores the state `active` to be `false` when the user clicking the node again or clicking the canvas. + +   ⚠️**Attension:** + +- The following code set the states for different behaviors, but does not assign the state styles for manipulated nodes. To change the styles when the states changed, refer to [State Styles](/en/docs/manual/middle/states/state). +- The configurations of customizing Behavior are introduced in [Behavior API](/en/docs/api/Behavior); +- `getEvent` returns the events which are listened by the Behavior. The events in G6 are introduced in [Event API](/en/docs/api/Event). + +```javascript +G6.registerBehavior('activate-node', { + getDefaultCfg() { + return { + multiple: true + }; + }, + getEvents() { + return { + 'node:click': 'onNodeClick', + 'canvas:click': 'onCanvasClick' + }; + } + onNodeClick(e) { + const graph = this.graph; + const item = e.item; + if (item.hasState('active')) { + graph.setItemState(item, 'active', false); + return; + } + // Get the configurations by this. If you do not allow multiple nodes to be 'active', cancel the 'active' state for other nodes + if (!this.multiple) { + this.removeNodesState(); + } + // Set the 'active' state of the clicked node to be true + graph.setItemState(item, 'active', true); + }, + onCanvasClick(e) { + // shouldUpdate can be overrode by users. Returning true means turning the 'active' to be false for all the nodes + if (this.shouldUpdate(e, self)) { + removeNodesState(); + } + }, + removeNodesState() { + graph.findAllByState('node', 'active').forEach(node => { + graph.setItemState(node, 'active', false); + }); + } +}); +``` + +## Using Behavior + +Now, you have a type of Behavior named `'activate-node'`. To use it, configure it into a mode of `modes` when instantiating a Graph. [Mode](/en/docs/manual/middle/states/mode). The following code configure the `'activate-node'` into the default mode, which means the `'activate-node'` Behavior will take effect in the default mode. + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + modes: { + // Configure the custom Behavior here to use it + default: ['activate-node'], + }, +}); +``` diff --git a/packages/site/docs/manual/middle/states/custom-behavior.zh.md b/packages/site/docs/manual/middle/states/custom-behavior.zh.md new file mode 100644 index 0000000000..13ae853f49 --- /dev/null +++ b/packages/site/docs/manual/middle/states/custom-behavior.zh.md @@ -0,0 +1,105 @@ +--- +title: 自定义交互 Behavior +order: 2 +--- + +G6 除了提供丰富的 [内置交互行为 Behavior](/zh/docs/manual/middle/states/defaultBehavior) 外,还提供了自定义交互行为的机制,方便用户开发更加定制化的交互行为。 + +在交互行为上, G6 主要考虑了三个场景: + +- 展示关系数据; +- 可视化建模; +- 图分析。 + +在这些场景中只要用户可能无法一眼看清楚所有需要的信息,都需要进行交互,例如: + +- 图太大,需要缩放; +- 单个节点上展示的信息太少,需要通过 tooltip 显示详情; +- 对节点进行增删改查。 + +我们无法将所有常用的交互全部内置到 G6 中。由于场景不一样,业务不一样,同样的目的需要的交互都不一样: + +- 有些系统需要从工具栏上点击后添加节点,有些系统需要从面板栏上拖出出新的节点; +- 有的业务添加边需要从锚点上拖拽出来,而有些直接点击节点后就可以拖拽出边; +- 有些边可以连接到所有节点上,而有些边不能连接到具体某个节点的某个锚点上; +- 所有的交互的触发、持续、结束都要允许能够进行个性化的判定; +- …… + +我们可以看到在图上的交互是繁杂多变的。各种冲突、各种配置项会让用户和开发者疲于应对。出于这些考虑,G6 提供了一套非常简单而灵活的机制来实现交互。 + +## Behavior 的生命周期 + +为实现交互,首先需要了解交互的生命周期。交互起源于用户在系统上的所有事件,是否允许交互发生同事件密切相关。所以我们看到交互的生命周期,即操作事件的过程如下: + +- 绑定事件; +- 触发事件; +- 持续事件; +- 结束事件; +- 移除事件。 + +## 自定义交互 registerBehavior + +通过 `G6.registerBehavior` 自定义 Behavior。下面代码实现了名为  `'activate-node'` 的交互行为,在终端用户点击节点时,置该节点的 `active` 状态为 `true`;再次点击或点击画布时,置该节点的 `active` 状态为 `false`。 + +   ⚠️ 注意: + +- 下面代码仅设置了不同交互后节点的状态,没有指定这些状态下节点的样式。若需要根据节点状态变化它的样式,参见 [配置不同 State 下的节点样式](/zh/docs/manual/middle/states/state)。 +- 自定义 Behavior 时,可选的方法请参数  [Behavior API](/zh/docs/api/Behavior); +- `getEvent` 返回该 Behavior 所需监听事件的对象,G6 中支持的所有事件,请参考  [Event API](/zh/docs/api/Event)。 + +```javascript +G6.registerBehavior('activate-node', { + getDefaultCfg() { + return { + multiple: true + }; + }, + getEvents() { + return { + 'node:click': 'onNodeClick', + 'canvas:click': 'onCanvasClick' + }; + } + onNodeClick(e) { + const graph = this.graph; + const item = e.item; + if (item.hasState('active')) { + graph.setItemState(item, 'active', false); + return; + } + // this 上即可取到配置,如果不允许多个 'active',先取消其他节点的 'active' 状态 + if (!this.multiple) { + this.removeNodesState(); + } + // 置点击的节点状态 'active' 为 true + graph.setItemState(item, 'active', true); + }, + onCanvasClick(e) { + // shouldUpdate 可以由用户复写,返回 true 时取消所有节点的 'active' 状态,即将 'active' 状态置为 false + if (this.shouldUpdate(e, self)) { + removeNodesState(); + } + }, + removeNodesState() { + graph.findAllByState('node', 'active').forEach(node => { + graph.setItemState(node, 'active', false); + }); + } +}); +``` + +## 使用自定义的 Behavior + +有了上面代码定义的名为 `'activate-node'` 的 Behavior 以后,在实例化 Graph 时,在 `modes` 中将其配置到默认或其他[行为模式](/zh/docs/manual/middle/states/mode)中。下面代码将其配置到了默认行为模式中,在默认模式下,该行为将会生效。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + modes: { + // 定义的 Behavior 指定到这里,就可以支持 Behavior 中定义的交互 + default: ['activate-node'], + }, +}); +``` diff --git a/packages/site/docs/manual/middle/states/defaultBehavior.en.md b/packages/site/docs/manual/middle/states/defaultBehavior.en.md new file mode 100644 index 0000000000..1e661801d0 --- /dev/null +++ b/packages/site/docs/manual/middle/states/defaultBehavior.en.md @@ -0,0 +1,801 @@ +--- +title: Built-in Behavior +order: 1 +--- + +## What is Behavior + +Behavior is the interaction mechanism in G6. It is used with [Interaction Mode](/en/docs/manual/middle/states/mode). This document introduces the Built-in behaviors in G6. Besides, you can register a type of [Custom Behavior](/zh/docs/manual/middle/states/custom-behavior). The document [Interaction Mode](/en/docs/manual/middle/states/mode) introduces how to configure the Behaviors onto the graph. + +## Built-in Behavior + +All the basic graphics Shapes, Items(nodes/edges) can be interacted by events. To achieve it with versatility, there are 14 built-in Behaviors in G6. + +### drag-combo + +Supported by V3.5 or later versions. + +- Description: Allows users to drag combo; +- Configurations: + - `type: 'drag-combo'`; + - `enableDelegate`: Whether activate `delegate` when dragging combos, which means whether to use a virtual rect moved with the dragging mouse instead of the combo. The effect is shown in the figures below. `false` by default; + - `delegateStyle`: The style of the `delegate` when dragging the combo with delegate; + - `onlyChangeComboSize`: Supported by V3.5 or later vertions. Only Change the size of the prarent combo whose child combo to be dragged, which means do not change the hierarchy structures of combos and nodes. `false` by default; + - `activeState`: The state's name(string) of the entered combo to be dragged over, coordinating with the configuration in `comboStateStyles` to define the state styles when instantiating a graph. It is empty by default; + - `selectedState`: The state's name(string) when combo is selected, `'selected'` by default; + - `shouldUpdate(e, self)`: Whether allow the behavior happens on the current item (e.item), see the example below for detail. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`; + - `shouldEnd(e, newParent, self)`: **Supported after v4.3.8.** Whether allow the behavior ends with current item (e.item) and the new parent combo. the second parameter is the detected new parent when drop. If it is dropped on the canvas, `newParent` is `undefined`, see the example below for detail. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldEnd`. + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['drag-combo'], + }, +}); +``` + +Configure the styles for enableDelegate or activeState: + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'drag-combo', + enableDelegate: true, + activeState: 'actived', + shouldUpdate: (e, self) => { + // Do not allow the combo with id 'combo1' be dragged + if (e.item && e.item.getModel().id === 'combo1') return false; + return true; + }, + // shouldEnd【supported by v4.3.8 and later versions】 + shouldEnd: (e, newParent, self) => { + // The combos are not allow to be drop on the combo with id combo1 + if (newParent && newParent.getModel().id === 'combo1') return false; + return true; + } + }, + ], + }, + comboStateStyles: { + actived: { + stroke: 'red', + lineWidth: 3, + }, + }, +}); +``` + +### collapse-expand-combo + +Supported by V3.5 or later. + +- Description: collapse or expand Combo. If the graph has layout configuration, this behavior will trigger re-layout. If you do not want re-layout the graph after collapsing or expanding a combo, use combo's click listener and [graph.collapseExpandCombo API](/en/docs/api/Graph#collapseexpandcombocombo) instead; +- Configurations: + - `type: 'collapse-expand-combo'`; + - `trigger`: Specify the trigger for collapsing and expanding a combo. `dblclick` by default. Options: `'click'`, `'dblclick'`; + - `relayout`: Whether relayout the graph after collapsing or expanding, `true` by default. + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['collapse-expand-combo'], + }, +}); +``` + +Configure the trigger to be 'click': + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'collapse-expand-combo', + trigger: 'click', + relayout: false, // do not relayout after collapsing or expanding + }, + ], + }, +}); +``` + +### drag-canvas + +- Description: Allows users drag canvas; +- Configurations: + - `type: 'drag-canvas'`; + - `direction`: The direction of dragging that is allowed. Options: `'x'`, `'y'`, `'both'`. `'both'` by default; + - `enableOptimize`: whether enable optimization, `false` by default. `enableOptimize: true` means hiding all edges and the shapes beside keyShapes of nodes while dragging canvas; + - `shouldBegin(e, self)`: Whether allow the behavior happen on the current item (e.item). **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldBegin`; + - `scalableRange`: scalable range when drag canvas, `zero` by default. -1 to 1 means the scalable percentage of the viewport; the image bellow illustrate the situation when it is smaller than -1 or bigger than 1: + + + + - `allowDragOnItem`: whether response when the users drag on items(node/edge/combo), `false` by default; +- Related timing events: + - `canvas:dragstart`: Triggered when drag start. Listened by `graph.on('canvas:dragstart', e => {...})`; + - `canvas:drag`: Triggered when dragging. Listened by `graph.on('canvas:drag', e => {...})`; + - `canvas:dragend`: Triggered when drag end. Listened by `graph.on('canvas:dragend', e => {...})`. + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['drag-canvas'], + }, +}); +``` + +By default, the x and y directions are both allowed. + +**Using Customized Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'drag-canvas', + direction: 'x', + }, + ], + }, +}); +``` + +The canvas can be dragged along x direction only.
img + + +### scroll-canvas + +- Description: Scroll the canvas by wheeling, *supported after v4.2.6*; +- Configurations: + - `type: 'scroll-canvas'`; + - `direction`: The direction of dragging that is allowed. Options: `'x'`, `'y'`, `'both'`. `'both'` by default; + - `enableOptimize`: whether enable optimization, `false` by default. `enableOptimize: true` means hiding all edges and the shapes beside keyShapes of nodes while dragging canvas; + - `zoomKey`: switch to zooming while pressing the key and wheeling. Options: `'shift'`, `'ctrl'`, `'alt'`, `'control'`, `'meta'`, using an array of these options allows any of these keys to trigger zooming; + - `scalableRange`: scalable range when drag canvas, `zero` by default. -1 to 1 means the scalable percentage of the viewport; the image bellow illustrate the situation when it is smaller than -1 or bigger than 1: + - `allowDragOnItem`: whether response when the users drag on items(node/edge/combo), `true` by default; + + + +- Related timing events: + - `wheel`: Triggered when wheeling. Listened by `graph.on('wheel', e => {...})`. + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['drag-canvas'], + }, +}); +``` + +By default, the x and y directions are both allowed. + +**Using Customized Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'drag-canvas', + direction: 'x', + }, + ], + }, +}); +``` + +The canvas can be dragged along x direction only.
img + +### zoom-canvas + +- Description: Zoom the canvas; +- Configurations: + - `type: 'zoom-canvas'`; + - `sensitivity`: The sensitivity of the zooming, range from 1 to 10. `5` by default; + - `minZoom`: minimum zoom ratio; + - `maxZoom`: maximum zoom ratio; + - `enableOptimize`: whether enable the optimization, false by default. If it is assigned to true, the shapes except keyShape will be hide when the ratio is smaller thant `optimizeZoom`; + - `optimizeZoom`: Takes effect when `enableOptimize` is `true`. `0.7` by default. See `enableOptimize` upon. + - `shouldUpdate(e, self)`: Whether allow the behavior happen. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`. + - `fixSelectedItems`: Fix the line width, fontSize, or overall size of some items while zooming out the canvas. `fixSelectedItems` is an object with: + - `fixSelectedItems.fixState`: The state of the items to be fixed. Set the item state to be the value to make it fix while zooming out. `'selected'` by default; + - `fixSelectedItems.fixAll`: Fix the overall size of the selected items. `fixAll` has higher priority than `fixSelectedItems.fixLineWidth` and `fixSelectedItems.fixLabel`; + - `fixSelectedItems.fixLineWidth`: Fix the lineWidth of the keyShape of the fix item; + - `fixSelectedItems.fixLabel`: Fix the fontSize of the item. +- Related timing events: + - `wheelzoom(e)`: Triggered when user zoom the graph. Listened by `graph.on('wheelzoom', e => {...})`. + +**Tips: Assign values for `minZoom` and  `maxZoom` on the graph to limit the zooming ratio.** + + +### drag-node + +- Description: Allows users drag nodes; +- Configurations: + - `type: 'drag-node'`; + - `delegateStyle`: The drawing properties when the nodes are dragged. `{ strokeOpacity: 0.6, fillOpacity: 0.6 }` by default; + - `updateEdge`: Whether to update all connected edges when dragging nodes. `true` by default. + - `enableDelegate`: Whether activate `delegate` when dragging nodes, which means whether to use a virtual rect moved with the dragging mouse instead of the node. The effect is shown in the figures below. `false` by default; + - `enableDebounce`: Whether enable updating with debounce while dragging to avoid the frequent calculation. It is a boolean and will be useful for graph with polyline edges. `false` by default; + - `enableOptimize`: Whether to hide the related edges to avoid calculation while dragging nodes. It is a boolean and will be useful for graph with polyline edges. `false` by default; + - `onlyChangeComboSize`:Supported by V3.5 or later vertions. Only Change the size of the prarent combo whose child node to be dragged, which means do not change the hierarchy structures of combos and nodes. `false` by default; + - `comboActiveState`: Supported by V3.5 or later vertions. The state's name(string) of the entered combo to be dragged over, coordinating with the configuration in `comboStateStyles` to define the state styles when instantiating a graph. It is empty by default; + - `selectedState`: Supported by V3.5 or later vertions. The state's name(string) when combo is selected, `'selected'` by default; + - `enableStack`: Whether push the operations into undo/redo stack, assign it to false to avoid pushing; + - `shouldBegin(e, self)`: Whether allow the behavior happen. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldBegin`; + - `shouldUpdate(e, self)`: Whether allow update the node/ delegate's position while dragging. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`; + - `shouldEnd(e, targetItem: Item, self)`: Whether allow update the node/ delegate's position after drag end. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldEnd`. + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['drag-node'], + }, +}); +``` + +img + +**Activate** `delegate` + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'drag-node', + enableDelegate: true, + shouldBegin: (e, self) => { + // Do not allow the node with id 'node1' to be dragged + if (e.item && e.item.getModel().id === 'node1') return false; + return true; + }, + }, + ], + }, +}); +``` + +img + +### click-select + +- Description: Select a node by clicking. Cancel the selected state by clicking the node agian or clicking the canvas; +- Configurations: + - `type: 'click-select'`; + - `multiple`: Whether to allow multiple selection. `true` by default. `false` means multiple selection is not allowed, and the `trigger` will not take effect. + - `trigger`: Specify which trigger for multiple selection. `shift` by default, which means multiple selection is activated when the shift button is pressed. Options: `'shift'`, `'ctrl'`, `'alt'`, and so on; + - `selectedState`: The state name for an item which is selected by this behaivor, `'selected'` by default; + - `selectNode`: Whether allow selecting node by this behavior, `true` by default; + - `selectEdge`: Whether allow selecting edge by this behavior, `false` by default; + - `selectCombo`: Whether allow selecting combo by this behaivor, `true` by default; + - `shouldBegin(e, self)`: Whether allow the behavior happen on the current item (e.item), see the example below. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldBegin`; + - `shouldUpdate(e, self)`: Whether allow the behavior changes the state and state style of the on the current item (e.item), see the example below. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`; +- Related timing events: + - `'nodeselectchange'`: Triggered when the set of selected items changed. Listened by `graph.on('nodeselectchange', e => {...})`. The fields of the parameter `e` are: + - `e.target`: The current manipulated item; + - `e.selectedItems`: The set of selected items after this operation; + - `e.select`: A boolean tag to distinguish if the current operation is select(`true`) or deselect (`false`). + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['click-select'], + }, +}); + +// Triggered when the set of selected items changed +graph.on('nodeselectchange', (e) => { + // The current manipulated item + console.log(e.target); + // The set of selected items after this operation + console.log(e.selectedItems); + // A boolean tag to distinguish if the current operation is select(`true`) or deselect (`false`) + console.log(e.select); +}); +``` + +Press **`Shift`** button to select more items.
img + +**Using Customized Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'click-select', + trigger: 'ctrl', + }, + // Whether allow the behavior happen on the item. If it returns false, the current manipulated item will not be selected and the timing event 'nodeselectchange' will not be triggered as well + shouldBegin: (e, self) => { + // Do not allow the behavior happen when the clicked shape has name 'text-shape' + if (e.target.get('name') === 'text-shape') return false; + // Do not allow the behavior happen when the clicked item has id 'text-shape' + if (e.item.getModel().id === 'id1') return false; + return true; + }, + // Whehter allow the behavior change the state or state styles of the current manipulated item. If it returns false, the state and state styles of the current item will not be changed. But the timing event 'nodeselectchange' will still be triggered + shouldUpdate: (e, self) => { + // The item's state and state style will not be changed if its id is 'id2' + if (e.item.getModel().id === 'id2') return false; + return true; + } + ], + }, +}); + +// Triggered when the set of selected items changed +graph.on('nodeselectchange', e => { + // The current manipulated item + console.log(e.target); + // The set of selected items after this operation + console.log(e.selectedItems); + // A boolean tag to distinguish if the current operation is select(`true`) or deselect (`false`) + console.log(e.select); +}); +``` + +With the configuration above, users are allowed to select more than one nodes when pressing **Ctrl**. You can also assign **Alt** for it. But the multiple selection is turned off when `multiple` is `false`, and the `trigger` will not take effect any more. + +### tooltip + +- Description: The tooltip for node; +- Configurations: + - `type: 'tooltip'`; + - `formatText(model)`: Format function, returns a text string or an HTML element; + - `offset`: the offset of the tooltip to the mouse. + - `shouldBegin(e, self)`: Whether allow the tooltip the show up. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldBegin`; + - `shouldUpdate(e, self)`: Whether allow the tooltip to be updated. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`. +- Related timing events: + - `tooltipchange`: Triggered when the tooltip is changed. Listened by `graph.on('tooltipchange', e => {...})`. + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + modes: { + default: [ + { + type: 'tooltip', + formatText(model) { + return model.xxx; + }, + offset: 10, + }, + ], + }, +}); +``` + +**Tips: Since there are no styles for tooltip in G6, you need to define the styles for it as:** + +```css +.g6-tooltip { + padding: 10px 6px; + color: #444; + background-color: rgba(255, 255, 255, 0.9); + border: 1px solid #e2e2e2; + border-radius: 4px; +} +``` + +### edge-tooltip + +The usage of edge-tooltip is similar to tooltip. It will be activated when the user hover the mouse onto an edge. + +- Description: The tooltip for edge; +- Configurations: + - `type: 'edge-tooltip'`; + - `formatText(model)`: Format function, returns a text string or an HTML element; + - `offset`: the offset of the tooltip to the mouse; + - `shouldBegin(e, self)`: Whether allow the tooltip the show up. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldBegin`; + - `shouldUpdate(e, self)`: Whether allow the tooltip to be updated. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`. +- Related timing events: + - `tooltipchange`: Triggered when the tooltip is changed. Listened by `graph.on('tooltipchange', e => {...})`. + +### activate-relations + +- Description: Highlight the node and its related nodes and edges when the mouse enter the node; +- Configurations: + - `type: 'activate-relations'`; + - `trigger: 'mouseenter'`. `mousenter` means acitvating when the mouse enter a node; `click` means activating when the mouse click a node; + - `activeState: 'active'`. The state name when the node is activated. When `activate-relations` is activated, the related nodes and edges will have this state. `active` by default. It can be combined with `nodeStyle` and `edgeStyle` of graph to enrich the visual effect; + - `inactiveState: 'inactive'`. The state name when of the node is inactivated. All the nodes and edges which are not activated by `activate-relations` will have this state. `inactive` by default. It can be combined with `nodeStyle` and `edgeStyle` of graph to enrich the visual effect; + - `resetSelected`: Whether to reset the selected nodes when highlight the related nodes. `false` by default, which means the selected state will not be covered by `activate-relations`; + - `shouldUpdate(item: Item, { event: G6Event, action: 'deactivate' | 'activate' }, self)`: Whether allow the behavior happen. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`; +- Related timing event: + - `'afteractivaterelations'`: Triggered when the activated items changed. Listened by `graph.on('afteractivaterelations', evt => {...})`. The fields of the parameter `e`: + - `e.item`: The current manipulated item; + - `e.action`: A string tag to distinguish whether the current action is `'activate'` or `'deactivate'`. + +
**Using Default Configuration**
+ +```javascript +const graph = new G6.Graph({ + modes: { + default: ['activate-relations'], + }, +}); + +graph.on('afteractivaterelations', (e) => { + // The current manipulated item + console.log(e.item); + // A string tag to distinguish whether the current action is `'activate'` or `'deactivate'` + console.log(e.action); +}); +``` + +The selected state of the selected node will be maintained after the `activate-relations` operation by default.
img + +**Using Customized Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'activate-relations', + resetSelected: true, + }, + ], + }, +}); + +graph.on('afteractivaterelations', (e) => { + // The current manipulated item + console.log(e.item); + // A string tag to distinguish whether the current action is `'activate'` or `'deactivate'` + console.log(e.action); +}); +``` + +Assign `true` to `resetSelected` to reset the selected states for nodes after the `activate-relations` operation. + +img + +### brush-select + +- Description: Allows uers to select nodes by brushing; +- Configurations: + - `type: 'brush-select'`; + - `brushStyle`: The styles of the marquee. It contains four configurations: `fill`、`fillOpacity`、`stroke` and `lineWidth`; + - `onSelect(nodes)`: The callback function when selecting a node. `nodes` is the selected ndoes; + - `onDeselect(nodes)`: The callback function when canceling selections. `nodes` is the selected ndoes; + - `selectedState`: The state of the selected nodes. `'selected'` by default; + - `includeEdges`: Whether to select the edges when selecting by brushing. `true` by default. `false` means do not select the edges. + - `includeCombos`: Whether to select the combos when selecting by brushing. `false` by default. `true` means combos are selected. + - `trigger`: The trigger button for this operation. `'shift'` by default, which means the select by brushing operation will be activated by pressing Shift button. Options: `'shift'`, `'ctrl' / 'control'`, `'alt'` and `'drag'`, not case sensitive: + - `'shift'`: Select by brushing when Shift is pressed; + - `'ctrl' / 'control'`: Select by brushing when Ctrl is pressed; + - `'alt'`: Select by brushing when Alt is pressed; + - `'drag'`: Select by brushing without any pressed buttons. Note that it will conflict with the `drag-canvas`; + - `shouldUpdate(item: Item, action: string, self)`: Whether allow the behavior happen on the current manipulated item (e.item). See the example below. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`; +- Related timing events: + - `'nodeselectchange'`: Triggered when the set of selected items changed. Listened by `graph.on('nodeselectchange', e => {...})`. The fields of the parameter `e`: + - `e.selectedItems`: The set of selected items after the operation; + - `e.select`: A boolean tag to distinguish whether the current operation is select(`true`) or deselect(`false`). + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['brush-select'], + }, +}); + +// Triggered when the set of selected items changed +graph.on('nodeselectchange', (e) => { + // The set of selected items after the operation + console.log(e.selectedItems); + // A boolean tag to distinguish whether the current operation is select(`true`) or deselect(`false`) + console.log(e.select); +}); +``` + +Select by brushing when the Shift button is pressed by default. And the edges are selectable as well.
img + +**Using Customized Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'brush-select', + trigger: 'ctrl', + includeEdges: false, // Will ignore edges + includeCombos: true, // Will select combos + }, + // Whether allow the behavior happen on the current manipulated item (e.item). If it returns false, the item will not be selected and the timing event 'nodeselectchange' will not be triggered + shouldUpdate: (e, self) => { + // Do not allow the behavior happen on the node/edge/combo with id 'id2' + if (e.item.getModel().id === 'id2') return false; + return true; + } + ], + }, +}); + +// Triggered when the set of selected items changed +graph.on('nodeselectchange', e => { + // The set of selected items after the operation + console.log(e.selectedItems); + // A boolean tag to distinguish whether the current operation is select(`true`) or deselect(`false`) + console.log(e.select); +}); +``` + +By the configurations above, the operation is activated when the Ctrl button is pressed, and the edges will not be selected during the process.
img + +**Conflict Configuration: ** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + 'drag-canvas', + { + type: 'brush-select', + trigger: 'drag', + }, + ], + }, +}); +``` + +When the `trigger` in `brush-select` is assigned to `drag`, an the `drag-canvas` exists in this mode, their operation will conflict.
img + +It is obvious that the selecting by brushing is activated while dragging the canvas. To avoid this situation, we can assign other values for `trigger` in `brush-select`. Besides, the following solution also works: + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['drag-canvas'], + brush: [ + { + type: 'brush-select', + trigger: 'drag', + }, + ], + }, +}); +``` + +It is a solution to put these two conflicting events into two mdoes. They will be activated on different graph modes. Dragging operation corresponds to `drag-canvas` in the default mode. When user switch the state to brush mode by `graph.setModel('brush')`, the dragging operation will be responsed by `brush-select` instead. Refer to [Mode](/en/docs/manual/middle/states/mode) for more information. + +### lasso-select + +- Description: Allows uers to select nodes by drawing a line over or around objects. +- Configurations: + - `type: 'lasso-select'`; + - `delegateStyle`: The styles of the marquee. It contains four configurations: `fill`、`fillOpacity`、`stroke` and `lineWidth`; + - `onSelect(nodes, edges)`: The callback function when selecting a node. `nodes` is the selected ndoes, `edges` is the selected edges; + - `onDeselect(nodes, edges)`: The callback function when canceling selections. `nodes` is the deselected ndoes, `edges` is the deselected edges; + - `selectedState`: The state of the selected nodes. `'selected'` by default; + - `includeEdges`: Whether to select the edges when selecting by brushing. `true` by default. `false` means do not select the edges. + - `trigger`: The trigger button for this operation. `'shift'` by default, which means the select by brushing operation will be activated by pressing Shift button. Options: `'shift'`, `'ctrl' / 'control'`, `'alt'` and `'drag'`, not case sensitive: + - `'shift'`: Select by brushing when Shift is pressed; + - `'ctrl' / 'control'`: Select by brushing when Ctrl is pressed; + - `'alt'`: Select by brushing when Alt is pressed; + - `'drag'`: Select by brushing without any pressed buttons. Note that it will conflict with the `drag-canvas`; + - `shouldUpdate(item: Item, action: string, self)`: Whether allow the behavior happen on the current manipulated item (e.item). See the example below. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`; + - `shouldDeselect({ action: string, nodes: INode[], edges: IEdge[] })`: Whether allow to deselect the items at current conditions. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldDeselect`. +- Related timing events: + - `'nodeselectchange'`: Triggered when the set of selected items changed. Listened by `graph.on('nodeselectchange', e => {...})`. The fields of the parameter `e`: + - `e.selectedItems`: The set of selected items after the operation, include `nodes` and `edges`; + - `e.select`: A boolean tag to distinguish whether the current operation is select(`true`) or deselect(`false`). + +The configuration of `lasso-select` behavior is the same as that of `brush-select` behavior. + +### collapse-expand + +- Description: Collapse or expand a subtree on a treeGraph; +- Attension: If you want to collapse a subtree by default when render the Graph in the first time, you can assign `collapsed: true` to the root of the subtree in its data. If you want to control the subtree to collapse/expand by code, you can also assign the `collapsed` for the root of the subtree and call `treeGraph.layout()` to make it take effect; +- Configurations: + - `type: 'collapse-expand'`; + - `trigger`: The operation for collapsing and expanding. Options: `click` and `dblclick`. `click` by default; + - `onChange(item: Item, collapsed: boolean, self)`: The callback function after collapsing or expanding. **Warining**: it will be removed from V3.1.2; + - `shouldBegin(e, collapsed: boolean, self)`: Whether allow this behavior happen on the current item (e.item). **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldBegin`; + - `shouldUpdate(e, collapsed: boolean, self)`: whether allow call `onChange` and relayout the graph after update the `collpased` to the node. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldUpdate`; +- Related timing event: + - `itemcollapsed`: Triggered when collapse-expand happens. Listened by `graph.on('itemcollapsed', e => {...})`. The fields of the parameter `e`: + - `e.item`: The current manipulated item; + - `e.collapsed`: A boolean flag to distinguish whether the current operaition is collapsing(`true`) or expanding(`false`). + +**Usage** + +```javascript +const graph = new G6.TreeGraph({ + modes: { + default: [ + { + type: 'collapse-expand', + trigger: 'click', + onChange(item, collapsed) { + const data = item.get('model').data; + data.collapsed = collapsed; + return true; + }, + shouldBegin: (e, self) => { + // Nothing happens when the current item has id 'node1' + if (e.item && e.item.getModel().id === 'node1') return false; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, +}); + +graph.on('itemcollapsed', (e) => { + // The current manipulated item + console.log(e.item); + // A boolean flag to distinguish whether the current operaition is collapsing(`true`) or expanding(`false`) + console.log(e.collapsed); +}); +``` + +### collapse-expand-group + +- Description: Collapse or expand a node group; +- Configurations: + - `type: 'collapse-expand-group'` + - `trigger`: The operation for collapsing and expanding. Options: `click` and `dblclick`. `dblclick` by default, which means double click. + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['collapse-expand-group'], + }, +}); +``` + +**Using Customized Configuration**
Assign `trigger` to **`click`**, the collapsing or expanding a node group will be triggered by click. + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'collapse-expand-group', + trigger: 'click', + }, + ], + }, +}); +``` + +img + +### drag-group + +- Description: Allows users drag node group; +- Configurations: + - `type: 'drag-group'`; + - `delegateStyle`: The style of the `delegate` when dragging the group. + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['drag-group'], + }, +}); +``` + +### create-edge + +- Description: create edge by interaction; +- Configurations: + - `type: 'create-edge'`; + - `trigger`: Specify the trigger for creating an edge, options: `'click'`, `'drag'`. The default value is `'click'`, which means the user is allowed to creat an edge by clicking two end nodes as source and target node respectively. `'drag'` means the user is allowed to create an edge by 'dragging' from a source node to a target ndoe. Note that `trigger: 'drag'` cannot create a self-loop edge; + - `key`: The assistant trigger key from the keyboard. If it is undefined or unset, only `trigger` decides the triggering interaction from user. Otherwise, this behavior will be triggered by `trigger` only when `key` is pressed. Options: `'shift'`, `'ctrl'`, 'control', `'alt'`, `'meta'`, `undefined`; + - `edgeConfig`: The edge configurations for the edges created by this behavior, the configurations are the same as the edge, ref to [Edge Configurations](/en/docs/manual/middle/elements/edges/defaultEdge). To modify the configurations for different added edges, listener to `'aftercreateedge'` and update the edge. + - `shouldBegin(e, self)`: Whether allow the behavior begins with the condition `e`. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldBegin`; + - `shouldEnd(e, self)`: Whether allow the behavior ends under the condition `e`. **Supported after v4.7.16** The last parameter is the behavior instance, which will be convenient to get it in arrow function formatted `shouldEnd`; +- Related timing event: + - `'aftercreateedge'`: Triggered after the creating process is finished. Listen to it by `graph.on('aftercreateedge', e => {...})`, where the parameter `e` has a property `edge` which is the created edge. + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['create-edge'], + }, +}); +graph.on('aftercreateedge', (e) => { + console.log(e.edge); +}); +``` + +**Using Customized Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + // takes effect when the 'shift' is been pressing and drag from a node to another + { + type: 'create-edge', + trigger: 'drag', + key: 'shift', + edgeConfig: { + type: 'cubic', + style: { + stroke: '#f00', + lineWidth: 2, + // ... // other edge style configurations + }, + // ... // other edge configurations + }, + }, + ], + }, +}); +``` + +### shortcuts-call + +- Description: allow the end-user to call a function of Graph with shortcuts keys. e.g. press down 'control' and '1' on keyboard to make the graph fit the canvas. Attention: make sure the focus is on the canvas when the end-user is pressing keys to call the function; +- Configurations: + - `type: 'shortcuts-call'`; + - `trigger`: the subject key to trigger the behavior, options: `'shift'`, `'alt'`, `'ctrl'`, `'control'`; + - `combinedKey`: the vice key for combination with `trigger` to trigger the behavior. When the `trigger` is pressed down, press the `combinedKey` will call the graph function with name `functionName`. If `combinedKey` is not specified or assigned with `undefined`, pressing the `trigger` down will call the function; + - `functionName`: the name of the Graph function to be called. If the name is wrong or it is not a function of the Graph, the keydown events will not be triggered. + - `functionParams`: the parameters or the called function. Make sure the parameters are correct for the function to be called according to corresponding docs, errors might occur otherwise. + +**Using Default Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['shortcuts-call'], + }, +}); +``` + +**Using Customized Configuration** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + // Press 'alt' and 'm' to call graph.moveTo(10, 10). Be sure that the fucos is on the canvas when press the keys + { + type: 'shortcuts-call', + // subject key + trigger: 'alt', + // vice key + combinedKey: 'm', + // move the graph to 10,10 + functionName: 'moveTo', + functionParams: [10, 10], + }, + ], + }, +}); +``` diff --git a/packages/site/docs/manual/middle/states/defaultBehavior.zh.md b/packages/site/docs/manual/middle/states/defaultBehavior.zh.md new file mode 100644 index 0000000000..78c3dbc5da --- /dev/null +++ b/packages/site/docs/manual/middle/states/defaultBehavior.zh.md @@ -0,0 +1,751 @@ +--- +title: 内置的 Behavior +order: 1 +--- + +## 什么是 Behavior + +Behavior 是 G6 提供的定义图上交互事件的机制。它与[交互模式 Mode](/zh/docs/manual/middle/states/mode) 搭配使用,如何将下文所述各种 Behavior 配置到图上,见 [交互模式](/zh/docs/manual/middle/states/mode)。 + +## 内置 Behavior + +理论上, G6 上的所有基础图形、Item(节点/边)都能通过事件来进行操作。考虑到通用性,G6 目前共提供了以下 14 个内置的 Behavior。此外,用户可以注册 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)。 + +### drag-combo + +V3.5 以上版本支持。 + +- 含义:拖动 Combo; +- 配置项: + - `type: 'drag-combo'`; + - `enableDelegate`:拖动 Combo 时候是否开启图形代理 delegate,即拖动 Combo 时候 Combo 不会实时跟随变动,拖动过程中有临时生成一个 delegate 图形,拖动结束后才更新 Combo 位置,默认为 false,不开启; + - `delegateStyle`:delegate 的样式; + - `onlyChangeComboSize`:拖动嵌套的 Combo 时,只改变父 Combo 的大小,不改变层级关系,默认为 false; + - `activeState`:当拖动 Combo 时,父 Combo 或进入到的 Combo 的状态值,需要用户在实例化 Graph 时在 `comboStateStyles` 里面配置,默认为空; + - `selectedState`:选中 Combo 的状态,默认为 selected,需要在 `comboStateStyles` 里面配置; + - `shouldUpdate(e, self)`:是否允许当前被操作的 combo 被拖拽,参见下面示例。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldEnd` 中访问该实例; + - `shouldEnd(e, newParent, self)`:【v4.3.8 后支持】是否允许当前被操作的 combo 完成拖拽。第二个参数为拖拽释放时检测到的新父 combo,若释放在画布上,则 `newParent` 为 `undefined`,参见下面示例。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldEnd` 中访问该实例。 + +**使用默认配置** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['drag-combo'], + }, +}); +``` + +用户可根据实际需求,为 `activeState` 或 `selectedState` 配置样式: + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'drag-combo', + enableDelegate: true, + activeState: 'actived', + shouldUpdate: (e, self) => { + // 不允许 id 为 'combo1' 的 combo 被拖拽 + if (e.item && e.item.getModel().id === 'combo1') return false; + return true; + }, + // shouldEnd【v4.3.8 后支持】 + shouldEnd: (e, newParent, self) => { + // 不可以将 combo 释放到 combo1 上 + if (newParent && newParent.getModel().id === 'combo1') return false; + return true; + } + }, + ], + }, + comboStateStyles: { + actived: { + stroke: 'red', + lineWidth: 3, + }, + }, +}); +``` + +### collapse-expand-combo + +V3.5 以上版本支持。 + +- 含义:收起和展开 Combo。若图配置有布局,则该 behavior 被触发后会触发图的重新布局。若希望避免重新布局,可以通过监听 combo 点击事件和 [graph.collapseExpandCombo API](/zh/docs/api/Graph#collapseexpandcombocombo) 控制收缩展开逻辑; +- 配置项: + - `type: 'collapse-expand-combo'`; + - `trigger`:触发方式,默认为双击收起或展示,可配置 `'click'` 和 `'dblclick'`; + - `relayout`:收缩或展开后是否触发重新布局,默认为 `true`。 + +**使用默认配置** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['collapse-expand-combo'], + }, +}); +``` + +用户可以配置成单击展示或收起: + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'collapse-expand-combo', + trigger: 'click', + relayout: false, // 收缩展开后,不重新布局 + }, + ], + }, +}); +``` + +### drag-canvas + +- 含义:拖拽画布; +- 配置项: + + - `type: 'drag-canvas'`; + - `direction`:允许拖拽方向,支持`'x'`,`'y'`,`'both'`,默认方向为 `'both'`; + - `enableOptimize`:是否开启优化,开启后拖动画布过程中隐藏所有的边及节点上非 keyShape 部分,默认关闭; + - `shouldBegin(e, self)`:是否允许触发该操作。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldBegin` 中访问该实例; + - `allowDragOnItem`:是否允许用户在节点/边/ combo 上拖拽时响应,默认为 false; + - `scalableRange`:拖动 canvas 可扩展的范围,默认为 0,值为 -1 ~ 1 代表可超出视口的范围的比例值(相对于视口大小)。值小于 -1 或大于 1 时,为正和负数时的效果如下图所示。 + + + +- 相关时机事件: + - `canvas:dragstart`:画布拖拽开始时触发,使用 `graph.on('canvas:dragstart', e => {...})` 监听; + - `canvas:drag`:画布拖拽中触发,使用 `graph.on('canvas:drag', e => {...})` 监听; + - `canvas:dragend`:画布拖拽结束后触发,使用 `graph.on('canvas:dragend', e => {...})` 监听。 + +**使用默认配置** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['drag-canvas'], + }, +}); +``` + +默认配置下,可以在 x 和 y 两个方向上拖动画布。 + +**使用自定义参数** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'drag-canvas', + direction: 'x', + }, + ], + }, +}); +``` + +此时只能在 x 方向上面拖动,y 方向上不允许拖动。
img + + +### scroll-canvas + +- 含义:滚轮滚动画布,*v4.2.6 起支持*; +- 配置项: + + - `type: 'scroll-canvas'`; + - `direction`:允许拖拽方向,支持`'x'`,`'y'`,`'both'`,默认方向为 `'both'`; + - `enableOptimize`:是否开启优化,开启后拖动画布过程中隐藏所有的边及节点上非 keyShape 部分,默认关闭; + - `zoomKey`:切换为滚动缩放的键盘按钮,按住该键并滚动滚轮,则切换为滚轮缩放画布,可选项为:`'shift'`,`'ctrl'`,`'alt'`,`'control'`,`'meta'`, 可使用数组监听多个按键,任意按键按下时都会触发缩放; + - `scalableRange`:拖动 canvas 可扩展的范围,默认为 0,值为 -1 ~ 1 代表可超出视口的范围的比例值(相对于视口大小)。值小于 -1 或大于 1 时,为正和负数时的效果如下图所示。 + - `allowDragOnItem`:是否允许用户在节点/边/ combo 上拖拽时响应,默认为 false; + + + +- 相关时机事件: + - `onWheel`:滚轮滚动时触发,使用 `graph.on('wheel', e => {...})` 监听。 + +**使用默认配置** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['scroll-canvas'], + }, +}); +``` + +默认配置下,可以在 x 和 y 两个方向上滚动画布。 + +**使用自定义参数** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'scroll-canvas', + direction: 'x', + }, + ], + }, +}); +``` + +此时只能在 x 方向上面滚动,y 方向上不允许滚动。
img + + +### zoom-canvas + +- 含义:缩放画布; +- `type: 'zoom-canvas'`; +- 配置项: + - `sensitivity`:缩放灵敏度,支持 1-10 的数值,默认灵敏度为 5; + - `minZoom`:最小缩放比例; + - `maxZoom`:最大缩放比例; + - `enableOptimize`:是否开启性能优化,默认为 false,设置为 true 开启,开启后缩放比例小于 optimizeZoom 时自动隐藏非 keyShape; + - `optimizeZoom`:当 enableOptimize 为 true 时起作用,默认值为 0.7,表示当缩放到哪个比例时开始隐藏非 keyShape; + - `shouldUpdate(e, self)`:是否允许发生缩放。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldBegin` 中访问该实例; + - `fixSelectedItems`:在缩小画布时是否固定选定元素的描边粗细、文本大小、整体大小等,`fixSelectedItems` 是一个对象,有以下变量: + - `fixSelectedItems.fixState`:将被固定的元素状态,被设置为该状态的节点将会在画布缩小时参与固定大小的计算,默认为 `'selected'`; + - `fixSelectedItems.fixAll`:固定元素的整体大小,优先级高于 `fixSelectedItems.fixLineWidth` 和 `fixSelectedItems.fixLabel`; + - `fixSelectedItems.fixLineWidth`:固定元素的 keyShape 的描边粗细; + - `fixSelectedItems.fixLabel`:固定元素的文本大小。 +- 相关时机事件: + - `wheelzoom(e)`:当缩放发生变化时被触发。使用 `graph.on('wheelzoom', e => {...})` 监听该时机事件。 + +**提示:若要限定缩放尺寸,请在 graph 上设置  `minZoom`  和  `maxZoom`。** + +### drag-node + +**说明:** V3.5 以上版本才支持拖动 Combo 中的节点。 + +- 含义:拖拽节点,或拖动 Combo 中的节点; +- 配置项: + - `type: 'drag-node'`; + - `delegateStyle`:节点拖拽时的绘图属性,默认为 `{ strokeOpacity: 0.6, fillOpacity: 0.6 }`; + - `updateEdge`:是否在拖拽节点时更新所有与之相连的边,默认为 `true` 。 + - `enableDelegate`:拖动节点过程中是否启用 `delegate`,即在拖动过程中是否使用方框代替元素的直接移动,效果区别见下面两个动图。默认值为  `false`; + - `onlyChangeComboSize`:V3.5 及以上版本支持,拖动节点过程中只改变 Combo 大小,不改变 Combo 结构,即不将节点从 Combo 中拖出或将节点拖入到 Combo 中,默认为 false; + - `enableDebounce`:是否在拖动节点时使用 debounce 来避免频繁的更新计算。这个 boolean 类型的配置项可以为折线边的更新提高性能,减少折线边的频繁重新寻径。默认值为 `false`; + - `enableOptimize`:是否在拖拽节点时隐藏相关边,拖拽结束后再显示。这个 boolean 类型的配置项可以完全避免拖拽过程中相关折线边的重新寻径,默认值为 `false`; + - `comboActiveState`:V3.5 及以上版本支持,拖动节点过程中,如果存在 Combo,节点所在 Combo 或节点进入的 Combo 的状态,需要在实例化 Graph 时候通过 `comboStateStyles` 进行配置,默认为空; + - `selectedState`:V3.5 及以上版本支持,选中 Combo 的样式,需要在实例化 Graph 时候通过 `comboStateStyles` 进行配置,默认为 selected; + - `enableStack`:该 behaivor 产生的拖拽节点是否入栈,设置为 false 则不入栈; + - `shouldBegin(e, self)`:是否允许当前被操作的节点被拖动。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldBegin` 中访问该实例; + - `shouldUpdate(e, self)`:是否允许当前被操作的节点在拖动过程中更新自身/ delegate 位置。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldUpdate` 中访问该实例; + - `shouldEnd(e, targetItem: Item, self)`:是否允许当前被操作的节点在拖拽结束时更新位置。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldEnd` 中访问该实例。 + +**使用默认配置** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['drag-node'], + }, +}); +``` + +img + +**启用** `delegate` + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'drag-node', + enableDelegate: true, + shouldBegin: (e, self) => { + // 不允许拖拽 id 为 'node1' 的节点 + if (e.item && e.item.getModel().id === 'node1') return false; + }, + }, + ], + }, +}); +``` + +img + +### click-select + +- 含义:点击选中节点,再次点击节点或点击 Canvas 取消选中状态; +- 配置项: + - `type: 'click-select'`; + - `multiple`:是否允许多选,默认为 `true`,当设置为 `false`,表示不允许多选,此时 `trigger` 参数无效; + - `trigger`:指定按住哪个键进行多选,默认为 shift,按住 Shift 键多选,用户可配置 shift、ctrl、alt; + - `selectedState`:被点击选择后设置元素的状态名,默认为 `'selected'`; + - `selectNode`:是否允许节点被该交互选中,默认为 `true`; + - `selectEdge`:是否允许边被该交互选中,默认为 `false`; + - `selectCombo`:是否允许 Combo 被该交互选中,默认为 `true`; + - `shouldBegin(e, self)`:是否允许该 behavior 发生,参考下面示例。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldBegin` 中访问该实例; + - `shouldUpdate(e, self)`:是否允许对该 behavior 发生状态响应,参考下面示例。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldUpdate` 中访问该实例。 +- 相关时机事件: + - `'nodeselectchange'`:当选中的元素集合发生变化时将会触发该时机事件。使用 `graph.on('nodeselectchange', e => {...})` 监听。其参数 `e` 有以下字段: + - `e.target`:当前操作的 item; + - `e.selectedItems`:当前操作后,所有被选中的 items 集合; + - `e.select`:当前操作是选中(true)还是取消选中(false)。 + +**使用默认配置** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['click-select'], + }, +}); + +// 当 click-select 选中的元素集合发生变化时将会触发下面时机事件,e 中包含相关信息 +graph.on('nodeselectchange', (e) => { + // 当前操作的 item + console.log(e.target); + // 当前操作后,所有被选中的 items 集合 + console.log(e.selectedItems); + // 当前操作时选中(true)还是取消选中(false) + console.log(e.select); +}); +``` + +按住 **`Shift`** 键可多选。
+ +**使用自定义参数** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'click-select', + trigger: 'ctrl', + // 是否允许该 behavior 发生。若返回 false,被操作的 item 不会被选中,也不会触发 'nodeselectchange' 时机事件 + shouldBegin: (e, self) => { + // 当点击的图形名为 'text-shape' 时,不允许该 behavior 发生 + if (e.target.get('name') === 'text-shape') return false; + // 当点击的节点/边/ combo 的 id 为 'id1' 时,不允许该 behavior 发生 + if (e.item.getModel().id === 'id1') return false; + return true; + }, + // 是否允许对该 behavior 发生状态响应。若返回 false,被操作的对象的状态及相关状态样式不会被更新,但是仍然会触发 'nodeselectchange' 时机事件 + shouldUpdate: (e, self) => { + // 当点击的节点/边/ combo 的 id 为 'id2' 时,该 item 不会发生状态的改变 + if (e.item.getModel().id === 'id2') return false; + return true; + }, + }, + ], + }, +}); + +// 当 click-select 选中的元素集合发生变化时将会触发下面时机事件,evt 中包含相关信息 +graph.on('nodeselectchange', (e) => { + // 当前操作的 item + console.log(e.target); + // 当前操作后,所有被选中的 items 集合 + console.log(e.selectedItems); + // 当前操作时选中(true)还是取消选中(false) + console.log(e.select); +}); +``` + +以上配置中,用户可按住 **Ctrl** 键进行多选,也可以配置 **Alt** 键。当配置了  `multiple` 参数为 `false`,则表示不允许多选,此时 `trigger` 参数无效。 + +### tooltip + +- 含义:节点文本提示; +- 配置项: + - `type: 'tooltip'`; + - `formatText(model)`:格式化函数,可以返回文本或者 HTML; + - `offset`:tooltip 距离鼠标的偏移量; + - `shouldBegin(e, self)`:是否允许 toolip 出现。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldBegin` 中访问该实例; + - `shouldUpdate(e, self)`:是否允许 toolip 内容更新。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldUpdate` 中访问该实例。 +- 相关时机事件: + - `tooltipchange`:当 tooltip 发生变化时被触发。使用 `graph.on('tooltipchange', e => {...})` 监听。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + modes: { + default: [ + { + type: 'tooltip', + formatText(model) { + return model.xxx; + }, + offset: 10, + }, + ], + }, +}); +``` + +**提示:由于 G6 没有内置任何 tooltip 的样式,用户需要自己定义样式,例如:** + +```css +.g6-tooltip { + padding: 10px 6px; + color: #444; + background-color: rgba(255, 255, 255, 0.9); + border: 1px solid #e2e2e2; + border-radius: 4px; +} +``` + +### edge-tooltip + +使用方式基本与 tooltip 相同,但是移到边时触发。主要是为了将两个交互区分开,以满足用户边与节点的提示样式或 HTML 结构不同,以及不需要在事件中去区分是节点事件还是边事件等。 + +- 含义:边文本提示; +- 配置项: + - `type: 'edge-tooltip'`; + - `formatText(model)`:格式化函数,可以返回文本或者 HTML; + - `offset`:tooltip 距离鼠标的偏移量; + - `shouldBegin(e, self)`:是否允许 toolip 出现。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldBegin` 中访问该实例; + - `shouldUpdate(e, self)`:是否允许 toolip 内容更新。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldUpdate` 中访问该实例。 +- 相关时机事件: + - `tooltipchange`:当 tooltip 发生变化时被触发。使用 `graph.on('tooltipchange', e => {...})` 监听。 + +### activate-relations + +- 含义:当鼠标移到某节点时,突出显示该节点以及与其直接关联的节点和连线; +- `type: 'activate-relations'`; +- 参数: + - `trigger: 'mouseenter'`。可以是  `mousenter`,表示鼠标移入时触发;也可以是  `click`,鼠标点击时触发; + - `activeState: 'active'`。活跃节点状态。当行为被触发,需要被突出显示的节点和边都会附带此状态,默认值为  `active`;可以与 graph 实例的  `nodeStyle`  和  `edgeStyle`  结合实现丰富的视觉效果。 + - `inactiveState: 'inactive'`。非活跃节点状态。不需要被突出显示的节点和边都会附带此状态。默认值为  `inactive`。可以与 graph 实例的  `nodeStyle`  和  `edgeStyle`  结合实现丰富的视觉效果; + - `resetSelected`:高亮相连节点时是否重置已经选中的节点,默认为 `false`,即选中的节点状态不会被 `activate-relations` 覆盖; + - `shouldUpdate(item: Item, { event: G6Event, action: 'deactivate' | 'activate' }, self)`:是否允许该 behavior 发生。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldUpdate` 中访问该实例。 +- 相关时机事件: + - `'afteractivaterelations'`:当高亮发生改变时触发该时机事件。使用 `graph.on('afteractivaterelations', evt => {...})` 监听。其参数 `e` 有以下字段: + - `e.item`:当前操作的节点 item; + - `e.action`:当前操作是选中(`'activate'`)还是取消选中(`'deactivate'`)。 + +
**使用默认配置**
+ +```javascript +const graph = new G6.Graph({ + modes: { + default: ['activate-relations'], + }, +}); + +graph.on('afteractivaterelations', (e) => { + // 当前操作的节点 item + console.log(e.item); + // 当前操作是选中(`'activate'`)还是取消选中(`'deactivate'`) + console.log(e.action); +}); +``` + +默认情况下,选中的节点状态,在操作完以后仍然会保持选中状态。
img + +**使用自定义参数** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'activate-relations', + resetSelected: true, + }, + ], + }, +}); + +graph.on('afteractivaterelations', (e) => { + // 当前操作的节点 item + console.log(e.item); + // 当前操作是选中(`'activate'`)还是取消选中(`'deactivate'`) + console.log(e.action); +}); +``` + +配置 `resetSelected` 参数为 `true` 后,交互后会重置节点的选择状态。 + +img + +### brush-select + +- 含义:拖动框选节点; +- 配置项: + - `type: 'brush-select'`; + - `brushStyle`:拖动框选框的样式,包括 `fill`、`fillOpacity`、`stroke` 和 `lineWidth` 四个属性; + - `onSelect(nodes)`:选中节点时的回调,参数 `nodes` 表示选中的节点; + - `onDeselect(nodes)`:取消选中节点时的回调,参数 `nodes` 表示取消选中的节点; + - `selectedState`:选中的状态,默认值为 `'selected'`; + - `includeEdges`:框选过程中是否选中边,默认为 `true`,用户配置为 `false` 时,则不选中边; + - `trigger`:触发框选的动作,默认为 `'shift'`,即用户按住 Shift 键拖动就可以进行框选操作,可配置的的选项为: `'shift'`、`'ctrl' / 'control'`、`'alt'` 和 `'drag'` ,不区分大小写: + - `'shift'`:按住 Shift 键进行拖动框选; + - `'ctrl' / 'control'`:按住 Ctrl 键进行拖动框选; + - `'alt'`:按住 Alt 键进行拖动框选; + - 风险  `'drag'`:不需要按任何键,进行拖动框选,如果同时配置了 `drag-canvas`,则会与该选项冲突。 + - `shouldUpdate(item: Item, action: string, self)`:是否允许对该 behavior 发生,参考下面示例。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldUpdate` 中访问该实例。 +- 相关时机事件: + - `'nodeselectchange'`:当选中的元素集合发生变化时将会触发该时机事件。使用 `graph.on('nodeselectchange', e => {...})` 监听。其参数 `e` 有以下字段: + - `e.selectedItems`:当前操作后,所有被选中的 items 集合; + - `e.select`:当前操作是选中(true)还是取消选中(false)。 + +**使用默认配置** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['brush-select'], + }, +}); + +// 当 click-select 选中的元素集合发生变化时将会触发下面时机事件,e 中包含相关信息 +graph.on('nodeselectchange', (e) => { + // 当前操作后,所有被选中的 items 集合 + console.log(e.selectedItems); + // 当前操作时选中(true)还是取消选中(false) + console.log(e.select); +}); +``` + +默认情况下,按住 Shift 键进行框选,选中节点的同时,也会选中边。
img + +**使用自定义参数** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + { + type: 'brush-select', + trigger: 'ctrl', + includeEdges: false, + // 是否允许对该 behavior 发生。若返回 false,被操作的 item 不会被选中,不触发 'nodeselectchange' 时机事件 + shouldUpdate: (e, self) => { + // 当点击的节点/边/ combo 的 id 为 'id2' 时,该 item 不会被选中 + if (e.item.getModel().id === 'id2') return false; + return true; + }, + }, + ], + }, +}); + +// 当 click-select 选中的元素集合发生变化时将会触发下面时机事件,e 中包含相关信息 +graph.on('nodeselectchange', (e) => { + // 当前操作后,所有被选中的 items 集合 + console.log(e.selectedItems); + // 当前操作时选中(true)还是取消选中(false) + console.log(e.select); +}); +``` + +上面的配置,按住 Ctrl 键,进行框选,框选过程中不会选中边。
img + +**冲突的配置:** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + 'drag-canvas', + { + type: 'brush-select', + trigger: 'drag', + }, + ], + }, +}); +``` + +当用户配置 `brush-select` 的 `trigger` 为 `drag`,同时又配置了 `drag-canvas` 时,在交互上面会出现冲突的情况。
img + +可以看到,在拖动过程中也出现了框选的情况,这种情况很显然不是我们期望的效果,除过使用 `brush-select` 的 `trigger` 参数避免这种冲突外,我们还可以通过下面的方式来实现: + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['drag-canvas'], + brush: [ + { + type: 'brush-select', + trigger: 'drag', + }, + ], + }, +}); +``` + +上面这种方式是使用不同的 mode 来区分,mode 可以达到使用相同交互动作而产生不同的效果。默认模式中,使用的是拖拽操作由 `drag-canvas` 响应。当用户通过通过 `graph.setMode('brush')` 切换到 brush 模式后,此时同样的拖拽操作由 `brush-select` 响应。更多关于 mode 的内容请参考 [Mode](/zh/docs/manual/middle/states/mode) 教程。 + +### lasso-select + +- 含义:自由圈选; +- 配置项: + - `type: 'lasso-select'`; + - `delegateStyle`:拖动框选框的样式,包括 `fill`、`fillOpacity`、`stroke` 和 `lineWidth` 四个属性; + - `onSelect(nodes, edges)`:选中节点时的回调,参数 `nodes` 表示选中的节点,`edges` 表示选中的边; + - `onDeselect(nodes, edges)`:取消选中节点时的回调,参数 `nodes` 表示取消选中的节点,`edges` 表示取消选中的边; + - `selectedState`:选中的状态,默认值为 `'selected'`; + - `includeEdges`:框选过程中是否选中边,默认为 `true`,用户配置为 `false` 时,则不选中边; + - `trigger`:触发框选的动作,默认为 `'shift'`,即用户按住 Shift 键拖动就可以进行框选操作,可配置的的选项为: `'shift'`、`'ctrl' / 'control'`、`'alt'` 和 `'drag'` ,不区分大小写: + - `'shift'`:按住 Shift 键进行拖动框选; + - `'ctrl' / 'control'`:按住 Ctrl 键进行拖动框选; + - `'alt'`:按住 Alt 键进行拖动框选; + - 风险  `'drag'`:不需要按任何键,进行拖动框选,如果同时配置了 `drag-canvas`,则会与该选项冲突。 + - `shouldUpdate(item: Item, action: string, self)`:是否允许对该 behavior 发生,参考下面示例。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldUpdate` 中访问该实例; + - `shouldDeselect({ action: string, nodes: INode[], edges: IEdge[] })`:当前条件下是否允许取消选中。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldDeselect` 中访问该实例。 +- 相关时机事件: + - `'nodeselectchange'`:当选中的元素集合发生变化时将会触发该时机事件。使用 `graph.on('nodeselectchange', e => {...})` 监听。其参数 `e` 有以下字段: + - `e.selectedItems`:当前操作后,所有被选中的 items 集合; + - `e.select`:当前操作是选中(true)还是取消选中(false)。 + +`lasso-select` 的配置和使用方法和 `brush-select` 一致。 + +### collapse-expand + +- 含义:只适用于树图,展开或收起子树; +- 注意:若希望在首次布局时有默认收起的子树,则可以在数据中设置子树根节点的属性 `collapsed` 为 `true`。若希望使用代码控制子树的展开/收起,同样可以在数据中设置子树根节点的 `collapsed` 属性,并调用 `treeGraph.layout()` 使之生效; +- 配置项: + - `type: 'collapse-expand'`; + - `trigger`:收起和展开树图的方式,支持 `'click'` 和 `'dblclick'` 两种方式。默认为 `'click'`,即单击; + - `onChange(item: Item, collapsed: boolean, self)`:收起或展开的回调函数。警告:V3.1.2 版本中将移除; + - `shouldBegin(e, collapsed: boolean, self)`:是否允许该 behavior 在当前操作的 item 上发生。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldBegin` 中访问该实例; + - `shouldUpdate(e, collapsed: boolean, self)`:更新节点 `collapsed` 字段后,是否允许调用 `onChange` 以及重新布局。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldUpdate` 中访问该实例; +- 相关时机事件: + - `itemcollapsed`:当 collapse-expand 发生时被触发。使用 `graph.on('itemcollapsed', e => {...})` 监听,参数 `e` 有以下字段: + - `e.item`:当前被操作的节点 item; + - `e.collapsed`:当前操作是收起(`true`)还是展开(`false`)。 + +**用法** + +```javascript +const graph = new G6.TreeGraph({ + modes: { + default: [ + { + type: 'collapse-expand', + trigger: 'click', + onChange: (item, collapsed) => { + const data = item.get('model').data; + data.collapsed = collapsed; + return true; + }, + shouldBegin: (e, self) => { + // 若当前操作的节点 id 为 'node1',则不发生 collapse-expand + if (e.item && e.item.getModel().id === 'node1') return false; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, +}); + +graph.on('itemcollapsed', (e) => { + // 当前被操作的节点 item + console.log(e.item); + // 当前操作是收起(`true`)还是展开(`false`) + console.log(e.collapsed); +}); +``` + +### create-edge + +- 含义:通过交互创建边; +- 配置项: + - `type:'create-edge'`; + - `trigger`:该交互的触发条件,可选 `'click'`,`'drag'`。默认为 `'click'`,即分别点击两个节点为这两个节点创建边。`'drag'` 代表从一个节点“拖拽”出一条边,在另一个节点上松开鼠标完成创建。注意,`trigger: 'drag'` 不能创建一个自环边; + - `key`:键盘按键作为该交互的辅助触发,若不设置或设置为 undefined 则代表只根据 `trigger` 决定该交互的触发条件。可选值:`'shift'`,`'ctrl'`, 'control',`'alt'`,`'meta'`,`undefined`; + - `edgeConfig`: 有该交互创建出的边的配置项,可以配置边的类型、样式等,其类型参考[边的配置](/zh/docs/manual/middle/elements/edges/defaultEdge)。如果需要为不同的被添加边赋予不同样式,请监听 `'aftercreateedge'` 并更新相对应的边; + - `shouldBegin(e, self)`:是否允许当前被操作的条件下开始创建边。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldBegin` 中访问该实例; + - `shouldEnd(e, self)`:是否允许当前被操作的条件下结束创建边。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldEnd` 中访问该实例; +- 相关时机事件: + - `'aftercreateedge'`:当边创建完成时将会触发该时机事件。使用 `graph.on('aftercreateedge', e => {...})` 监听。其参数 `e` 中的 `edge` 字段即为刚刚创建的边。 + +**使用默认配置** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['create-edge'], + }, +}); +graph.on('aftercreateedge', (e) => { + console.log(e.edge); +}); +``` + +**使用自定义参数** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + // 只有当 'shift' 键被按下,才能够通过从一个节点拖拽到另一个节点来创建一条边 + { + type: 'create-edge', + trigger: 'drag', + key: 'shift', + edgeConfig: { + type: 'cubic', + style: { + stroke: '#f00', + lineWidth: 2, + // ... // 其它边样式配置 + }, + // ... // 其它边配置 + }, + }, + ], + }, +}); +``` + +### shortcuts-call + +- 含义:允许终端用户使用键盘组合键调用 Graph 的函数,例如按下键盘上的 control 与 1,对图进行适应画布。注意:终端用户使用该功能时焦点必须在画布上才能够正确触发; +- 配置项: + - `type:'shortcuts-call'`; + - `trigger`:触发该交互的主键,可选 `'shift'`,`'alt'`,`'ctrl'`,`'control'`。默认为 `'control'`。设置时需要注意可能与其他正在别使用的 Behavior 的 trigger 产生冲突; + - `combinedKey`:触发该交互的副按键,在按住主键 `trigger` 后,按下该副按键,将会调用 `functionName` 指定的函数。若不设置或设置为 undefined 则代表只根据 `trigger` 触发; + - `functionName`: 被调用的 Graph 的一个函数名,若传入一个错误的函数名,将会不产生任何效果; + - `functionParams`: 被调用的函数的参数,请根据各函数的文档传入正确的参数,错误的参数可能导致报错。 + +**使用默认配置** + +```javascript +const graph = new G6.Graph({ + modes: { + default: ['shortcuts-call'], + }, +}); +``` + +**使用自定义参数** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + // 当按住 'alt' 键,并按下 'm' 键,将调用 graph.moveTo(10, 10) + { + type: 'shortcuts-call', + // 主健 + trigger: 'alt', + // 副键 + combinedKey: 'm', + // 将图内容的左上角移动到 10,10 + functionName: 'moveTo', + functionParams: [10, 10], + }, + ], + }, +}); +``` diff --git a/packages/site/docs/manual/middle/states/mode.en.md b/packages/site/docs/manual/middle/states/mode.en.md new file mode 100644 index 0000000000..9386583acc --- /dev/null +++ b/packages/site/docs/manual/middle/states/mode.en.md @@ -0,0 +1,96 @@ +--- +title: Interaction Mode +order: 3 +--- + +## What is Mode + +When a user interacts with a graph, there may be different interaction modes due to different intents. For example, clicking a node in edit mode requires a pop-up window for the user to edit, and clicking a node in view mode requires selecting a node. + +To address the problem above, G6 provides the interaction Mode. It is a manage mechanism for the [Behavior](/en/docs/manual/middle/states/defaultBehavior) on a graph. There can be multiple interaction modes on a graph, each interaction mode contains multiple interaction [Behavior](/en/docs/manual/middle/states/defaultBehavior)s. + +For example, there are two modes on a graph: default and edit: + +- default mode contains click to select node behavior and drag canvas behavior; +- edit mode contains click node to pop up an editing window behavior and drag node behavior; + +Default mode takes effect by default, which means the node will be selected by clicking insteand of a editing window pops up. You can switch to edit mode by simple code, then the behaviors in the defualt mode will not take effect any more, which means the editing window will pop up when user clicks a node. + +## Configure Mode + +Configure the `modes` when instantiating a Graph: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + modes: { + // 支持的 behavior + default: ['drag-canvas', 'zoom-canvas'], + edit: ['click-select'], + }, +}); +``` + +There are two modes on the graph defined above: `default` and `edit`. The `default` mode contains two [Behavior](/en/docs/manual/middle/states/defaultBehavior)s: `'drag-canvas'` and ` '``zoom-canvas' ` with default configurations. + +## Switch Mode + +The `default` mode takes effect by default. Users are allowed to drag and zoom the canvas. Swich the mode to edit mode by ` graph.``setMode('edit') ` to select a node by clicking. + +```javascript +graph.setMode('edit'); +``` + +Now, the graph supports clicking to select nodes. The `'drag-canvas'` and `'zoom-canvas'` behaviors in `default` do not take effect any more. + +`setMode` calls the following operations inside: + +- Unbind all the event listeners of current mode; +- Generate new Behaviors. Initialize the events; +- Bind event listeners to the new Behaviors. + +## Edit Mode + +If there are existing Behaviors ([Built-in Behavior](/en/docs/manual/middle/states/defaultBehavior) or [Custom Behavior](/en/docs/manual/middle/states/custom-behavior)), You can add them to a mode by `graph.addBehaviors`, and also remove some Behaviors by `graph.removeBehaviors`: + +```javascript +// Add drag-canvas with configurations from default mode +graph.addBehaviors('drag-canvas', 'default'); + +// Remove drag-canvas from default mode +graph.removeBehaviors('drag-canvas', 'default'); + +// Add drag-canvas with configurations into edit mode +graph.addBehaviors( + { + type: 'drag-canvas', + direction: 'x', + }, + 'edit', +); + +// Remove drag-canvas from edit mode +graph.removeBehaviors('drag-canvas', 'edit'); + +// Add multiple behaviors into default mode +graph.addBehaviors(['drag-canvas', 'zoom-canvas'], 'default'); + +// Remove multiple behaviors from default mode +graph.removeBehaviors(['drag-canvas', 'zoom-canvas'], 'default'); + +// -------- + +// Update the behavior 'zoom-canvas' from mode 'default' +graph.updateBehavior('zoom-canvas', { sensitivity: 1.5, enableOptimize: true}, 'default'); + +// update the behavior 'click-select' from mode 'select' +graph.updateBehavior('click-select', { trigger: 'ctrl' }, 'select'); + +``` + +## Related Reading + +- [Built-in Behavior](/en/docs/manual/middle/states/defaultBehavior) +- [Custom Behavior](/en/docs/manual/middle/states/custom-behavior) diff --git a/packages/site/docs/manual/middle/states/mode.zh.md b/packages/site/docs/manual/middle/states/mode.zh.md new file mode 100644 index 0000000000..4ea5ff40c3 --- /dev/null +++ b/packages/site/docs/manual/middle/states/mode.zh.md @@ -0,0 +1,96 @@ +--- +title: 交互模式 Mode +order: 3 +--- + +## 什么是 Mode + +用户在交互一张图时,可能由于意图不同而存在不同的交互模式,例如在编辑模式下点击节点需要弹出窗口让用户编辑,在查看模式下点击节点需要选中节点。 + +为了解决上述问题,G6 提供了交互模式 Mode,它是图上交互行为 [Behavior](/zh/docs/manual/middle/states/defaultBehavior) 的管理机制。一个图上可以有存在多种交互模式,每个交互模式包含多种交互行为 [Behavior](/zh/docs/manual/middle/states/defaultBehavior)。 + +例如,存在 default 和 edit 两种 mode(交互模式): + +- default 模式中包含点击选中节点行为和拖拽画布行为; +- edit 模式中包含点击节点弹出编辑框行为和拖拽节点行为。 + +默认情况下,该图对 default 模式中的行为见效,即点击节点时节点被选中而不是弹出编辑框。用户可以通过简单的命令切换该图的行为模式到 edit 模式,则 default 模式中的行为失效,edit 交互模式中的行为起效,即点击节点将弹出编辑框。 + +## 配置 Mode + +在实例化图时配置 `modes` 属性: + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 500, + height: 500, + modes: { + // 支持的 behavior + default: ['drag-canvas', 'zoom-canvas'], + edit: ['click-select'], + }, +}); +``` + +以上是模式定义的一个例子。在图上定义了两个模式,分别是 `default`,`edit`。其中 `default` 包含两个  [Behavior](/zh/docs/manual/middle/states/defaultBehavior):`'drag-canvas'` 和 '`zoom-canvas'`,都使用行为的默认参数。 + +## 切换 Mode + +默认时 graph 会使用 `default` 模式 ,可以拖动和缩放画布,当需要点击选中节点时,可以通过  `graph.setMode('edit')`  来切换到 `edit` 的 Mode 。 + +```javascript +graph.setMode('edit'); +``` + +此时 graph 便支持了点击选中节点,`default` 模式下的拖拽画布 `'drag-canvas'`、放缩画布行为 `'zoom-canvas'` 失效。 + +在调用了 `setMode` 方法后,G6 内部进行了以下操作: + +- 解绑目前图模式的所有事件监听; +- 生成新的 Behavior ,进行事件初始化; +- 绑定新的行为对应的事件监听。 + +## 编辑已有的 Mode + +如果有已经定义好的 Behavior ([内置 Behavior](/zh/docs/manual/middle/states/defaultBehavior) 或 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)),需要把它添加到某个模式下,可以通过  `graph.addBehaviors` 方法;需要从某个模式中移除一些 Behavior,可以使用  `graph.removeBehaviors` 方法。如下示例: + +```javascript +// 向 default 模式中添加名为 drag-canvas 的行为,并使用行为的默认配置 +graph.addBehaviors('drag-canvas', 'default'); + +// 从 default 模式中移除名为 drag-canvas 的行为 +graph.removeBehaviors('drag-canvas', 'default'); + +// 向 edit 模式中添加名为 drag-canvas 的行为,并定义个性化配置 +graph.addBehaviors( + { + type: 'drag-canvas', + direction: 'x', + }, + 'edit', +); + +// 从 edit 模式中移除名为 drag-canvas 的行为 +graph.removeBehaviors('drag-canvas', 'edit'); + +// 一次向 default 模式中添加多个行为 +graph.addBehaviors(['drag-canvas', 'zoom-canvas'], 'default'); + +// 一次从 default 模式中移除多个行为 +graph.removeBehaviors(['drag-canvas', 'zoom-canvas'], 'default'); + +// -------- + +// 更新 'default' 模式下的 behavior 'zoom-canvas' +graph.updateBehavior('zoom-canvas', { sensitivity: 1.5, enableOptimize: true}, 'default'); + +// 更新 'select' 模式下的 behavior 'click-select' +graph.updateBehavior('click-select', { trigger: 'ctrl' }, 'select'); +``` + +## 相关阅读 + +- [使用多种交互模式](/zh/docs/manual/advanced/mode-and-custom-behavior) +- [内置交互行为 Behavior](/zh/docs/manual/middle/states/defaultBehavior) +- [自定义交互行为 Behavior](/zh/docs/manual/middle/states/custom-behavior) diff --git a/packages/site/docs/manual/middle/states/state.en.md b/packages/site/docs/manual/middle/states/state.en.md new file mode 100644 index 0000000000..2fdf4e0b7f --- /dev/null +++ b/packages/site/docs/manual/middle/states/state.en.md @@ -0,0 +1,314 @@ +--- +title: State +order: 4 +--- + +## What is State + +The **State** in G6 is the state of an item (node/edge), including **Interaction State** and **Bussiness State**. + +In G6, the way to configure interaction state and business state is the same. For some users who only use G6 to develop of a certain requirement, and do not want to understand G6 in depth, there is no need to distinguish the difference between the interactive state and the business state. You can define and use the states in the same way without understanding cost. + +### Interaction State + +The interaction state is closely related to specific interaction actions, such as the user using a mouse to select a node, or hover an edge. + +G6 handles interactive states by default. + +### Bussiness State + +Business state refers to the states customized according to the user's business needs. Business state is not related to interaction actions, and is strongly related to specific business logic. It can also be understood as being strongly data-driven. Such as the execution status of a task, the approval state of an application, etc., different data values ​​represent different business states. Business state has nothing to do with user interaction, but it is handled in the same way as interaction state in G6. + +## When to Use State + +The principle of judging whether or not to use state comes from the perspective of interaction and business: + +- Some interactions need to change the style and properties of nodes or edges; +- The content presented to the user will change based on the data (eg 1 for success, 0 for failure). + +If one of these conditions is met, state should be used. + +## Using state + +After defining the state, you can activate it by **`graph.setItemState`**, which can be called in the listeners like `graph.on` or the custom Behavior, or any place as you wish. + +### graph.on + +Activate the hover state in the event listeners. + +```javascript +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'hover', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'hover', false); +}); +``` + +### Behavior + +Activate the selected state in custom Behavior. + +```javascript +G6.registerBehavior('nodeClick', { + getEvents() { + return { + 'node:click': 'onClick', + }; + }, + onClick(e) { + e.preventDefault(); + if (!this.shouldUpdate.call(this, e)) { + return; + } + const { item } = e; + const graph = this.graph; + graph.setItemState(item, 'selected', true); + }, +}); +``` + +## Configure Styles for State + +In last section, we call `graph.setItemState` to activate/inactivate the states on a node or an edge. But it just marks the state on the item object. To reflect these states to the visual space which is observed by the end users, we need to set the item styles for different states to response the states change. + +There are three ways to define the styles of a state: + +- Define the state styles in `nodeStateStyles` and `edgeStateStyles` when instantiating a Graph; +- Define `stateStyles` in the data of a node or an edge; +- Configure the `stateStyles` in options when customizing a node/edge. + +The value of a state can be a boolean or string (multi-value). And you can configure styles for different sub-shapes by assigning the shape name. Refer to the examples below. + +⚠️ NOTICE: + +- Multi-value and state styles for sub-shapes are supported by v3.4 and later versions. +- The state styles for sub-shapes are only available for the sub-shapes which are the chilren of the root graphics group of a node/edge, but not other descendant shapes grouped by nested sub-graphics-groups. The sub-shapes in the built-in nodes/edges are all the children of the root graphics group of a node/edge. If you are customizing a node/edge type, this rule should be noticed. + +The format of `nodeStateStyles`, `edgeStateStyles`, and `stateStyles` are shown below: + +```javascript +{ + // the style of the boolean state 'hover' when it is true + hover: { + // the keyShape style + fill: '#d3adf7', + // the style for the shape with name 'node-label' + 'node-label': { + fontSize: 15 + }, + }, + // the style of the boolean state 'running' when it is true + running: { + // the keyShape style + stroke: 'steelblue', + }, + // state with multi-value and sub-shapes styles are supported by v3.4 and later version + // multi-value state 'bodyState', the style for the value is 'health' + 'bodyState:health': { + // the keyShape style + fill: 'green', + // ... other keyShape styles + // the style for the shape with name 'shape-name1' + 'shape-name1': { + stroke: '#ccc' + // ... other styles for the shape with name 'shape-name1' + }, + // the style for the shape with name 'shape-name2' + 'shape-name2': { + fill: 'red' + // ... other styles for the shape with name 'shape-name2' + } + }, + // multi-value state 'bodyState', the style for the value is 'suspect' + 'bodyState:suspect': { + // ... + }, + // multi-value state 'bodyState', the style for the value is 'ill' + 'bodyState:ill': { + // ... + } + // ... other state styles +} +``` +### Configure When instantiating a Graph + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'diamond', + style: { + // Node style on default state + fill: 'blue', + }, + }, + nodeStateStyles: { + hover: { + // The node style when it is on the its hover state is true + fill: '#d3adf7', + }, + running: { + // The node style when it is on the its running state is true + stroke: 'steelblue', + }, + }, +}); +``` + +The code above defines the styles of interaction state `hover` and bussiness state `running` by `nodeStateStyles`, which means when the mouse `hover` a node, the filling color of the node will be changed into `'#d3adf7'`; the shape with `name` `node-labe` of the node is also changed. When the `running` of a node is activated, the stroke color of the node will be changed into `'steelblue'`.
img + +(Supported by v3.4 and later versions) The example above also assign the state styles for `'bodyState'`, which is a multi-value state. When the `'bodyState'` is `'health'` for a node, the keyShape style and the style for the shapes with `name` `'shape-name1'` and `'shape-name2'` will be changed. + +Similarly, the `style` of `defaultEdge` defines the styles of the node on the default state. And `edgeStateStyles` can be used for defined the styles on other states. + + +### Configure the State Styles in the Data of a Node/Edge + +You can use this way to configure different state styles for different nodes and edges. + +```javascript +const data = { + nodes: [ + { + id: 'node1', + styles: { + // default styles + }, + stateStyles: { + //... see the example above + }, + // ... + }, + { + id: 'node2', + styles: { + // default styles + }, + stateStyles: { + //... see the example above + }, + // ... other configurations + }, + // ... + ], + edges: [ + { + source: 'node1', + target: 'node2', + styles: { + // default styles + }, + stateStyles: { + //... see the example above + }, + // ... other configurations + }, + //... + ], +}; +``` + + +### Configure Styles When Customizing Node + +The following code defines the styles for interaction states `hover` and `selected` by `stateStyles`. When user hovers anode, the opacity of the node will reduce to 0.8. When user clicks the ndoe, the line width of the stroke will widen to 3. + +```javascript +G6.registerNode('customShape', { + // The configurations of the custom node + options: { + size: 60, + style: { + lineWidth: 1 + }, + stateStyles: { + // The style of the node when the mouse hovers the node + hover: { + fillOpacity: 0.8 + }, + // The style of the node when the node is selected + selected: { + lineWidth: 3 + } + } + } +} +``` + +img + + +## Updating the Configurations for State Styles + +⚠️ NOTICE: Updating state styles are supported by v3.4 and later versions. + +Updating the state styles means modifying the configurations for one state styles configured in [Configure Styles for State](#Configure-Styles-for-State). E.g. the example below shows how to modify the default styles and state styles for a node or an edge instance. Similary, you could also use the format shown in [Configure Styles for State](#Configure-Styles-for-State) to modify the state styles for multi-value states. + +```javascript +graph.updateItem(item, { + // modify the default styles + style: { + stroke: 'green', + // modify the styles for the sub-shape with name 'node-label' + 'node-label': { + stroke: 'yellow', + }, + }, + stateStyles: { + // modify the styles for hover + hover: { + opacity: 0.1, + // modify the hover style for the sub-shape with name 'node-label' + 'node-text': { + stroke: 'blue', + }, + }, + }, +}); +``` + +There might be two situations when you calling `graph.updateItem`: + +- The state for the item is activated, that is `item.hasState('hover') === true`: the modified hover style will take effect immediately; +- The state for the item is inactivated, that is `item.hasState('hover') === false`: the modified hover style will take effect after calling `graph.setItemState(item, 'hover', true)`. + +## Cancel/Inactivate the State + +We suggest you to call `graph.clearItemStates` to cancel the state setted by `graph.setItemState`. `graph.clearItemStates` can be used for cancel one or several states. + +```javascript +graph.setItemState(item, 'bodyState', 'health'); +graph.setItemState(item, 'selected', true); +graph.setItemState(item, 'active', true); +// cancel one state +graph.clearItemStates(item, 'selected'); +graph.clearItemStates(item, ['selected']); +// cancel multiple states +graph.clearItemStates(item, ['bodyState:health', 'selected', 'active']); +``` + +## State Priority + +States conflict sometimes. State priority is necessary for this situation. G6 does not support explicit methods for setting state priorities. The rule is: the latter the state is setted (by `graph.setItemState`), the higher the priority. User could get the activate/inactivate state by `hasState`, and then decide whether to activate another state. In other word, it is controlled by the user. E.g. in most graph visualization app, the node will be highlighted when the mouse hovering the node. And usually we hope the node's 'active' state not to be covered by hover state. That is to say, the priority of 'active' state is higher than 'hover' state. + +```javascript +// Set the node to be 'active' +graph.setItemState(item, 'active', true); +// Hover the node +const hasActived = item.hasState('active'); +// Set the node to be 'hover' only if the node has no 'active' state +if (!hasActived) { + graph.setItemState(item, 'hover', true); +} +``` + + +## Conclusion + +G6 provides the state management for simplify the states of the items. For more information about the state thinking, please refer to The Thinking of the State in G6. diff --git a/packages/site/docs/manual/middle/states/state.zh.md b/packages/site/docs/manual/middle/states/state.zh.md new file mode 100644 index 0000000000..ec1408c6ae --- /dev/null +++ b/packages/site/docs/manual/middle/states/state.zh.md @@ -0,0 +1,344 @@ +--- +title: 状态 State +order: 4 +--- + +## 什么是 state + +G6 中的 **state**,指的是节点或边的状态,包括**交互状态**和**业务状态**两种。 + +在 G6 中,配置交互状态和业务状态的方式是相同的。对于部分只使用 G6 来完成某个需求的开发,而不想深入理解 G6 的用户,其实不用区分交互状态和业务状态的区别,使用相同的方式定义状态,完全没有理解成本。 + +### 交互状态 + +交互状态是与具体的交互动作密切相关的,如用户使用鼠标选中某个节点则该节点被选中,hover 到某条边则该边被高亮等。 + +G6 中默认处理的是交互状态。 + +### 业务状态 + +指根据用户业务需求自定义的状态。业务状态是与交互动作无关的,与具体业务逻辑强相关的,也可理解为是强数据驱动的。如某个任务的执行状态、某条申请的审批状态等,不同的数据值代表不同的业务状态。业务状态与用户交互动作无关,但在 G6 中的处理方式同交互状态一致。 + +## 何时使用 state + +判断是否该使用 state 的原则很简单,从交互和业务两个层面来看: + +- 某个交互动作要改变节点或边的样式及属性; +- 呈现给用户的内容会根据数据改变(如 1 代表成功,0 代表失败)。 + +满足上述条件其一,则应该使用 state。 + +## 设置 state + +**使用 `graph.setItemState(item, stateName, stateValue)`  来使定义的状态生效**。 + +### 状态的类型 + +状态可以是二值的,也可以是多值的(G6 3.4 后支持)。 + +#### 二值状态 + +通过 `graph.setItemState(item, stateName, stateValue)` 设置状态的值。 + +| 参数名 | 类型 | 描述 | +| ---------- | -------- | ----------------------------------------------- | +| item | Number | 需要被设置状态的节点/边实例 | +| stateName | String | 状态名称,可以是任意字符串 | +| stateValue | Booelean | true 代表该状态是被激活,false 代表该状态被灭活 | + +示例: + +```javascript +graph.setItemState(item, 'stateName', true); +``` + +#### 多值状态 + +多值状态在 G6 3.4 后支持。通过 `graph.setItemState(item, stateName, stateValue)` 设置状态的值。 + +| 参数名 | 类型 | 描述 | +| ---------- | ------ | --------------------------- | +| item | Number | 需要被设置状态的节点/边实例 | +| stateName | String | 状态名称,可以是任意字符串 | +| stateValue | String | 状态的值,可以是任意字符串 | + +示例: + +```javascript +graph.setItemState(item, 'stateName', 'stateValue'); +``` + +### 调用的时机 + +该函数可以在监听函数 `graph.on` 中被调用,也可以在自定义 Behavior 中调用,或在其他任意地方用于响应交互/业务的变化。 + +#### graph.on + +在回调函数中使定义的交互状态 hover 生效。 + +```javascript +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'hover', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'hover', false); +}); +``` + +#### Behavior + +在自定义 Behavior 中使定义的交互状态 selected 生效。 + +```javascript +G6.registerBehavior('nodeClick', { + getEvents() { + return { + 'node:click': 'onClick', + }; + }, + onClick(e) { + e.preventDefault(); + if (!this.shouldUpdate.call(this, e)) { + return; + } + const { item } = e; + const graph = this.graph; + graph.setItemState(item, 'selected', true); + }, +}); +``` + +## 配置 state 样式 + +上小节使用 `graph.setItemState` 使某些状态在图元素(节点/边)上被激活/灭活,仅仅是为该元素做了某些状态的标识。为了将这些状态反应到终端用户所见的视觉空间中,我们需要为不同的状态设置不同的图元素样式,以响应该图元素状态的变化。 + +在 G6 中,有三种方式配置不同状态的样式: + +- 在实例化 Graph 时,通过 `nodeStateStyles` 和 `edgeStateStyles` 对象定义; +- 在节点/边数据中,在 `stateStyles` 对象中定义状态; +- 在自定义节点/边时,在 options 配置项的 `stateStyles` 对象中定义状态。 + +可为二值/多值状态设置 keyShape 样式以及其他子图形的样式。 + +⚠️ 注意: + +- 多值状态和除 keyShape 以外的子图形状态样式设置在 V3.4 后支持。 +- 子图形状态样式仅限于指定节点/边的图形分组下平铺的图形,不支持嵌套图形分组下的图形。内置节点/边的图形分组内的图形均为平铺,在自定义节点时需要注意该规则。 + +`nodeStateStyles` 、 `edgeStateStyles` 、 `stateStyles` 对象的格式如下: + +```javascript +{ + // 二值状态 hover 为 true 时的样式 + hover: { + // keyShape 的状态样式 + fill: '#d3adf7', + // name 为 node-label 的子图形在该状态值下的样式 + 'node-label': { + fontSize: 15 + }, + }, + // 二值状态 running 为 true 时的样式 + running: { + // keyShape 的状态样式 + stroke: 'steelblue', + }, + // 多值状态与子图形样式的设置在 G6 3.4 后支持 + // 多值状态 bodyState 为 health 时的样式 + 'bodyState:health': { + // keyShape 该状态值下的样式 + fill: 'green', + // ... 其他样式 + // name 为 shape-name1 的子图形在该状态值下的样式 + 'shape-name1': { + stroke: '#ccc' + // ... 其他样式 + }, + // name 为 shape-name2 的子图形在该状态值下的样式 + 'shape-name2': { + fill: 'red' + // ... 其他样式 + } + }, + // 多值状态 bodyState 为 suspect 时的样式 + 'bodyState:suspect': { + // ... + }, + // 多值状态 bodyState 为 ill 时的样式 + 'bodyState:ill': { + // ... + } + // ... 其他状态 +} +``` + +### 实例化 Graph 时定义 state 样式 + +使用这种方式可以为图上的所有节点/边配置全局统一的 state 样式。 + +```javascript +const graph = new G6.Graph({ + container: 'mountNode', + width: 800, + height: 600, + defaultNode: { + type: 'diamond', + style: { + // 默认状态样式 + fill: 'blue', + // ... 其他样式 + }, + }, + nodeStateStyles: { + // ...见上方例子 + }, + defaultEdge: { + // ... + }, + edgeStateStyles: { + // ... + }, +}); +``` + +上面的实例代码中,我们在实例化 Graph 时候,通过 `nodeStateStyles` 定义了交互状态 `hover` 和业务状态 `running` 的样式。当某个任务状态变为正在执行时,二值状态 `running` 被设置为 `true` 后,节点的描边色将变为 `'steelblue'`。当二值状态 `hover` 被设置为 `true` 时,节点 `keyShape` 的填充色会变为 `'#d3adf7'`;(V3.4 后支持)且该节点中 `name` 为 `'node-label'` 的子图形也会发生改变,这里的 `'node-label'` 即节点上的文本图形,它的 `fontSize` 将会发生改变。
img + +(V3.4 后支持)上面实例还指定了名为 `'bodyState'` 的多值状态各值下的样式。当一个节点的 `'bodyState'` 的值被设置为 `'health'` 时,该节点的 keyShape 样式以及 `name` 为 `'shape-name1'` 和 `'shape-name2'` 的子图形样式都会发生变化。 + +同理,`defaultEdge` 中的 `style` 属性定义了默认状态下边的样式,使用 `edgeStateStyles`  可以定义不同状态下边的样式。 + +### 在节点/边数据中定义 state 样式 + +使用这种方式可以为不同的节点/边分别配置不同的 state 样式。 + +```javascript +const data = { + nodes: [ + { + id: 'node1', + styles: { + // 默认样式 + }, + stateStyles: { + //... 见上方例子 + }, + // ... + }, + { + id: 'node2', + styles: { + // 默认样式 + }, + stateStyles: { + //... 见上方例子 + }, + // ...其他配置 + }, + // ... + ], + edges: [ + { + source: 'node1', + target: 'node2', + styles: { + // 默认样式 + }, + stateStyles: { + //... 见上方例子 + }, + // ...其他配置 + }, + //... + ], +}; +``` + +### 自定义节点和边时定义 state 样式 + +使用这种方式可以为自定义的节点/边类型配置 state 样式。 + +```javascript +G6.registerNode('customShape', { + // 自定义节点时的配置 + options: { + size: 60, + style: { + lineWidth: 1 + }, + stateStyles: { + // ... 见上方例子 + } + } +} +``` + +## 更新状态样式配置 + +⚠️ 注意: 更新状态样式配置在 V3.4 后支持。 + +更新状态样式配置是指更改在 [配置 state 样式](#配置-state-样式) 中设置的某状态下的样式配置。如下代码可以修改节点/边实例的默认样式以及各状态下的样式配置。同样,您也可以使用 [配置 state 样式](#配置-state-样式) 中的样式格式修改多值状态的样式配置。 + +```javascript +graph.updateItem(item, { + // 修改默认样式 + style: { + stroke: 'green', + // 修改 name 为 'node-label' 的子图形的默认样式 + 'node-label': { + stroke: 'yellow', + }, + }, + stateStyles: { + // 修改 hover 状态下的样式 + hover: { + opacity: 0.1, + // 修改 name 为 'node-label' 的子图形 hover 状态下的样式 + 'node-text': { + stroke: 'blue', + }, + }, + }, +}); +``` + +使用 `graph.updateItem` 更新某状态的样式配置时,可能存在两种情况: + +- item 的该状态为激活状态,即 `item.hasState('hover') === true`:此时该状态值对应的修改后的样式配置会立即在 item 上生效; +- item 的该状态为灭活状态或不存在该状态,即 `item.hasState('hover') === false`:在用户执行 `graph.setItemState(item, 'hover', true)` 后,更新后的配置在 item 上的样式。 + +## 取消状态 + +建议使用 `graph.clearItemStates` 来取消 `graph.setItemState` 设置的状态。`graph.clearItemStates` 支持一次取消单个或多个状态。 + +```javascript +graph.setItemState(item, 'bodyState', 'health'); +graph.setItemState(item, 'selected', true); +graph.setItemState(item, 'active', true); +// 取消单个状态 +graph.clearItemStates(item, 'selected'); +graph.clearItemStates(item, ['selected']); +// 取消多个状态 +graph.clearItemStates(item, ['bodyState:health', 'selected', 'active']); +``` + +## 状态优先级 + +有时候,各个状态的样式之间可能有冲突,需要控制哪一状态的样式优先显示。G6 不提供显式设置状态优先级的方法,所有状态遵循:后设置的状态(通过 `graph.setItemState`)优先级高于前者。用户可以通过 `hasState` 方法判断元素的某种状态是否是激活态,从而判断是否应该激活另一个状态。这一逻辑完全由业务用户控制,实现这种控制也非常简单。例如,一般情况下,鼠标 hover 到某个节点后,该节点会高亮,但希望当该节点处于 active 状态时,鼠标 hover 上去后也不要覆盖 active 的状态,即 active 优先级高于 hover。 + +```javascript +// 设置节点处于 active 状态 +graph.setItemState(item, 'active', true); +// 鼠标 hover +const hasActived = item.hasState('active'); +// 当节点没有 active 时才设置 hover 状态 +if (!hasActived) { + graph.setItemState(item, 'hover', true); +} +``` + +## 小结 + +G6 底层提供了状态管理的能力,通过使用 state,简化了状态管理,降低了用户的认知成本。更多关于 G6 中状态的内容请参考  G6 状态量思考。 diff --git a/packages/site/docs/manual/tutorial/animation.en.md b/packages/site/docs/manual/tutorial/animation.en.md new file mode 100644 index 0000000000..97b0cb4770 --- /dev/null +++ b/packages/site/docs/manual/tutorial/animation.en.md @@ -0,0 +1,36 @@ +--- +title: Animation* +order: 6 +--- + +The animation mechanism is too complicated to understand by beginners and out of the scope of the tutorial. In this chapter, we only introduce the animation in G6 briefly. For more information, please refer to [Basic Animation](/en/docs/manual/middle/animation). + +There are two levels of animation in G6: + +- GLobal animation: Transform the graph animatively when the changes are global; +- Item animation: The animation on a node or an edge. + +## Global Animation + +The global animation is controlled by Graph instance. It takes effect when some global changes happen, such as: + +- `graph.updateLayout(cfg)` + +Configure `animate: true` when instantiating a graph to achieve it. + +**Example** + +```javascript +const graph = new G6.Graph({ + // ... // Other configurations + animate: true, // Boolean, whether to activate the animation when global changes happen +}); +``` + +## Item Animation + +G6 allows user to customize animation for item when register a type of custom item.
img + +img + +For more cases, please refer to [Animation Case](/en/examples/scatter/node). diff --git a/packages/site/docs/manual/tutorial/animation.zh.md b/packages/site/docs/manual/tutorial/animation.zh.md new file mode 100644 index 0000000000..cbf88addd8 --- /dev/null +++ b/packages/site/docs/manual/tutorial/animation.zh.md @@ -0,0 +1,36 @@ +--- +title: 动画(选读) +order: 6 +--- + +由于动画机制较为复杂,我们未在 Tutorial-案例 中增加动画。本文简单描述了 G6 中的动画,希望快速上手的用户可以跳过本文,希望深入了解的用户可参见:[基础动画](/zh/docs/manual/middle/animation)。 + +G6 的动画分为两个层次: + +- 图全局动画:图整体变化时的动画过渡; +- 元素动画:节点和边的动画效果。 + +## 全局动画 + +G6 的全局动画指通过图实例进行操作时,产生的动画效果。例如: + +- `graph.updateLayout(cfg)` + +通过实例化图时配置 `animate: true`,可以达到每次进行上述操作时,动画效果变化的目的。 + +**例子** + +```javascript +const graph = new G6.Graph({ + // ... // 其他配置项 + animate: true, // Boolean,可选,全局变化时否使用动画过渡 +}); +``` + +## 元素动画 + +G6 允许用户通过自定义节点/边的方式,给元素增加动画效果,如下:
img + +img + +更多关于动画的案例请参考 [G6 中的动画案例](/zh/examples/scatter/node)。 diff --git a/packages/site/docs/manual/tutorial/behavior.en.md b/packages/site/docs/manual/tutorial/behavior.en.md new file mode 100644 index 0000000000..620383f516 --- /dev/null +++ b/packages/site/docs/manual/tutorial/behavior.en.md @@ -0,0 +1,302 @@ +--- +title: Interaction Behavior +order: 4 +--- + +G6 encapsulates a set of interaction behaviors. Now we add simple some behaviors to **Tutorial Demo**: hover node, click node, click edge, drag cavas, zoom canvas. The expected result: + +img + +
Figure 1 **Tutorial Demo** with interaction behaviors
+ +## Basic Concept + +### Interaction Behavior + +G6 provides several **Built-in** interaction behaviors. You can enable these behaviors conveniently: + +- `drag-canvas`: enable the canvas to be dragged; +- `zoom-canvas`: enable the canvas to be zoomed; + +Refer to [Interaction Behavior](/en/docs/manual/middle/states/defaultBehavior) document for more information. + +### Mode + +Mode is a mechanism for state management in G6. One mode is a set of several Behaviors. You can assemble different Behaviors to modes. The concept of mode is too complicated to understand for the beginners of G6. You do not need to know it well in this tutorial. For more information, please refer to [Interaction Mode](/en/docs/manual/middle/states/mode). + +### Interaction State + +[State](/en/docs/manual/middle/states/state) is a mechanism of item state in G6. You can set different item styles for different states. When the state of an item is changed, the style will be updated automatically. For example, set the state `'click'` of a node as `true` or `false`, and set the node style of the state `'click'` with thicker stroke. This style will take effect when the state `'click'` is switched to `true`, and restore when `'click'` state is switched to `false`. There will be a specific in the Usage part. + +## Usage + +### Built-in Behaviors: Drag and Zoom + +Only assign `modes` when instantiating the graph, the corresponding built-in Behaviors will be enabled: + +```javascript +const graph = new G6.Graph({ + // ... // Other configurations + modes: { + default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // Allow users to drag canvas, zoom canvas, and drag nodes + }, +}); +``` + +The code above uses the Behaviors by assigning their types. Besides, you can also configure the parameters for them, e.g. the sensitivity of zooming, max/min zoom ratio. Refer to [Ineteraction Behavior](/en/docs/manual/middle/states/defaultBehavior) document for more detail. + +`modes` object above defines a set of interaction modes of the graph, where `default` is the default mode, which includes `'drag-canvas'`, `'zoom-canvas'`, and `'drag-node'`. You can add more modes with their Behaviors into `modes`, e.g. `edit` mode: + +```javascript +// Different modes with different Behaviors +modes: { + default: ['drag-canvas'], + edit: [] +} +``` + +Refer to [Interaction Mode](/en/docs/manual/middle/states/mode) and [Interaction Behavior](/en/docs/manual/middle/states/defaultBehavior) document for more detail. + +### Hover and Click to Change Styles + +Sometimes, the styles of the items interacted by users should be updated to make the response. As shown in figure 1, the styles are changed when user hovers the node, clicks the node, and clicks the edge. It is achieved by [State](/en/docs/manual/middle/states/state) mechanism. In other word, whether the item is clicked or hovered can be described as some states. You are able to set the styles for different states by two steps: + +- Step 1: Set the styles for different states; +- Step 2: Listen to the relative events and switch the states. + +#### Set the State Styles + +Set the state styles by `nodeStateStyles` and `edgeStateStyles` for nodes and edges respectively when instantiating a Graph.
The relative requirements in **Tutorial Demo** are: + +- The color of the node is changed when mouse hover it; +- The stroke of the node gets thicker and darker when user clicks it; +- The edge become blue when user clicks it. + +The following code sets the styles for nodes in the state of `hover` and `click`( = `true`), and the styles for edges in the state of `click` ( = `true`): + +```javascript +const graph = new G6.Graph({ + // ... // Other configurations + // The set of styles of nodes in different states + nodeStateStyles: { + // The node style when the state 'hover' is true + hover: { + fill: 'lightsteelblue', + }, + // The node style when the state 'click' is true + click: { + stroke: '#000', + lineWidth: 3, + }, + }, + // The edge styles in different states + edgeStateStyles: { + // The edge style when the state 'click' is true + click: { + stroke: 'steelblue', + }, + }, +}); +``` + +#### Listen to the Events and Switch the States + +The listeners in G6 are mounted on the instance of Graph. `graph` is the instance of G6.Graph in the following code. `graph.on()` listens some event (`click` / `mouseenter` / `mouseleave` / ... all the events are collected in: [Event API](/en/docs/api/Event))of some type of item(`node` / `edge`) + +```javascript +// add listener on graph +graph.on('itemType:event', (e) => { + // do something +}); +``` + +Now, we add listeners to graph for **Tutorial Demo**, and update the states by `graph.setItemState()`: + +```javascript +// Mouse enter a node +graph.on('node:mouseenter', (e) => { + const nodeItem = e.item; // Get the target item + graph.setItemState(nodeItem, 'hover', true); // Set the state 'hover' of the item to be true +}); + +// Mouse leave a node +graph.on('node:mouseleave', (e) => { + const nodeItem = e.item; // Get the target item + graph.setItemState(nodeItem, 'hover', false); // Set the state 'hover' of the item to be false +}); + +// Click a node +graph.on('node:click', (e) => { + // Swich the 'click' state of the node to be false + const clickNodes = graph.findAllByState('node', 'click'); + clickNodes.forEach((cn) => { + graph.setItemState(cn, 'click', false); + }); + const nodeItem = e.item; // et the clicked item + graph.setItemState(nodeItem, 'click', true); // Set the state 'click' of the item to be true +}); + +// Click an edge +graph.on('edge:click', (e) => { + // Swich the 'click' state of the edge to be false + const clickEdges = graph.findAllByState('edge', 'click'); + clickEdges.forEach((ce) => { + graph.setItemState(ce, 'click', false); + }); + const edgeItem = e.item; // Get the clicked item + graph.setItemState(edgeItem, 'click', true); // Set the state 'click' of the item to be true +}); +``` + +## Complete Code + +```html + + + + + Tutorial Demo + + +
+ + + + + + +``` + +⚠️Attention: 
Replace the url `'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'` to change the data into yours. diff --git a/packages/site/docs/manual/tutorial/behavior.zh.md b/packages/site/docs/manual/tutorial/behavior.zh.md new file mode 100644 index 0000000000..176d1fc722 --- /dev/null +++ b/packages/site/docs/manual/tutorial/behavior.zh.md @@ -0,0 +1,305 @@ +--- +title: 图的交互 Behavior +order: 4 +--- + +G6 封装了一系列交互方法,方便用户直接使用。本文将为 **Tutorial 案例** 增加简单的交互:hover 节点、点击节点、点击边、放缩画布、拖拽画布。本节目标效果如下: + +img + +> 图 1 Tutorial 案例的交互效果。 + +## 基本概念 + +### 交互行为 Behavior + +G6 中的交互行为。G6 **内置**了一系列交互行为,用户可以直接使用。简单地理解,就是可以一键开启这些交互行为: + +- `drag-canvas`:拖拽画布; +- `zoom-canvas`:缩放画布。 + +更多详见:[交互行为 Behavior](/zh/docs/manual/middle/states/defaultBehavior) + +### 交互管理 Mode + +Mode 是 G6 交互行为的管理机制,一个 mode 是多种行为 Behavior 的组合,允许用户通过切换不同的模式进行交互行为的管理。由于该概念较为复杂,在本入门教程中,读者不需要对该机制深入理解。如有需求,参见 [交互模式 Mode](/zh/docs/manual/middle/states/mode)。 + +### 交互状态 State + +[状态 State](/zh/docs/manual/middle/states/state) 是 G6 中的状态机制。用户可以为图中的元素(节点/边)设置不同的状态及不同状态下的样式。在状态发生变化时,G6 自动更新元素的样式。例如,可以为节点设置状态 `'click'` 为 `true` 或 `false`,并为节点设置 `'click'` 的样式为加粗节点边框。当 `'click'` 状态被切换为 `true` 时,节点的边框将会被加粗,`'click'` 状态被切换为 `false` 时,节点的样式恢复到默认。在下面的使用方法中,将会有具体例子。 + +## 使用方法 + +### 拖拽、缩放——内置的交互行为 + +在 G6 中使用内置 Behavior 的方式非常简单,只需要在图实例化时配置 `modes`。拖拽和缩放属于 G6 内置交互行为,修改代码如下: + +```javascript +const graph = new G6.Graph({ + // ... // 其他配置项 + modes: { + default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // 允许拖拽画布、放缩画布、拖拽节点 + }, +}); +``` + +除了直接使用内置交互名称外,也可以为 Behavior 配置参数,例如放缩画布的敏感度、最大/最小放缩程度等,具体用法参见  [交互行为 Behavior](/zh/docs/manual/middle/states/defaultBehavior)。 + +上面代码中的 `modes` 定义了 G6 的模式,`default` 是默认的模式,还可以允许有其他的模式,比如:编辑模式 `edit` 等。不同的模式,用户能进行的行为可以不同,比如默认模式能拖拽画布,编辑模式不允许拖拽画布: + +```javascript +// 举例解释不同模式 +modes: { + default: ['drag-canvas'], + edit: [] +} +``` + +更多关于模式、行为可以参考: [交互模型 Mode](/zh/docs/manual/middle/states/mode) 和 [交互行为 Behavior](/zh/docs/manual/middle/states/defaultBehavior) 文档。 + +### Hover、Click 改变样式——状态式交互 + +有时我们希望通过交互可以将元素样式变成特定样式,如我们看到的图 1 中,鼠标 hover 节点、点击节点、点击边时,样式发生了变化。这里涉及到了 G6 中 [状态 State](/zh/docs/manual/middle/states/state) 的概念。简单地说,是否 `hover` 、`click` 、任何操作(可以是自己起的状态名),都可以称为一种状态(state)。用户可以自由设置不同状态下的元素样式。要达到交互更改元素样式,需要两步: + +- Step 1: 设置各状态下的元素样式; +- Step 2: 监听事件并切换元素状态。 + +#### 设置各状态下的元素样式 + +在实例化图时,通过 `nodeStateStyles` 和 `edgeStateStyles` 两个配置项可以配置元素在不同状态下的样式。
为达到 **Tutorial 案例** 中的效果: + +- 鼠标 hover 节点时,该节点颜色变浅; +- 点击节点时,该节点边框加粗变黑; +- 点击边时,该边变成蓝色。 + +下面代码设置了节点分别在 `hover` 和 `click` 状态为 `true` 时的样式,边在 `click` 状态为 `true` 时的样式: + +```javascript +const graph = new G6.Graph({ + // ... // 其他配置项 + // 节点不同状态下的样式集合 + nodeStateStyles: { + // 鼠标 hover 上节点,即 hover 状态为 true 时的样式 + hover: { + fill: 'lightsteelblue', + }, + // 鼠标点击节点,即 click 状态为 true 时的样式 + click: { + stroke: '#000', + lineWidth: 3, + }, + }, + // 边不同状态下的样式集合 + edgeStateStyles: { + // 鼠标点击边,即 click 状态为 true 时的样式 + click: { + stroke: 'steelblue', + }, + }, +}); +``` + +#### 监听事件并切换元素状态 + +G6 中所有元素监听都挂载在图实例上,如下代码中的 `graph` 对象是 G6.Graph 的实例,`graph.on()`  函数监听了某元素类型(`node` / `edge`)的某种事件(`click` / `mouseenter` / `mouseleave` / ... 所有事件参见:[Event API](/zh/docs/api/Event))。 + +```javascript +// 在图实例 graph 上监听 +graph.on('元素类型:事件名', (e) => { + // do something +}); +``` + +现在,我们通过下面代码,为 **Tutorial 案例** 增加点和边上的监听事件,并在监听函数里使用 `graph.setItemState()` 改变元素的状态: + +```javascript +// 鼠标进入节点 +graph.on('node:mouseenter', (e) => { + const nodeItem = e.item; // 获取鼠标进入的节点元素对象 + graph.setItemState(nodeItem, 'hover', true); // 设置当前节点的 hover 状态为 true +}); + +// 鼠标离开节点 +graph.on('node:mouseleave', (e) => { + const nodeItem = e.item; // 获取鼠标离开的节点元素对象 + graph.setItemState(nodeItem, 'hover', false); // 设置当前节点的 hover 状态为 false +}); + +// 点击节点 +graph.on('node:click', (e) => { + // 先将所有当前是 click 状态的节点置为非 click 状态 + const clickNodes = graph.findAllByState('node', 'click'); + clickNodes.forEach((cn) => { + graph.setItemState(cn, 'click', false); + }); + const nodeItem = e.item; // 获取被点击的节点元素对象 + graph.setItemState(nodeItem, 'click', true); // 设置当前节点的 click 状态为 true +}); + +// 点击边 +graph.on('edge:click', (e) => { + // 先将所有当前是 click 状态的边置为非 click 状态 + const clickEdges = graph.findAllByState('edge', 'click'); + clickEdges.forEach((ce) => { + graph.setItemState(ce, 'click', false); + }); + const edgeItem = e.item; // 获取被点击的边元素对象 + graph.setItemState(edgeItem, 'click', true); // 设置当前边的 click 状态为 true +}); +``` + +## 完整代码 + +至此,完整代码如下: + +```html + + + + + Tutorial Demo + + +
+ + + + + + +``` + +⚠️ 注意:
若需更换数据,请替换  `'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'`  为新的数据文件地址。 diff --git a/packages/site/docs/manual/tutorial/elements.en.md b/packages/site/docs/manual/tutorial/elements.en.md new file mode 100644 index 0000000000..af1b6e1f40 --- /dev/null +++ b/packages/site/docs/manual/tutorial/elements.en.md @@ -0,0 +1,306 @@ +--- +title: Configure the Items +order: 2 +--- + +There are `Node` and `Edge` two types of items in a graph. In the last chapter, we rendered the **Tutorial Demo** with items with rough styles. Now, we are going to beautify the items while introducing the properties of the items. + +img + +> Figure 1  The **Tutorial Demo** with cofigured items. + +## Basic Concept + +### Graph Item + +There are `Node` and `Edge` two types of items in a graph. Several [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) and [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge) are provided by G6. The main difference between different types of items is their [Graphics Shape](/en/docs/manual/middle/elements/shape/shape-keyshape). For example, a node's graphics type can be a circle, a rect, an image, or others. + +## Properties of Item + +The properties of an item can be be divided into two categories: + +- **Style Property `style`**: Corresponds to the style in Canvas. When the [State](/en/docs/manual/middle/states/state) of an item is changed, the style can be updated. It is an object named `style`; +- **Other Property**: Such as graphics `type`, `id`, they are a kind of properties that will not be changed when the [State](/en/docs/manual/middle/states/state) of the item is changed. + +For example, When you change the state `'hover'` or `'click'` to `true` for a node A, only the **style properties** of A can be updated, e.g. `fill`, `stroke`, and so on. The **other properties** such as `type` can not be changed. To update the other properties, configure A by [graph.updateItem](/en/docs/api/graphFunc/item#graphupdateitemitem-model-stack) manually. + +### Data Structure + +The data structure of a node: + +```javascript +{ + id: 'node0', // Unique id of the node + type: 'circle', // The type of graphics shape of the node + size: 40, // The size + label: 'node0' // The label + visible: true, // Controls the visible of the node when first render. false means hide the item. All the items are visible by default + labelCfg: { // The configurations for the label + positions: 'center',// The relative position of the label + style: { // The style properties of the label + fontSize: 12, // The font size of the label + // ... // Other style properties of the label + } + } + // ..., // Other properties of the node + style: { // The object of style properties of the node + fill: '#000', // The filling color + stroke: '#888', // The stroke color + // ... // Other styleattribtues of the node + } +} +``` + +The data structure of an edge is similar to node, but two more properties `source` and `target` in addition, representing the `id` of the source node and the `id` of the target node respectively. + +
We can refine the visual requirements in figure 1 of **Tutorial Demo** into: + +- Visual Effect: + - R1: Set the color for stroke and filling for nodes with `fill` and `stroke`; + - R2: Set the color for the label with `labelCfg`; + - R3: Set the opacity and color for edges with `opacity`,`stroke`; + - R4: Set the direction of the label with `labelCfg`; +- Map the data to visual channels: + - R5: Configure the type of nodes with `type` according to the property `class` in node data; + - R6: Configure the line widht of edges with `lineWidth` according to the property `weight` in edge data. + +## Configure the Properties + +To satisfy different scenario, G6 provides 7 ways to configure the properties for items. Here we will only introduce two of them: + +1. Configure the global properties when instantiating a Graph; +2. Configure the properties for different items in their data. + +### 1. Configure the Global Properties When Instantiating a Graph + +**Applicable Scene:** Unify the configurations for all the nodes or edges.
**Usage:** Configure it with two configurations of graph: + +- `defaultNode`: The **Style Property** and **Other Properties** in the default state; +- `defaultEdge`: The **Style Property** and **Other Properties** in the default state. + +⚠️Attention: It is a way of unified global configuration, which does not distinguish the nodes with different properties (e.g. `class` and `weight`) in their data. That is to say, only R1, R2, R3, and R4 can be satisfied now: + +img + +> Figure 2  **Tutorial Demo** with items configured by global configurations. + +
Configure the `defaultNode` and `defaultEdge` for graph to achieve the expected effect: + +```javascript +const graph = new G6.Graph({ + // ... // Other configurations of the graph + // The style properties and other properties for all the nodes in the default state + defaultNode: { + size: 30, // The size of nodes + // ... // The other properties + // The style properties of nodes + style: { + fill: 'steelblue', // The filling color of nodes + stroke: '#666', // The stroke color of nodes + lineWidth: 1, // The line width of the stroke of nodes + }, + // The properties for label of nodes + labelCfg: { + // The style properties for the label + style: { + fill: '#fff', // The color of the text + }, + }, + }, + // The style properties and other properties for all the edges in the default state + defaultEdge: { + // ... // The other properties + // The style properties of edges + style: { + opacity: 0.6, // The opacity of edges + stroke: 'grey', // The color of the edges + }, + // The properties for label of edges + labelCfg: { + autoRotate: true, // Whether to rotate the label according to the edges + }, + }, +}); +``` + +### 2. Configure the Properties in Data + +**Applicable Scene:** By this way, you can configure different items according to their properties in data.
Thus, the R5 and R6 can be satisfied now.
**Usage:** Write the properties into each item data, or traverse the data to assign the properties. Here we show assigning the attrbiutes into data by traversing: + +```javascript +const nodes = remoteData.nodes; +nodes.forEach((node) => { + if (!node.style) { + node.style = {}; + } + switch ( + node.class // Configure the graphics type of nodes according to their class + ) { + case 'c0': { + node.type = 'circle'; // The graphics type is circle when class = 'c0' + break; + } + case 'c1': { + node.type = 'rect'; // The graphics type is rect when class = 'c1' + node.size = [35, 20]; // The node size when class = 'c1' + break; + } + case 'c2': { + node.type = 'ellipse'; // The graphics type is ellipse when class = 'c2' + node.size = [35, 20]; // The node size when class = 'c2' + break; + } + } +}); + +graph.data(remoteData); +``` + +The result: + +img + +> Figure 3 + +From figure 3, we find some nodes are rendered as rects, some are ellipses. We also set the `size` to override the `size` in global configuration. The `size` is an array when the node is a rect or an ellipse. We did not set the `size` for circle node, so `size: 30` in global configuration will still take effect for circle node. That is to say, configuring items by writing into data has higher priority than global configurations. + +We further set the line widths for edges according to their weight: + +```javascript +// const nodes = ... + +// Traverse the egdes data +const edges = remoteData.edges; +edges.forEach((edge) => { + if (!edge.style) { + edge.style = {}; + } + edge.style.lineWidth = edge.weight; // Mapping the weight in data to lineWidth +}); + +graph.data(remoteData); +``` + +The result: + +img + +The line width of the edges takes effect in the figure above. But the opacity and color setted in the global configurations are lost. The reason is that the global `style` object in graph instance is overrided by the second configure method. The solution is move all the styles to the data: + +```javascript +const graph = new G6.Graph({ + // ... + defaultEdge: { + // Remove the style here + labelCfg: { + // The properties for label of edges + autoRotate: true, // Whether to rotate the label according to the edges + }, + }, +}); + +// Traverse the nodes data +// const nodes = ... +// nodes.forEach ... + +// Traverse the egdes data +const edges = remoteData.edges; +edges.forEach((edge) => { + if (!edge.style) { + edge.style = {}; + } + edge.style.lineWidth = edge.weight; // Mapping the weight in data to lineWidth + // The styles are moved to here + opt.style.opacity = 0.6; + opt.style.stroke = 'grey'; +}); + +graph.data(remoteData); +graph.render(); +``` + +## Complete Code + +```html + + + + + Tutorial Demo + + +
+ + + + + + +``` + +⚠️Attention: 
Replace the url `'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'` to change the data into yours. diff --git a/packages/site/docs/manual/tutorial/elements.zh.md b/packages/site/docs/manual/tutorial/elements.zh.md new file mode 100644 index 0000000000..fbf43b7bf0 --- /dev/null +++ b/packages/site/docs/manual/tutorial/elements.zh.md @@ -0,0 +1,306 @@ +--- +title: 元素及其配置 +order: 2 +--- + +图的元素特指图上的**节点** `Node` 和**边** `Edge` 。在上一章节中,我们已经将 **Tutorial 案例**的图绘制了出来,但是各个元素及其 `label`  在视觉上很简陋。本文通过将上一章节中简陋的元素美化成如下效果,介绍元素的属性、配置方法。 + +img + +> 图 1  元素属性配置后的 **Tutorial 案例**。 + +## 基本概念 + +### 图的元素 + +图的元素特指图上的**节点** `Node` 和**边** `Edge` 。G6 内置了一系列 [内置的节点](/zh/docs/manual/middle/elements/nodes/defaultNode) 和 [内置的边](/zh/docs/manual/middle/elements/edges/defaultEdge),供用户自由选择。G6 不同的内置节点或不同的内置边主要区别在于元素的 [图形 Shape](/zh/docs/manual/middle/elements/shape/shape-keyshape),例如,节点可以是圆形、矩形、图片等。 + +## 元素的属性 + +不论是节点还是边,它们的属性分为两种: + +- **样式属性 `style`**:对应 Canvas 中的各种样式,在元素[状态 State](/zh/docs/manual/middle/states/state) 发生变化时,可以被改变; +- **其他属性**:例如图形类型( `type`)、id(`id` )一类在元素[状态 State](/zh/docs/manual/middle/states/state) 发生变化时不能被改变的属性。 + +例如,G6 设定 hover 或 click 节点,造成节点状态的改变,只能自动改变节点的**样式属性**(如 `fill`、`stroke` 等**)**,**其他属性**(如 `type`  等)不能被改变。如果需要改变其他属性,要通过  [graph.updateItem](/zh/docs/api/graphFunc/item#graphupdateitemitem-model-stack) 手动配置。**样式属性**是一个名为  `style`  的对象, `style` 字段与其他属性并行。 + +### 数据结构 + +以节点元素为例,其属性的数据结构如下: + +```javascript +{ + id: 'node0', // 元素的 id + type: 'circle', // 元素的图形 + size: 40, // 元素的大小 + label: 'node0' // 标签文字 + visible: true, // 控制初次渲染显示与隐藏,若为 false,代表隐藏。默认不隐藏 + labelCfg: { // 标签配置属性 + positions: 'center',// 标签的属性,标签在元素中的位置 + style: { // 包裹标签样式属性的字段 style 与标签其他属性在数据结构上并行 + fontSize: 12 // 标签的样式属性,文字字体大小 + // ... // 标签的其他样式属性 + } + } + // ..., // 其他属性 + style: { // 包裹样式属性的字段 style 与其他属性在数据结构上并行 + fill: '#000', // 样式属性,元素的填充色 + stroke: '#888', // 样式属性,元素的描边色 + // ... // 其他样式属性 + } +} +``` + +边元素的属性数据结构与节点元素相似,只是其他属性中多了 `source` 和 `target` 字段,代表起始和终止节点的 `id`。
细化在图 1 中 **Tutorial 案例** 的视觉需求,我们需要完成: + +- 视觉效果: + - R1: 节点的描边和填充色,对应节点样式属性:`fill`,`stroke`; + - R2: 节点上标签文本的颜色,对应节点其他属性:`labelCfg`; + - R3: 边的透明度和颜色,对应边样式属性:`opacity`,`stroke`; + - R4: 边上标签文本的方向和颜色,对应边其他属性:`labelCfg`; +- 数据与视觉映射: + - R5: 根据数据中节点的 `class` 属性映射节点的形状,对应节点其他属性:`type`; + - R6: 根据数据中边的 `weight` 属性映射边的粗细,对应边样式属性:`lineWidth`。 + +## 配置属性 + +在 G6 中,根据不同的场景需求,有 7 种配置元素属性的方式。这里,我们简单介绍其中的两种: + +1. 实例化图时配置元素的全局属性; +2. 在数据中配置。 + +### 1. 实例化图时全局配置 + +**适用场景:**所有节点统一的属性配置,所有边统一的属性配置。
**使用方式:**使用图的两个配置项: + +- `defaultNode`:节点在默认状态下的**样式属性**(`style`)和**其他属性**; +- `defaultEdge`:边在默认状态下的**样式属性**(`style`)和**其他属性**。 + +⚠️ 注意: 由于是统一的配置,不能根据数据中的属性(如 `class`、`weight`)等值的不同进行个性化设置,因此只能满足 R1、R2、R3、R4 需求。达到如下效果: + +img + +> 图 2  全局配置元素属性后的 **Tutorial 案例**。 + +
通过如下方式在实例化图时  `defaultNode` 和  `defaultEdge` ,可以完成上图效果: + +```javascript +const graph = new G6.Graph({ + // ... // 图的其他配置 + // 节点在默认状态下的样式配置(style)和其他配置 + defaultNode: { + size: 30, // 节点大小 + // ... // 节点的其他配置 + // 节点样式配置 + style: { + fill: 'steelblue', // 节点填充色 + stroke: '#666', // 节点描边色 + lineWidth: 1, // 节点描边粗细 + }, + // 节点上的标签文本配置 + labelCfg: { + // 节点上的标签文本样式配置 + style: { + fill: '#fff', // 节点标签文字颜色 + }, + }, + }, + // 边在默认状态下的样式配置(style)和其他配置 + defaultEdge: { + // ... // 边的其他配置 + // 边样式配置 + style: { + opacity: 0.6, // 边透明度 + stroke: 'grey', // 边描边颜色 + }, + // 边上的标签文本配置 + labelCfg: { + autoRotate: true, // 边上的标签文本根据边的方向旋转 + }, + }, +}); +``` + +### 2. 在数据中配置 + +**适用场景:**不同节点/边可以有不同的个性化配置。
因此,这种配置方式可以满足 R5、R6 需求。
**使用方式:**可以直接将配置写入数据文件;也可以在读入数据后,通过遍历的方式写入配置。这里展示读入数据后,通过遍历的方式写入配置。下面代码展示了如何遍历数据进行属性的配置: + +```javascript +const nodes = remoteData.nodes; +nodes.forEach((node) => { + if (!node.style) { + node.style = {}; + } + switch ( + node.class // 根据节点数据中的 class 属性配置图形 + ) { + case 'c0': { + node.type = 'circle'; // class = 'c0' 时节点图形为 circle + break; + } + case 'c1': { + node.type = 'rect'; // class = 'c1' 时节点图形为 rect + node.size = [35, 20]; // class = 'c1' 时节点大小 + break; + } + case 'c2': { + node.type = 'ellipse'; // class = 'c2' 时节点图形为 ellipse + node.size = [35, 20]; // class = 'c2' 时节点大小 + break; + } + } +}); + +graph.data(remoteData); +``` + +运行结果如下: + +img + +> 图 3 + +可以看到,图中有一些节点被渲染成了矩形,还有一些被渲染成了椭圆形。除了设置 `type` 属性之外,我们还覆盖了上文全局配置的节点的 `size` 属性,在矩形和椭圆的情况下,`size` 是一个数组;而在默认圆形的情况下,G6 将仍然读取全局配置的 `size` 属性为数字 `30`。也就是说,动态配置属性让我们既可以根据数据的不同配置不同的属性值,也可以有能力覆盖全局静态的属性值。 + +进一步地,我们尝试根据数据的比重不同,配置不一样边的粗细: + +```javascript +// const nodes = ... + +// 遍历边数据 +const edges = remoteData.edges; +edges.forEach((edge) => { + if (!edge.style) { + edge.style = {}; + } + edge.style.lineWidth = edge.weight; // 边的粗细映射边数据中的 weight 属性数值 +}); + +graph.data(remoteData); +``` + +结果如下: + +img + +如图所示,边的粗细已经按照数据的比重成功渲染了出来,但是边原有的样式(透明度、颜色)却丢失了。这是因为我们提到过动态配置属性会覆盖全局配置属性,这里配置了 `style.lineWidth`,导致覆盖了全局的 `style` 对象。解决办法是将被覆盖的边的样式都移到动态配置里面来: + +```javascript +const graph = new G6.Graph({ + // ... + defaultEdge: { + // 去掉全局配置的 style + labelCfg: { + // 边上的标签文本配置 + autoRotate: true, // 边上的标签文本根据边的方向旋转 + }, + }, +}); + +// 遍历点数据 +// const nodes = ... +// nodes.forEach ... + +// 遍历边数据 +const edges = remoteData.edges; +edges.forEach((edge) => { + if (!edge.style) { + edge.style = {}; + } + edge.style.lineWidth = edge.weight; // 边的粗细映射边数据中的 weight 属性数值 + // 移到此处 + edge.style.opacity = 0.6; + edge.style.stroke = 'grey'; +}); + +graph.data(remoteData); +graph.render(); +``` + +## 完整代码 + +至此,完整代码如下: + +```html + + + + + Tutorial Demo + + +
+ + + + + + +``` + +**⚠️ 注意:** 
若需更换数据,请替换  `'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'`  为新的数据文件地址。 diff --git a/packages/site/docs/manual/tutorial/epilog.en.md b/packages/site/docs/manual/tutorial/epilog.en.md new file mode 100644 index 0000000000..e722a2d128 --- /dev/null +++ b/packages/site/docs/manual/tutorial/epilog.en.md @@ -0,0 +1,19 @@ +--- +title: Conclusion +order: 7 +--- + +Congratulations! You have created a graph visualization powered by G6. You have learnt: + +- Basic rendering; +- Load remote data; +- Configure the graph and items; +- Utilize layout; +- Add interaction behavior; +- Add tools. + +[Complete Code of Tutorial Demo](https://codepen.io/Yanyan-Wang/pen/mdbYZvZ). + +If you wish to go further in G6, please check out [Key Concept in G6](/en/docs/manual/middle/graph); For further information, check out [Further Reading](/en/docs/manual/advanced/coordinate-system). + +You can also refer to [G6 API](/en/docs/api/Graph) during the process of development. diff --git a/packages/site/docs/manual/tutorial/epilog.zh.md b/packages/site/docs/manual/tutorial/epilog.zh.md new file mode 100644 index 0000000000..d3f99164f5 --- /dev/null +++ b/packages/site/docs/manual/tutorial/epilog.zh.md @@ -0,0 +1,19 @@ +--- +title: 结语 +order: 7 +--- + +恭喜!你已经成功创建了一个基于 G6 的图可视化应用,并学会了: + +- 基本绘制方法 +- 远程数据加载 +- 属性配置 +- 布局运用 +- 增加交互 +- 添加辅助工具 + +完整代码见:[Tutorial 案例代码](https://codepen.io/Yanyan-Wang/pen/mdbYZvZ)。 + +如果你希望继续学习深入的 G6 知识,请查看 [G6 核心概念](/zh/docs/manual/middle/graph);关于 G6 的扩展内容,请查看 [G6 扩展阅读](/zh/docs/manual/advanced/coordinate-system)。 + +开发过程中可通过 [G6 API](/zh/docs/api/Graph) 快速查询。 diff --git a/packages/site/docs/manual/tutorial/example.en.md b/packages/site/docs/manual/tutorial/example.en.md new file mode 100644 index 0000000000..c1a71c674c --- /dev/null +++ b/packages/site/docs/manual/tutorial/example.en.md @@ -0,0 +1,216 @@ +--- +title: Render the Tutorial Demo +order: 1 +--- + +In this chapter, we preliminary configure and render the **Tutorial Demo**. You will learn the common configurations of Graph. + +## Basic Rendering + +### Create a HTML Container + +Create an HTML container for graph canvas, `div` tag in general. G6 will append a `canvas` tag to it and draw graph on the `canvas`. + +```html + +
+ + + + +``` + +### Data Preparation + +The data for G6 should be JSON format, includes array properties `nodes` and `edges`: + +```html + +``` + +⚠️Attention: + +- `nodes` is an array of nodes, the `id` is an unique and required property; the `x` and `y` are the coordinates of the node; +- `edges` is an array of edges, `source` and `target` are required, represent the `id` of the source node and the `id` of the target node respectively. +- The properties of node and edge are described in [Built-in Nodes](/en/docs/manual/middle/elements/nodes/defaultNode) and [Built-in Edges](/en/docs/manual/middle/elements/edges/defaultEdge) document. + +### Instantiate the Graph + +The container, width, and height are required configurations when instantiating a Graph: + +```html + +``` + +### Load the Data and Render + +Load data and render are two separated steps. + +```html + +``` + +### The Result + +After calling `graph.render()` , G6 will render the graph according to the data. + +img + +## Load the Real Data + +In the above demo, we render a graph with two nodes and one edge defined in the code directly. For real scenario, the data might be loaded remotely. We prepare the JSON data for **Tutorial Demo** with the address:
`https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json` + +### Load the Remote Data + +Modify index.html to load remote data asynchronously by `fetch`, and pass it to the instance of G6 Graph: + +```html + +``` + +> `fetch` allows us to fetch the remote data asynchronously, and controll the process by `async`/`await`. If you are curious about `fetch` and `async`/`await`, please refer to: [async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function), [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) + +We will get the following result with the code above: + +img + +The data has been loaded correctly. But the result is a little bit strange due to the large amount of nodes and edges. Limited by the size of canvas, part of the graph is arranged out of the view. We are going to solve all these problems now. + +Here goes a part of tutorial-data.json. There are `x` and `y` in node data, and some of them are not in the range of `width: 800, height: 600`. + +```json +{ + "nodes": [ + { "id": "0", "label": "n0", "class": "c0", "x": 1000, "y": -100 }, + { "id": "1", "label": "n1", "class": "c0", "x": 300, "y": -10 } + //... + ], + "edges": [ + //... + ] +} +``` + +G6 will render the graph according to the position information in the data once G6 finds `x` and `y` in the data. This mechanism satisfies the requirement that rendering the source data. To solve the problem of the graph out of the view port partially, two configurations are provided: + +- `fitView`: Whether to fit the graph to the canvas; +- `fitViewPadding`: The padding between the content of the graph and the borders of the canvas. + +We modify the code about instantiating Graph as shown below: + +```javascript +const graph = new G6.Graph({ + // ... + fitView: true, + fitViewPadding: [20, 40, 50, 20], +}); +``` + +The result from this code shows that the graph has been fitted to the canvas: img + +## Common Configuration + +The configurations below will be used in the following Tutorial: + +| Name | Type | Options / Example | Default | Description | +| --- | --- | --- | --- | --- | +| fitView | Boolean | true / false | false | Whether to fit the graph to the canvas. | +| fitViewPadding | Number / Array | 20 / [ 20, 40, 50, 20 ] | 0 | The padding between the content of the graph and the borders of the canvas. | +| animate | Boolean | true / false | false | Whether to activate the global animation. | +| modes | Object | {
  default: [ 'drag-node', 'drag-canvas' ]
} | null | The set of graph interaction modes. This is a complicated concept, refer to [Mode](/en/docs/manual/middle/states/mode) for more detial. | +| defaultNode | Object | {
  type: 'circle',
  color: '#000',
  style: {
    ......
  }
} | null | The default global properties for nodes, includes styles properties and other properties. | +| defaultEdge | Object | {
  type: 'polyline',
  color: '#000',
  style: {
    ......
  }
} | null | The default global properties for edges, includes styles properties and other properties. | +| nodeStateStyles | Object | {
  hover: {
    ......
  },
  select: {
    ......
  }
} | null | The style properties of nodes in different states except for default state. Such as hover, select. | +| edgeStateStyles | Object | {
  hover: {
    ......
  },
  select: {
    ......
  }
} | null | The style properties of edges in different states except for default state. Such as hover, select. | + +## Complete Code + +```html + + + + + Tutorial Demo + + +
+ + + + + + +``` + +⚠️ Attention:
Replace the url `'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'` to change the data into yours. diff --git a/packages/site/docs/manual/tutorial/example.zh.md b/packages/site/docs/manual/tutorial/example.zh.md new file mode 100644 index 0000000000..9cee6c0427 --- /dev/null +++ b/packages/site/docs/manual/tutorial/example.zh.md @@ -0,0 +1,220 @@ +--- +title: 绘制 Tutorial 案例 +order: 1 +--- + +本文将进行 **Tutorial 案例**的简单绘制和配置。通过本文,你将知道创建一般图时一些常用的配置项及其作用。 + +## 基础绘制 + +### 创建容器 + +需要在 HTML 中创建一个用于容纳 G6 绘制的图的容器,通常为 `div`  标签。G6 在绘制时会在该容器下追加 `canvas` 标签,然后将图绘制在其中。 + +```html + +
+ + + + +``` + +### 数据准备 + +引入 G6 的数据源为 JSON 格式的对象。该对象中需要有节点(`nodes`)和边(`edges`)字段,分别用数组表示: + +```html + +``` + +⚠️ 注意: + +- `nodes` 数组中包含节点对象,唯一的 `id` 是每个节点对象中必要的属性,`x`、 `y` 用于定位; +- `edges` 数组中包含边对象,`source` 和 `target` 是每条边的必要属性,分别代表了该边的起始点 `id` 与 目标点 `id`。 +- 点和边的更多属性参见:[内置的节点](/zh/docs/manual/middle/elements/nodes/defaultNode),[内置的边](/zh/docs/manual/middle/elements/edges/defaultEdge) 教程。 + +### 图实例化 + +图实例化时,至少需要为图设置容器、宽、高: + +```html + +``` + +### 图的渲染 + +数据的加载和图的渲染是两个步骤,可以分开进行。 + +```html + +``` + +### 绘制结果 + +调用 `graph.render()` 方法之后,G6 引擎会根据加载的数据进行图的绘制。结果如下: + +img + +## 真实数据加载 + +上文中,我们使用了仅含有两个节点和一条边的数据,直接将数据定义放在了代码中。而真实场景的数据通常是远程接口请求加载的。为了方便,我们已经给读者准备好了一份 JSON 数据文件,地址如下:
`https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json` + +### 加载远程数据 + +修改 index.html,通过 `fetch` 函数异步加载远程的数据源,并传入 G6 的图实例中: + +```html + +``` + +> `fetch` 函数允许我们发起网络请求,加载数据,而其异步的过程可以通过 async/await 进行更合理的控制。这里我们为了方便起见,将主要逻辑放在了 `main` 函数里面。如果读者对 `fetch` 和 `async`/`await` 的知识有疑问,可以参考:async functionFetch API + +运行后,我们得到了下图结果: + +img + +乍看之下,图像有点奇怪,实际上数据已经正确的加载了进来。由于数据量比较大,节点和边都非常的多,显得内容比较杂乱。另外由于画布大小的限制,实际的图被画布截断了,目前只能看见部分内容,这个问题后文会继续解决。 + +请看下面摘取自 tutorial-data.json 的部分数据,我们发现数据中节点定义了位置信息 `x` 与 `y`,并且很多节点的  `x` 和 `y` 不在图的宽高(`width: 800, height: 600`)范围内: + +```json +{ + "nodes": [ + { "id": "0", "label": "n0", "class": "c0", "x": 1000, "y": -100 }, + { "id": "1", "label": "n1", "class": "c0", "x": 300, "y": -10 } + //... + ], + "edges": [ + //... + ] +} +``` + +由于 G6 在读取数据时,发现了数据中带有位置信息(`x` 和 `y`),就会按照该位置信息进行绘制,这是为了满足按照原始数据绘制的需求设计的。但为优化超出屏幕到问题,G6 提供了图的两个相关配置项: + +- `fitView`:设置是否将图适配到画布中; +- `fitViewPadding`:画布上四周的留白宽度。 + +我们将实例化图的代码更改为如下形式: + +```javascript +const graph = new G6.Graph({ + // ... + fitView: true, + fitViewPadding: [20, 40, 50, 20], +}); +``` + +上述代码将会生成如下图: img + +可以看到,图像已经可以自动适配画布的大小,完整的显示了出来。 + +## 常用配置 + +本文使用到的配置以及后续 Tutorial 将会使用到到常用配置如下: + +| 配置项 | 类型 | 选项 / 示例 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| fitView | Boolean | true / false | false | 是否将图适配到画布大小,可以防止超出画布或留白太多。 | +| fitViewPadding | Number / Array | 20 / [ 20, 40, 50, 20 ] | 0 | 画布上的四周留白宽度。 | +| animate | Boolean | true / false | false | 是否启用图的动画。 | +| modes | Object | {
  default: [ 'drag-node', 'drag-canvas' ]
} | null | 图上行为模式的集合。由于比较复杂,按需参见:[G6 中的 Mode](/zh/docs/manual/middle/states/mode) 教程。 | +| defaultNode | Object | {
  type: 'circle',
  color: '#000',
  style: {
    ......
  }
} | null | 节点默认的属性,包括节点的一般属性和样式属性(style)。 | +| defaultEdge | Object | {
  type: 'polyline',
  color: '#000',
  style: {
    ......
  }
} | null | 边默认的属性,包括边的一般属性和样式属性(style)。 | +| nodeStateStyles | Object | {
  hover: {
    ......
  },
  select: {
    ......
  }
} | null | 节点在除默认状态外,其他状态下的样式属性(style)。例如鼠标放置(hover)、选中(select)等状态。 | +| edgeStateStyles | Object | {
  hover: {
    ......
  },
  select: {
    ......
  }
} | null | 边在除默认状态外,其他状态下的样式属性(style)。例如鼠标放置(hover)、选中(select)等状态。 | + +## 完整代码 + +至此,完整代码如下: + +```html + + + + + Tutorial Demo + + +
+ + + + + + +``` + +⚠️ 注意:
若需更换数据,请替换  `'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'`  为新的数据文件地址。 diff --git a/packages/site/docs/manual/tutorial/layout.en.md b/packages/site/docs/manual/tutorial/layout.en.md new file mode 100644 index 0000000000..9b435e0e47 --- /dev/null +++ b/packages/site/docs/manual/tutorial/layout.en.md @@ -0,0 +1,182 @@ +--- +title: Utilizing Layout +order: 3 +--- + +When there is no node position information in the data, or the location information does not meet the requirements, layouts in G6 will help you to arrange the nodes. There are 9 layouts for general graph and 4 layouts for tree graph in G6: + +
**Layouts for General Graph:** + +- Random Layout: Randomizes the node positions; +- **Force Layout: Classical force-directed layout algorithm:** + + > In force-directed layout, items are simulated as physical particals with attractive forces and repulsive forces. Lead by the forces, the nodes will move to appropriate positions to balance the forces. It is suitable for describing the relationships between objects, e.g. relationships between person, computer networks. + +- Circular Layout: Arranges the nodes on a circle; +- Radial Layout: Arranges the nodes radially; +- MDS Layout: Multidimensional scaling; +- Fruchterman Layout: A kind of force-directed layout; +- Dagre Layout: Hierarchical layout; +- Concentric Layout: Arranges the nodes on concentrics, while the more important (measure with degree by default), the more center the node will be; +- Grid Layout: Arranges the nodes on the grid according with order (data order by default). + +**Layouts for TreeGraph:** + +- Dendrogram Layout; +- CompactBox Layout; +- Mindmap Layout; +- Indented Layout. + +For more information about each layout algorithm, please refer to [Graph Layout API](/en/docs/api/graphLayout/guide) or [TreeGraph Layout API](/en/docs/api/treeGraphLayout/guide). We will utilize Force Layout in the tutorial. + +img + +## Turnoff the fitView + +We used `fitView` to fit the graph to the canvas in the previous Tutorial. From now on, we turn it off by note the line of code below to make further improvements. + +```javascript +const graph = new G6.Graph({ + // ... + // fitView: true, + // fitViewPadding: [ 20, 40, 50, 20 ] +}); +``` + +## Default Layout + +When the `layout` is not assigned to graph instance: + +- If there is position information with `x` and `y` in node data, render with these information; +- If there is no position information in node data, arrange the nodes with Random Layout by default. + +## Configure the Layout + +It is very simple to configure a layout for a graph in G6. Just assign `layout` to the graph when instantiating. The following code configures the layout with `type: 'force'`, which is the classical force-directed layout algorithm. And set `preventOverlap: true` to avoid node overlappings. More configurations are described in: [Graph Layout API](/en/docs/api/graphLayout/guide) or [TreeGraph Layout API](/en/docs/api/treeGraphLayout/guide). + +```javascript +const graph = new G6.Graph({ + ... // Other configurations + layout: { // Object, layout configuration. random by default + type: 'force', // Force layout + preventOverlap: true, // Prevent node overlappings + // nodeSize: 30 // The size of nodes for collide detection. Since we have assigned sizes for each node to their data in last chapter, the nodeSize here is not required any more. + } +}); +``` + +The result: + +img + +The layout balances the forces by moving the nodes. But the nodes are too crowded to show the label clearly now. `linkDistance` in the configuration of force layout can be used to scale the edge length to keep a distance between two adjacent nodes: + +```javascript +const graph = new G6.Graph({ + // ... + layout: { + type: 'force', + preventOverlap: true, + linkDistance: 100, // The link distance is 100 + }, +}); +``` + +The result: + +img +
+ +> Transformation between different layouts and configurations are described in: [Layout Transformation](/en/docs/manual/middle/layout/layout-mechanism). + +**Tips:** 
The layout algorithm will be executed in `graph.render()`. + +## Complete Code + +```html + + + + + Tutorial Demo + + +
+ + + + + + +``` + +⚠️Attention: 
Replace the url `'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'` to change the data into yours. diff --git a/packages/site/docs/manual/tutorial/layout.zh.md b/packages/site/docs/manual/tutorial/layout.zh.md new file mode 100644 index 0000000000..53b0e16e12 --- /dev/null +++ b/packages/site/docs/manual/tutorial/layout.zh.md @@ -0,0 +1,183 @@ +--- +title: 使用图布局 Layout +order: 3 +--- + +当数据中没有节点位置信息,或者数据中的位置信息不满足需求时,需要借助一些布局算法对图进行布局。G6 提供了 9 种一般图的布局和 4 种树图的布局:
**一般图:** + +- Random Layout:随机布局; +- **Force Layout:经典力导向布局:** + + > 力导向布局:一个布局网络中,粒子与粒子之间具有引力和斥力,从初始的随机无序的布局不断演变,逐渐趋于平衡稳定的布局方式称之为力导向布局。适用于描述事物间关系,比如人物关系、计算机网络关系等。 + +- Circular Layout:环形布局; +- Radial Layout:辐射状布局; +- MDS Layout:高维数据降维算法布局; +- Fruchterman Layout:Fruchterman 布局,一种力导布局; +- Dagre Layout:层次布局; +- Concentric Layout:同心圆布局,将重要(默认以度数为度量)的节点放置在布局中心; +- Grid Layout:格子布局,将节点有序(默认是数据顺序)排列在格子上。 + +**树图布局:** + +- Dendrogram Layout:树状布局(叶子节点布局对齐到同一层); +- CompactBox Layout:紧凑树布局; +- Mindmap Layout:脑图布局; +- Indented Layout:缩进布局。 + +各种布局方法的具体介绍及其配置参见 [图布局 API](/zh/docs/api/graphLayout/guide) 或 [树图布局 API](/zh/docs/api/treeGraphLayout/guide)。本教程中,我们使用的是力导向布局 (Force Layout)。 + +img + +## 取消自动适配画布 + +我们在之前的教程里面,为了能够将超出画布的图适配到视野中,在实例化图时使用了 `fitView`  配置项。这节开始我们将会去掉这个特性。因为复杂的布局系统会打破适配的规则,反而会造成更多的困扰。让我们将相关的适配代码变为注释: + +```javascript +const graph = new G6.Graph({ + // ... + // fitView: true, + // fitViewPadding: [ 20, 40, 50, 20 ] +}); +``` + +## 默认布局 + +当实例化图时没有配置布局时: + +- 若数据中节点有位置信息(`x` 和 `y`),则按照数据的位置信息进行绘制; +- 若数据中节点没有位置信息,则默认使用 Random Layout 进行布局。 + +## 配置布局 + +G6 使用布局的方式非常简单,在图实例化的时候,加上 layout 配置即可。下面代码在实例化图时设置了布局方法为 `type: 'force'`,即经典力导向图布局。并设置了参数 `preventOverlap: true` ,表示希望节点不重叠。力导向布局的更多配置项参见:[Layout API](/zh/docs/api/graphLayout/force)。 + +```javascript +const graph = new G6.Graph({ + // ... // 其他配置项 + layout: { + // Object,可选,布局的方法及其配置项,默认为 random 布局。 + type: 'force', // 指定为力导向布局 + preventOverlap: true, // 防止节点重叠 + // nodeSize: 30 // 节点大小,用于算法中防止节点重叠时的碰撞检测。由于已经在上一节的元素配置中设置了每个节点的 size 属性,则不需要在此设置 nodeSize。 + }, +}); +``` + +结果如下: + +img + +如图所示,节点按照力导向布局自动平衡。但是图中的节点过于拥挤,边上的文字信息被挤占,无法看清。我们希望布局计算边的距离可以更长一些。G6 的力导向布局提供了  `linkDistance` 属性用来指定布局的时候边的距离长度: + +```javascript +const graph = new G6.Graph({ + // ... + layout: { + type: 'force', + preventOverlap: true, + linkDistance: 100, // 指定边距离为100 + }, +}); +``` + +结果如下: + +img +
+ +> 不同布局之间、相同布局不同参数允许动态切换和过渡,具体参见:[布局切换](/zh/docs/manual/middle/layout/layout-mechanism)。 + +提示:布局将在调用  `graph.render()` 时执行计算。 + +## 完整代码 + +至此,完整代码如下: + +```html + + + + + Tutorial Demo + + +
+ + + + + + +``` + +⚠️ 注意:
若需更换数据,请替换  `'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'`  为新的数据文件地址。 diff --git a/packages/site/docs/manual/tutorial/plugins.en.md b/packages/site/docs/manual/tutorial/plugins.en.md new file mode 100644 index 0000000000..50396a6ada --- /dev/null +++ b/packages/site/docs/manual/tutorial/plugins.en.md @@ -0,0 +1,199 @@ +--- +title: Plugins and Tools +order: 5 +--- + +To assist user to exploration a graph, G6 provides some tools, including plugins and interaction tools. + +Now, we are going to add minimap, grid, node tooltip, and edge tooltip to **Tutorial Demo**. + +## Plugin + +Apply plugins with three steps:
  Step 1: Import the plugin;
  Step 2: Instantiate the plugin;
  Step 3: Configure plugin onto the instance of Graph. + +### Minimap + +Minimap is a tool for quick preview and exploration on large graph. + +img + +Now, we are goint to configure a minimap to **Tutorial Demo**. + +**Expected Effect** + +img + +**Usage** + +In G6, Minimap is a plugin. You only need to instantiate it and configure the minimap onto the instance of Graph: + +```javascript +// Instantiate the Minimap +const minimap = new G6.Minimap({ + size: [100, 100], + className: 'minimap', + type: 'delegate', +}); + +// Instantiate the Graph +const graph = new G6.Graph({ + // ... // Other configurations + plugins: [minimap], // Configure minimap to the graph +}); +``` + +### Image Minimap + +The theory of the [Minimap](#minimap) is copy the graphics from the main graph onto the canvas of the minimap, which will lead to double rendering cost. To alleviate this problem, G6 provides another Image Minimap which is drawn by one `` instead of canvas. But you have to provide the `graphImg` which is the url or base64 string of the main graph's screenshot image, and the image is controlled by yourself totally, which means you might need to update the image by calling `minimap.updateGraphImg` manually when the content of the main graph is changed. + +**预期效果** + +img + +**Usage** + +`graphImg` is required when instantiating the Image Minimap. Update the `graphImg` for the minimap. We recommand you to update the graphImg when the main graph is updated. + +```javascript +// Instantiate the Image Minimap plugin +const imageMinimap = new G6.ImageMinimap({ + width: 200, + graphImg: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*eD7nT6tmYgAAAAAAAAAAAABkARQnAQ' +}); +const graph = new G6.Graph({ + //... Other configurations + plugins: [imageMinimap], // Configure imageMinimap +}); + +graph.data(data); +graph.render() + +... // Some operations which update the main graph +imageMinimap.updateGraphImg(img); // Update the minimap's image (generated by yourself) + +``` + +### Grid + +Grid helps to align the node while user drags it. + +**Expected Effect** + +img + +**Usage** Configure it onto the graph: + +```javascript +// const minimap = ... + +// Instantiate grid +const grid = new G6.Grid(); + +// Instantiate the Graph +const graph = new G6.Graph({ + // ... // Other configurations + plugins: [minimap, grid], // Configure grid onto the graph +}); +``` + +## Interaction Tool + +Interaction tools assist user interact a graph. Two steps are required:
  Step 1: Configure `modes` when instantiating a graph;
  Step 2: Define the styles for the tools. + +### Tooltip for Node + +Node tooltip shows the detail information when mouse enters a node. More configurations are in [Built-in tooltip](/en/docs/manual/middle/states/defaultBehavior#tooltip). + +**Expected Effect** + +img + +**Usage** + +Configure `'tooltip'` to `modes` when instantiating the Graph: + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + // ... + { + type: 'tooltip', // Tooltip + formatText(model) { + // The content of tooltip + const text = 'label: ' + model.label + '
class: ' + model.class; + return text; + }, + }, + ], + }, +}); +``` + +Actually, tooltip is a floating `
` tag of HTML. Thus, you can define the CSS style for it in ` + +``` + +### Tooltip for Edge + +Edge tooltip shows the detail information when mouse enters a edge. More configurations are in [Built-in edge-tooltip](/en/docs/manual/middle/states/defaultBehavior#edge-tooltip). + +**Expected Effect** + +img + +**Usage** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + // ... + { + type: 'tooltip', // Node tooltip + // ... + }, + { + type: 'edge-tooltip', // Edge tooltip + formatText(model) { + // The content of the edge tooltip + const text = + 'source: ' + + model.source + + '
target: ' + + model.target + + '
weight: ' + + model.weight; + return text; + }, + }, + ], + }, +}); +``` + +The same as node tooltip, edge-tooltip is a floating `
` tag in HTML. Thus, you can define the CSS style for it in ` + +``` + +### edge-tooltip 边提示框 + +边提示框可以用在边的详细信息的展示。当鼠标滑过边时,显示一个浮层告知边的详细信息。更多配置参见 [内置交互 edge-tooltip](/zh/docs/manual/middle/states/defaultBehavior#edge-tooltip)。 + +**预期效果** + +img + +**使用方法** + +```javascript +const graph = new G6.Graph({ + modes: { + default: [ + // ... + { + type: 'tooltip', // 节点提示框 + // ... + }, + { + type: 'edge-tooltip', // 边提示框 + formatText(model) { + // 边提示框文本内容 + const text = + 'source: ' + + model.source + + '
target: ' + + model.target + + '
weight: ' + + model.weight; + return text; + }, + }, + ], + }, +}); +``` + +与 tooltip 相同,edge-tooltip 是一个悬浮的 `
` 标签,可以使用与 tooltip 相同的方法设置其悬浮框的样式。 + +## 完整代码 + +至此,**Tutorial 案例** 完成,完整代码见:Tutorial 案例代码。 + +⚠️ 注意:
若需更换数据,请替换  `'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'`  为新的数据文件地址。 diff --git a/packages/site/docs/manual/tutorial/preface.en.md b/packages/site/docs/manual/tutorial/preface.en.md new file mode 100644 index 0000000000..ac36776483 --- /dev/null +++ b/packages/site/docs/manual/tutorial/preface.en.md @@ -0,0 +1,63 @@ +--- +title: Preface +order: 0 +--- + +## What is G6 + +[G6](https://github.com/antvis/g6) is a graph visualization engine, which provides a set of basic mechanisms, including rendering, layout, analysis, interaction, animation, and other auxiliary tools. G6 aims to simplify the complex relationships, and help people to obtain the insight of relational data. + +## Introduction to The Tutorial + +We will build a simple graph visualization during this tutorial. We call this demo **Tutorial Demo** in the following Tutorial. Complete Code. + +img + +
Tutorial Demo result
+ +## Preface + +This tutorial introduces how to combine creating and rendering a graph, configuring items, layout, interaction, animation, and other tools to complete the final **Tutorial Demo**. The readers will learn the basic and key concepts of G6 in this tutorial. + +There are 6 chapters in this tutorial: + +- Create & Render a Graph +- Items & Their Configurations +- Utilize Layout +- Interaction Behaviors +- Plugins & Tools +- \*Animation (not Required) + +**Tips:** 
This tutorial is designed for people who prefer to learn by doing. If you prefer learning concepts from the ground up, check out our [Key Concepts](/en/docs/manual/middle/graph). You might find this tutorial and the guide complementary to each other. + +## Prerequisites + +It doesn't matter if you're not familiar with G6. But we’ll assume that you have some familiarity with HTML and JavaScript, but you should be able to follow along even if you’re coming from a different programming language. You might be tempted to skip if you already know the basics of G6. + +## Setup + +Any code editor works for this Tutorial. We recommend to run this demo in Chrome. In this tutorial, we import G6 V3.7.1 by CDN. We simplified the code to make it easy. For other environments, please refer to the installation guide in [Getting Started](/en/docs/manual/getting-started). + +New an index.html file, and add the code below: + +```html + + + + + Tutorial Demo + + + + + + + + + + +``` + +Open index.html with your browser. It is success if there is the version number of G6 printed in the console. diff --git a/packages/site/docs/manual/tutorial/preface.zh.md b/packages/site/docs/manual/tutorial/preface.zh.md new file mode 100644 index 0000000000..a9908bfb60 --- /dev/null +++ b/packages/site/docs/manual/tutorial/preface.zh.md @@ -0,0 +1,64 @@ +--- +title: 前言 +order: 0 +--- + +## 什么是 G6 + +G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等基础的图可视化能力。旨在让关系变得透明,简单。让用户获得关系数据的 Insight。 + +## 入门教程简介 + +我们在本入门教程中将会完成一个如下图所示简单的图可视化,我们将在后文中称其为 **Tutorial 案例**,完整代码。 + +img + +
Tutorial 案例 效果图
+ +## 前言 + +我们将会通过本入门教程完成包含图的创建、渲染、元素的配置、布局、交互、动画、工具的最终的  **Tutorial 案例**。在这部分教学中,读者将会学习到 G6 中基础和核心的功能。掌握该入门教程内容后,可以帮助读者初步理解 G6 并为深度理解 G6 打好基础。 + +该入门教程将会划分为以下几个章节: + +- 快速上手 +- 创建图 +- 元素及其配置 +- 使用图布局 Layout +- 图的交互 Behavior +- 插件 & 工具 +- \*动画(选读) + +`提示` 
该入门教程是为希望“边学边做”的读者设计的。如果您更希望从底层概念开始学习 G6,您可以参见:[核心概念](/zh/docs/manual/middle/graph)。 + +## 基础知识 + +本教程展示如何使用 G6 创建一个完整的图可视化应用。在学习之前,我们假设读者对 HTML 和 JavaScript 有所了解,但并不要求对 G6 有任何的基础。如果读者对 G6 的基本内容已经熟知,可以适当跳过部分内容,有针对性地阅读重要的知识点。 + +## 环境准备 + +建议使用新版的 Chrome 浏览器作为运行环境,用任意的代码编辑器进行代码的编写即可。本教程默认采用 CDN 的方式直接引入 G6 类库,引入的版本是 3.7.1,此版本很多特性会大大简化我们的代码。如果希望在其他环境尝试本教程的学习,读者可以参考 [快速上手](/zh/docs/manual/getting-started) 中的安装配置部分。 + +新建 index.html 文件,并添加如下代码: + +```html + + + + + Tutorial Demo + + + + + + + + + + +``` + +使用浏览器打开 index.html 文件,打开控制台,可以看到 G6 的版本号,说明 G6 已成功引入。 diff --git a/packages/site/examples/algorithm/algoDemos/API.en.md b/packages/site/examples/algorithm/algoDemos/API.en.md new file mode 100644 index 0000000000..0300da1f54 --- /dev/null +++ b/packages/site/examples/algorithm/algoDemos/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/algorithm/algoDemos/API.zh.md b/packages/site/examples/algorithm/algoDemos/API.zh.md new file mode 100644 index 0000000000..2b62731c9c --- /dev/null +++ b/packages/site/examples/algorithm/algoDemos/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/algorithm/algoDemos/demo/colorSets.js b/packages/site/examples/algorithm/algoDemos/demo/colorSets.js new file mode 100644 index 0000000000..294b8ba0dd --- /dev/null +++ b/packages/site/examples/algorithm/algoDemos/demo/colorSets.js @@ -0,0 +1,138 @@ +import G6 from '@antv/g6'; + +const tipDiv = document.createElement('div'); +tipDiv.innerHTML = `These are the suggested categorical color palette in G6. Input the subject colors, 'getColorSetsBySubjectColors' generate a set of colors for different state styles. You can modify the 'subjectColors' in the code to try your subject color. And it also provide default and dark theme with different background colors.
下面是 G6 的推荐色板。输入主题色,'getColorSetsBySubjectColors' 将会输出与该主题色相关的一系列状态颜色。还可以指定叠加背景色的同时,指定主题是 default(亮色)或 dark(暗色)`; +document.getElementById('container').appendChild(tipDiv); + +// Generate color sets according to subject colors +const subjectColors = [ + '#5F95FF', // blue + '#61DDAA', + '#65789B', + '#F6BD16', + '#7262FD', + '#78D3F8', + '#9661BC', + '#F6903D', + '#008685', + '#F08BB4', +]; +const backColor = '#fff'; +const theme = 'default'; +const disableColor = '#777'; +const colorSets = G6.Util.getColorSetsBySubjectColors( + subjectColors, + backColor, + theme, + disableColor, +); + +const data = { nodes: [] }; + +subjectColors.forEach((color, i) => { + data.nodes.push({ + id: `node-${color}`, + label: color, + labelCfg: { + position: 'bottom', + }, + style: { + fill: colorSets[i].mainFill, + stroke: colorSets[i].mainStroke, + }, + stateStyles: { + active: { + fill: colorSets[i].activeFill, + stroke: colorSets[i].activeStroke, + shadowColor: colorSets[i].activeStroke, + }, + inactive: { + fill: colorSets[i].inactiveFill, + stroke: colorSets[i].inactiveStroke, + }, + selected: { + fill: colorSets[i].selectedFill, + stroke: colorSets[i].selectedStroke, + shadowColor: colorSets[i].selectedStroke, + }, + highlight: { + fill: colorSets[i].highlightFill, + stroke: colorSets[i].highlightStroke, + }, + disable: { + fill: colorSets[i].disableFill, + stroke: colorSets[i].disableStroke, + }, + }, + }); +}); + +// data.nodes.push({ +// id: `node-custom`, +// label: `customColor` +// }) + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 160; + +G6.Global.nodeStateStyles = {}; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: ['drag-canvas', 'drag-node', 'zoom-canvas'], + }, + fitView: true, + layout: { + type: 'grid', + }, + defaultNode: { + type: 'circle', + labelCfg: { + position: 'bottom', + }, + }, + nodeStateStyles: { + active: { + fill: colorSets[1].activeFill, + stroke: colorSets[1].activeStroke, + }, + }, +}); +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (e) => { + graph.getNodes().forEach((node) => { + graph.setItemState(node, 'active', false); + }); + graph.setItemState(e.item, 'active', true); +}); + +graph.on('node:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); + +graph.on('node:click', (e) => { + graph.getNodes().forEach((node) => { + graph.setItemState(node, 'selected', false); + }); + graph.setItemState(e.item, 'selected', true); +}); + +graph.on('canvas:click', (e) => { + graph.getNodes().forEach((node) => { + graph.setItemState(node, 'selected', false); + }); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 160); + }; diff --git a/packages/site/examples/algorithm/algoDemos/demo/gaddi.js b/packages/site/examples/algorithm/algoDemos/demo/gaddi.js new file mode 100644 index 0000000000..8652541aac --- /dev/null +++ b/packages/site/examples/algorithm/algoDemos/demo/gaddi.js @@ -0,0 +1,231 @@ +import G6 from '@antv/g6'; + +const { GADDI } = G6.Algorithm; + +const colors = [ + '#5F95FF', // blue + '#61DDAA', + '#65789B', + '#F6BD16', + '#7262FD', + '#78D3F8', + '#9661BC', + '#F6903D', + '#008685', + '#F08BB4', +]; +const colorSets = G6.Util.getColorSetsBySubjectColors(colors, '#fff', 'default', '#777'); + + +const data = {"nodes":[{"id":"0","label":"0","x":217.86420885505296,"y":114.28884847734246,"cluster":"nodeType-0"},{"id":"1","label":"1","x":113.58792632732174,"y":25.785315472468127,"cluster":"nodeType-1"},{"id":"2","label":"2","x":179.59682070334452,"y":38.87850516662148,"cluster":"nodeType-2"},{"id":"3","label":"3","x":204.20226244579672,"y":-5.33508012158744,"cluster":"nodeType-0"},{"id":"4","label":"4","x":308.74171746938134,"y":10.554714934145961,"cluster":"nodeType-1"},{"id":"5","label":"5","x":341.99836557519745,"y":48.30310308747067,"cluster":"nodeType-2"},{"id":"6","label":"6","x":376.62085426957793,"y":-10.286527884559707,"cluster":"nodeType-0"},{"id":"7","label":"7","x":254.42832676365109,"y":62.51510456243093,"cluster":"nodeType-1"},{"id":"8","label":"8","x":139.32504277036227,"y":135.40373347960067,"cluster":"nodeType-2"},{"id":"9","label":"9","x":245.24964464688256,"y":166.03052347036282,"cluster":"nodeType-0"},{"id":"10","label":"10","x":250.33418485239127,"y":227.6830390965898,"cluster":"nodeType-1"},{"id":"11","label":"11","x":176.79897890681895,"y":235.80422881594598,"cluster":"nodeType-2"},{"id":"12","label":"12","x":331.32653587613936,"y":158.8731219947074,"cluster":"nodeType-0"},{"id":"13","label":"13","x":292.8309736938475,"y":106.794007203338,"cluster":"nodeType-1"},{"id":"14","label":"14","x":175.6768625027686,"y":179.3951732038534,"cluster":"nodeType-2"},{"id":"15","label":"15","x":83.26466725029928,"y":96.50488885477502,"cluster":"nodeType-0"},{"id":"16","label":"16","x":318.09206038019914,"y":241.38853020289633,"cluster":"nodeType-1"},{"id":"17","label":"17","x":397.2096443837184,"y":251.11323425020436,"cluster":"nodeType-2"},{"id":"18","label":"18","x":396.9307017482416,"y":305.79619379078093,"cluster":"nodeType-0"},{"id":"19","label":"19","x":323.19884694585255,"y":354.7889914141042,"cluster":"nodeType-1"},{"id":"20","label":"20","x":334.7320270398703,"y":289.0437229111331,"cluster":"nodeType-2"},{"id":"21","label":"21","x":276.9512819725375,"y":284.9808595526045,"cluster":"nodeType-0"},{"id":"22","label":"22","x":235.086300958203,"y":348.23515388308186,"cluster":"nodeType-1"},{"id":"23","label":"23","x":263.36101059763547,"y":442.4976175844671,"cluster":"nodeType-2"},{"id":"24","label":"24","x":195.01142425098806,"y":287.081197770762,"cluster":"nodeType-0"},{"id":"25","label":"25","x":120.39188452563401,"y":337.8524937873151,"cluster":"nodeType-1"},{"id":"26","label":"26","x":109.74700930888443,"y":407.5171194512714,"cluster":"nodeType-2"},{"id":"27","label":"27","x":328.18870325041166,"y":527.9726877901181,"cluster":"nodeType-0"},{"id":"28","label":"28","x":221.745767731853,"y":400.38906283199213,"cluster":"nodeType-1"},{"id":"29","label":"29","x":243.98258526664858,"y":553.9974335527247,"cluster":"nodeType-2"},{"id":"30","label":"30","x":179.30103487685315,"y":433.1126685019916,"cluster":"nodeType-0"},{"id":"31","label":"31","x":221.65781762707928,"y":474.10779931544715,"cluster":"nodeType-1"},{"id":"32","label":"32","x":349.13614579528684,"y":422.65934738145364,"cluster":"nodeType-2"},{"id":"33","label":"33","x":300.5432325124777,"y":401.2786277319002,"cluster":"nodeType-0"}],"edges":[{"source":"0","target":"1","cluster":"edgeType-0"},{"source":"0","target":"2","cluster":"edgeType-1"},{"source":"0","target":"3","cluster":"edgeType-2"},{"source":"0","target":"4","cluster":"edgeType-0"},{"source":"0","target":"5","cluster":"edgeType-1"},{"source":"0","target":"7","cluster":"edgeType-2"},{"source":"0","target":"8","cluster":"edgeType-0"},{"source":"0","target":"9","cluster":"edgeType-1"},{"source":"0","target":"10","cluster":"edgeType-2"},{"source":"0","target":"11","cluster":"edgeType-0"},{"source":"0","target":"13","cluster":"edgeType-1"},{"source":"0","target":"14","cluster":"edgeType-2"},{"source":"0","target":"15","cluster":"edgeType-0"},{"source":"0","target":"16","cluster":"edgeType-1"},{"source":"2","target":"3","cluster":"edgeType-2"},{"source":"4","target":"5","cluster":"edgeType-0"},{"source":"4","target":"6","cluster":"edgeType-1"},{"source":"5","target":"6","cluster":"edgeType-2"},{"source":"7","target":"13","cluster":"edgeType-0"},{"source":"8","target":"14","cluster":"edgeType-1"},{"source":"9","target":"10","cluster":"edgeType-2"},{"source":"10","target":"22","cluster":"edgeType-0"},{"source":"10","target":"14","cluster":"edgeType-1"},{"source":"10","target":"12","cluster":"edgeType-2"},{"source":"10","target":"24","cluster":"edgeType-0"},{"source":"10","target":"21","cluster":"edgeType-1"},{"source":"10","target":"20","cluster":"edgeType-2"},{"source":"11","target":"24","cluster":"edgeType-0"},{"source":"11","target":"22","cluster":"edgeType-1"},{"source":"11","target":"14","cluster":"edgeType-2"},{"source":"12","target":"13","cluster":"edgeType-0"},{"source":"16","target":"17","cluster":"edgeType-1"},{"source":"16","target":"18","cluster":"edgeType-2"},{"source":"16","target":"21","cluster":"edgeType-0"},{"source":"16","target":"22","cluster":"edgeType-1"},{"source":"17","target":"18","cluster":"edgeType-2"},{"source":"17","target":"20","cluster":"edgeType-0"},{"source":"18","target":"19","cluster":"edgeType-1"},{"source":"19","target":"20","cluster":"edgeType-2"},{"source":"19","target":"33","cluster":"edgeType-0"},{"source":"19","target":"22","cluster":"edgeType-1"},{"source":"19","target":"23","cluster":"edgeType-2"},{"source":"20","target":"21","cluster":"edgeType-0"},{"source":"21","target":"22","cluster":"edgeType-1"},{"source":"22","target":"24","cluster":"edgeType-2"},{"source":"22","target":"25","cluster":"edgeType-0"},{"source":"22","target":"26","cluster":"edgeType-1"},{"source":"22","target":"23","cluster":"edgeType-2"},{"source":"22","target":"28","cluster":"edgeType-0"},{"source":"22","target":"30","cluster":"edgeType-1"},{"source":"22","target":"31","cluster":"edgeType-2"},{"source":"22","target":"32","cluster":"edgeType-0"},{"source":"22","target":"33","cluster":"edgeType-1"},{"source":"23","target":"28","cluster":"edgeType-2"},{"source":"23","target":"27","cluster":"edgeType-0"},{"source":"23","target":"29","cluster":"edgeType-1"},{"source":"23","target":"30","cluster":"edgeType-2"},{"source":"23","target":"31","cluster":"edgeType-0"},{"source":"23","target":"33","cluster":"edgeType-1"},{"source":"32","target":"33","cluster":"edgeType-2"}]}; +const button = document.createElement('button'); +button.innerHTML = `Click Here to Match 点此开始匹配`; +document.getElementById('container').appendChild(button); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + modes: { + default: ['drag-canvas', 'drag-node', 'zoom-canvas'], + }, +}); + +const legendDataMap = {} +data.nodes.forEach((node, i) => { + const colorSet = colorSets[(+node.cluster.split('-')[1])]; + node.style = { + fill: colorSet.mainFill, + stroke: colorSet.mainStroke, + } + if (!legendDataMap[node.cluster]) { + legendDataMap[node.cluster] = { + shape: 'circle', + r: 6, + text: node.cluster, + fill: colorSet.mainFill, + stroke: colorSet.mainStroke, + } + } +}) +data.edges.forEach((edge, i) => { + const colorSet = colorSets[(+edge.cluster.split('-')[1])]; + edge.style = { + stroke: colorSet.mainStroke, + lineWidth: 2, + opacity: 0.3, + endArrow: true + } + + if (!legendDataMap[edge.cluster]) { + legendDataMap[edge.cluster] = { + shape: 'line', + text: edge.cluster, + stroke: colorSet.mainStroke, + lineWidth: 2, + opacity: 0.3, + endArrow: true + } + } +}) +graph.data(data); +graph.render(); + +// pattern graph data +const pattern = { + nodes: [{ + id: 'pn0', + cluster: 'nodeType-0' + },{ + id: 'pn1', + cluster: 'nodeType-1' + },{ + id: 'pn2', + cluster: 'nodeType-2' + }], + edges: [ + { source: 'pn1', target: 'pn0', cluster: 'edgeType-1' }, + { source: 'pn1', target: 'pn2', cluster: 'edgeType-0' }, + { source: 'pn2', target: 'pn0', cluster: 'edgeType-2' }, + ] +} + + +// draw pattern graph +pattern.nodes.forEach(pNode => { + const colorSet = colorSets[(+pNode.cluster.split('-')[1])]; + pNode.style = { + fill: colorSet.mainFill, + stroke: colorSet.mainStroke, + } +}); +pattern.edges.forEach(pEdge => { + const colorSet = colorSets[(+pEdge.cluster.split('-')[1])]; + pEdge.style = { + stroke: colorSet.mainStroke, + } +}); +const patternGraphWidth = Math.max(width / 5, 100); +const patternGraphHeight = Math.max(height / 5, 100); +const patternGraph = new G6.Graph({ + container: 'container', + width: patternGraphWidth, + height: patternGraphHeight, + fitView: true, + defaultEdge: { + style: { + endArrow: true + } + }, + layout: { + type: 'circular' + } +}) +patternGraph.data(pattern); +patternGraph.render(); +const patternCanvas = patternGraph.get('canvas'); +const patternCanvasEl = patternCanvas.get('el'); +patternCanvasEl.style.position = 'absolute'; +patternCanvasEl.style.top = '50px'; +patternCanvasEl.style.left = '15px'; +patternCanvasEl.style.backgroundColor = '#eee'; +patternCanvasEl.style.opacity = 0.7; +patternCanvas.addShape('text', { + attrs: { + text: 'Pattern', + x: patternGraphWidth - 55, + y: patternGraphHeight - 10, + fill: '#000', + fontWeight: 500 + } +}) + + +// draw legend +const legendGroup = graph.get('canvas').addGroup({ + attrs: { + opacity: 0.7 + } +}); +const legendBack = legendGroup.addShape('rect', { + attrs: { + height: 10, + width: 10, + x: 5, + y: -10, + fill: '#eee' + } +}) +legendGroup.addShape('text', { + attrs: { + text: 'Legend', + x: 40, + y: 5, + fill: '#000', + fontWeight: 500 + } +}) +Object.keys(legendDataMap).forEach((cluster, i) => { + const lData = legendDataMap[cluster]; + legendGroup.addShape(lData.shape, { + attrs: { + x: 20, + y: 20 + i * 25, + x1: 13, + x2: 28, + y1: 20 + i * 25, + y2: 20 + i * 25, + ...lData + } + }) + legendGroup.addShape('text', { + attrs: { + x: 35, + y: 20 + i * 25, + text: lData.text, + fill: '#000', + textBaseline: 'middle' + } + }) +}) +const legendBBox = legendGroup.getBBox(); +legendBack.attr({ + width: legendBBox.width + 10, + height: legendBBox.height + 10 +}) +legendGroup.setMatrix([1, 0, 0, 0, 1, 0, -10, patternGraphHeight + 40, 1]) + + +// click the button to run GADDI graph pattern matching +// and the result will be marked with hull +button.addEventListener('click', (e) => { + const matches = GADDI( + data, + pattern, + true, + undefined, + undefined, + 'cluster', + 'cluster' + ); + matches.forEach((match, i) => { + graph.createHull({ + id: `match-${i}`, + members: match.nodes.map(node => node.id) + }) + }); + button.innerHTML = `The results are marked with hulls 结果已用轮廓标记`; + button.disabled = true; +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/algorithm/algoDemos/demo/labelPropagation.js b/packages/site/examples/algorithm/algoDemos/demo/labelPropagation.js new file mode 100644 index 0000000000..90a4d00550 --- /dev/null +++ b/packages/site/examples/algorithm/algoDemos/demo/labelPropagation.js @@ -0,0 +1,67 @@ +import G6 from '@antv/g6'; + +const { labelPropagation } = G6.Algorithm; + +const button = document.createElement('button'); +button.innerHTML = `Click Here to Clustering 点此自动聚类`; +document.getElementById('container').appendChild(button); + +// Generate color sets according to subject colors +const subjectColors = [ + '#5F95FF', // blue + '#61DDAA', + '#65789B', + '#F6BD16', + '#7262FD', + '#78D3F8', + '#9661BC', + '#F6903D', + '#008685', + '#F08BB4', +]; +const colorSets = G6.Util.getColorSetsBySubjectColors(subjectColors, '#fff', 'default', '#777'); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: ['drag-canvas', 'drag-node', 'zoom-canvas'], + }, + layout: { + type: 'gForce', + minMovement: 0.1, + }, +}); +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + + button.addEventListener('click', (e) => { + const clusteredData = labelPropagation(data, false); + clusteredData.clusters.forEach((cluster, i) => { + const colorSet = colorSets[i % colorSets.length]; + cluster.nodes.forEach((node) => { + node.style = { + fill: colorSet.mainFill, + stroke: colorSet.mainStroke, + }; + }); + }); + graph.refresh(); + }); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/algorithm/algoDemos/demo/louvain.js b/packages/site/examples/algorithm/algoDemos/demo/louvain.js new file mode 100644 index 0000000000..79e0be306f --- /dev/null +++ b/packages/site/examples/algorithm/algoDemos/demo/louvain.js @@ -0,0 +1,67 @@ +import G6 from '@antv/g6'; + +const { louvain } = G6.Algorithm; + +const button = document.createElement('button'); +button.innerHTML = `Click Here to Clustering 点此自动聚类`; +document.getElementById('container').appendChild(button); + +// Generate color sets according to subject colors +const subjectColors = [ + '#5F95FF', // blue + '#61DDAA', + '#65789B', + '#F6BD16', + '#7262FD', + '#78D3F8', + '#9661BC', + '#F6903D', + '#008685', + '#F08BB4', +]; +const colorSets = G6.Util.getColorSetsBySubjectColors(subjectColors, '#fff', 'default', '#777'); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: ['drag-canvas', 'drag-node', 'zoom-canvas'], + }, + layout: { + type: 'gForce', + minMovement: 0.1, + }, +}); +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + + button.addEventListener('click', (e) => { + const clusteredData = louvain(data, false); + + clusteredData.clusters.forEach((cluster, i) => { + const colorSet = colorSets[i % colorSets.length]; + cluster.nodes.forEach((node) => { + const model = graph.findById(node.id).getModel(); + model.style.fill = colorSet.mainFill + model.style.stroke = colorSet.mainStroke + }); + }); + graph.refresh(); + }); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/algorithm/algoDemos/demo/meta.json b/packages/site/examples/algorithm/algoDemos/demo/meta.json new file mode 100644 index 0000000000..315739efa3 --- /dev/null +++ b/packages/site/examples/algorithm/algoDemos/demo/meta.json @@ -0,0 +1,48 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "gaddi.js", + "title": { + "zh": "图模式匹配", + "en": "Graph Pattern Matching" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ucUBSoDw2TwAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "shortestPath.js", + "title": { + "zh": "最短路径", + "en": "Shortest Path" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*pBV2T6BU6g8AAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "labelPropagation.js", + "title": { + "zh": "LP 自动聚类", + "en": "Label Propagation Clustering" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*NXkTSbPhEA0AAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "louvain.js", + "title": { + "zh": "LOUVAIN 自动聚类", + "en": "LOUVAIN Clustering" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*OVgcSa33xBYAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "colorSets.js", + "title": { + "zh": "自动计算色板", + "en": "Color Sets Compoting" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*hS7MTKlo6LsAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/algorithm/algoDemos/demo/shortestPath.js b/packages/site/examples/algorithm/algoDemos/demo/shortestPath.js new file mode 100644 index 0000000000..f44ff58fb5 --- /dev/null +++ b/packages/site/examples/algorithm/algoDemos/demo/shortestPath.js @@ -0,0 +1,92 @@ +import G6 from '@antv/g6'; + +const tipDiv = document.createElement('div'); +tipDiv.innerHTML = `Press 'shift' and click two nodes to select begin and end nodes. 按住 'shift' 并点选两个节点作为起点和终点。`; +document.getElementById('container').appendChild(tipDiv); + +const button = document.createElement('button'); +button.innerHTML = `查看最短路径`; +document.getElementById('container').appendChild(button); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 40; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: ['click-select', 'drag-canvas', 'drag-node', 'zoom-canvas'], + }, + fitView: true, +}); +fetch('https://gw.alipayobjects.com/os/bmw-prod/b0ca4b15-bd0c-43ec-ae41-c810374a1d55.json') + .then((res) => res.json()) + .then((data) => { + const clearStates = () => { + graph.getNodes().forEach((node) => { + graph.clearItemStates(node); + }); + graph.getEdges().forEach((edge) => { + graph.clearItemStates(edge); + }); + }; + + graph.on('canvas:click', (e) => { + clearStates(); + }); + + graph.data(data); + graph.render(); + + button.addEventListener('click', (e) => { + const selectedNodes = graph.findAllByState('node', 'selected'); + if (selectedNodes.length !== 2) { + alert('Please select TWO nodes!\n\r请选择有且两个节点!'); + return; + } + clearStates(); + const { findShortestPath } = G6.Algorithm; + // path 为其中一条最短路径,allPath 为所有的最短路径 + const { path, allPath } = findShortestPath( + data, + selectedNodes[0].getID(), + selectedNodes[1].getID(), + ); + + const pathNodeMap = {}; + path.forEach((id) => { + const pathNode = graph.findById(id); + pathNode.toFront(); + graph.setItemState(pathNode, 'highlight', true); + pathNodeMap[id] = true; + }); + graph.getEdges().forEach((edge) => { + const edgeModel = edge.getModel(); + const source = edgeModel.source; + const target = edgeModel.target; + const sourceInPathIdx = path.indexOf(source); + const targetInPathIdx = path.indexOf(target); + if (sourceInPathIdx === -1 || targetInPathIdx === -1) return; + if (Math.abs(sourceInPathIdx - targetInPathIdx) === 1) { + graph.setItemState(edge, 'highlight', true); + } else { + graph.setItemState(edge, 'inactive', true); + } + }); + graph.getNodes().forEach((node) => { + if (!pathNodeMap[node.getID()]) { + graph.setItemState(node, 'inactive', true); + } + }); + }); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 40); + }; diff --git a/packages/site/examples/algorithm/algoDemos/index.en.md b/packages/site/examples/algorithm/algoDemos/index.en.md new file mode 100644 index 0000000000..41e256f7c2 --- /dev/null +++ b/packages/site/examples/algorithm/algoDemos/index.en.md @@ -0,0 +1,10 @@ +--- +title: Shortest Path +order: 1 +--- + +G6 provides a set of algorithms for graph analysis. + +## Usage + +Fore more information, please refer to the API docs [Algorithm](/zh/docs/api/Algorithm). diff --git a/packages/site/examples/algorithm/algoDemos/index.zh.md b/packages/site/examples/algorithm/algoDemos/index.zh.md new file mode 100644 index 0000000000..c03cbae2bb --- /dev/null +++ b/packages/site/examples/algorithm/algoDemos/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 算法示例 +order: 1 +--- + +G6 提供了一系列图算法,为分析复杂数据提供强有力辅助。 + +## 使用指南 + +更多信息请参见 API 文档 [算法](/zh/docs/api/Algorithm)。 diff --git a/packages/site/examples/case/graphDemos/demo/christmasBubbles.js b/packages/site/examples/case/graphDemos/demo/christmasBubbles.js new file mode 100644 index 0000000000..87a0d459a3 --- /dev/null +++ b/packages/site/examples/case/graphDemos/demo/christmasBubbles.js @@ -0,0 +1,681 @@ +import G6 from '@antv/g6'; + +// 实际开发中把 window.AntVUtil 换成从 @antv/util 引入的相关模块 +// replace window.AntVUtil.isObject with +// import { mix } from '@antv/util'; +const { mix } = window.AntVUtil; + +let showNodes = []; +let showEdges = []; +let curShowNodes = []; +let curShowEdges = []; +let nodes = []; +let edges = []; +let nodeMap = new Map(); +let edgesMap = new Map(); +let curShowNodesMap = new Map(); +let highlighting = false; +let currentFocus; +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const LIMIT_OVERFLOW_WIDTH = width; +const LIMIT_OVERFLOW_HEIGHT = height; + +const mapNodeSizeAndFontSize = (nodes, propertyName, visualRange) => { + let minp = 9999999999; + let maxp = -9999999999; + nodes.forEach((node) => { + const propertyValue = node[propertyName] > 5000 ? 5000 : node[propertyName]; + minp = propertyValue < minp ? propertyValue : minp; + maxp = propertyValue > maxp ? propertyValue : maxp; + }); + const rangepLength = maxp - minp; + const rangevLength = visualRange[1] - visualRange[0]; + nodes.forEach((node) => { + const propertyValue = node[propertyName] > 5000 ? 5000 : node[propertyName]; + node.size = ((propertyValue - minp) / rangepLength) * rangevLength + visualRange[0]; + if (node.labelCfg && node.labelCfg.style) { + const textLength = node.label.length; + let fontSize = (1.3 * node.size) / textLength; + if (fontSize < 11) fontSize = 11; + node.labelCfg.style.fontSize = fontSize; + } + }); +}; + +const Colors = { noun: '#588c73', adj: '#f2e394', v: '#d96459', adv: '#f2ae72' }; + +let graph; +const layoutCfg = { + type: 'force', + nodeSize: (d) => { + return d.size / 2 + 5; + }, + nodeStrength: 2500, + collideStrength: 0.8, + alphaDecay: 0.01, + preventOverlap: true, + onTick: () => { + const nodeItems = graph.getNodes(); + const height = graph.get('height'); + const width = graph.get('width'); + const padding = 10; + nodeItems.forEach((item) => { + const model = item.getModel(); + if (model.x > width - padding) model.x = width - padding; + else if (model.x < padding) model.x = padding; + + if (model.y > height - padding) model.y = height - padding; + else if (model.y < padding) model.y = padding; + }); + }, +}; + +// G6.registerBehavior('double-finger-drag-canvas', { +// getEvents: function getEvents() { +// return { +// wheel: 'onWheel' +// }; +// }, + +// onWheel: ev => { +// if (ev.ctrlKey) { +// const canvas = graph.get('canvas'); +// const point = canvas.getPointByClient(ev.clientX, ev.clientY); +// let ratio = graph.getZoom(); +// if (ev.wheelDelta > 0) { +// ratio = ratio + ratio * 0.05; +// } else { +// ratio = ratio - ratio * 0.05; +// } +// graph.zoomTo(ratio, { +// x: point.x, +// y: point.y +// }); +// } else { +// const x = ev.deltaX || ev.movementX; +// const y = ev.deltaY || ev.movementY; +// translate(x, y); +// } +// ev.preventDefault(); +// } +// }); + +G6.registerNode( + 'bubble', + { + drawShape(cfg, group) { + const self = this; + const r = cfg.size / 2; + // a circle by path + const path = [ + ['M', -r, 0], + ['C', -r, r / 2, -r / 2, r, 0, r], + ['C', r / 2, r, r, r / 2, r, 0], + ['C', r, -r / 2, r / 2, -r, 0, -r], + ['C', -r / 2, -r, -r, -r / 2, -r, 0], + ['Z'], + ]; + const keyShape = group.addShape('path', { + attrs: { + x: 0, + y: 0, + path, + fill: cfg.color || 'steelblue', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + + const mask = group.addShape('path', { + attrs: { + x: 0, + y: 0, + path, + opacity: 0.25, + fill: cfg.color || 'steelblue', + shadowColor: cfg.color, // cfg.color.split(' ')[2].substr(2), + shadowBlur: 40, + shadowOffsetX: 0, + shadowOffsetY: 30, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'mask-shape', + }); + + const spNum = 10; // split points number + const directions = [], + rs = []; + self.changeDirections(spNum, directions); + for (let i = 0; i < spNum; i++) { + const rr = r + directions[i] * ((Math.random() * r) / 1000); // +-r/6, the sign according to the directions + if (rs[i] < 0.97 * r) rs[i] = 0.97 * r; + else if (rs[i] > 1.03 * r) rs[i] = 1.03 * r; + rs.push(rr); + } + keyShape.animate( + () => { + const path = self.getBubblePath(r, spNum, directions, rs); + return { path }; + }, + { + repeat: true, + duration: 1000, + }, + ); + + const directions2 = [], + rs2 = []; + self.changeDirections(spNum, directions2); + for (let i = 0; i < spNum; i++) { + const rr = r + directions2[i] * ((Math.random() * r) / 1000); // +-r/6, the sign according to the directions + if (rs2[i] < 0.97 * r) rs2[i] = 0.97 * r; + else if (rs2[i] > 1.03 * r) rs2[i] = 1.03 * r; + rs2.push(rr); + } + mask.animate( + () => { + const path = self.getBubblePath(r, spNum, directions2, rs2); + return { path }; + }, + { + repeat: true, + duration: 1000, + }, + ); + return keyShape; + }, + changeDirections(num, directions) { + for (let i = 0; i < num; i++) { + if (!directions[i]) { + const rand = Math.random(); + const dire = rand > 0.5 ? 1 : -1; + directions.push(dire); + } else { + directions[i] = -1 * directions[i]; + } + } + return directions; + }, + getBubblePath(r, spNum, directions, rs) { + const path = []; + const cpNum = spNum * 2; // control points number + const unitAngle = (Math.PI * 2) / spNum; // base angle for split points + let angleSum = 0; + const sps = []; + const cps = []; + for (let i = 0; i < spNum; i++) { + const speed = 0.001 * Math.random(); + rs[i] = rs[i] + directions[i] * speed * r; // +-r/6, the sign according to the directions + if (rs[i] < 0.97 * r) { + rs[i] = 0.97 * r; + directions[i] = -1 * directions[i]; + } else if (rs[i] > 1.03 * r) { + rs[i] = 1.03 * r; + directions[i] = -1 * directions[i]; + } + const spX = rs[i] * Math.cos(angleSum); + const spY = rs[i] * Math.sin(angleSum); + sps.push({ x: spX, y: spY }); + for (let j = 0; j < 2; j++) { + const cpAngleRand = unitAngle / 3; + const cpR = rs[i] / Math.cos(cpAngleRand); + const sign = j === 0 ? -1 : 1; + const x = cpR * Math.cos(angleSum + sign * cpAngleRand); + const y = cpR * Math.sin(angleSum + sign * cpAngleRand); + cps.push({ x, y }); + } + angleSum += unitAngle; + } + path.push(['M', sps[0].x, sps[0].y]); + for (let i = 1; i < spNum; i++) { + path.push([ + 'C', + cps[2 * i - 1].x, + cps[2 * i - 1].y, + cps[2 * i].x, + cps[2 * i].y, + sps[i].x, + sps[i].y, + ]); + } + path.push(['C', cps[cpNum - 1].x, cps[cpNum - 1].y, cps[0].x, cps[0].y, sps[0].x, sps[0].y]); + path.push(['Z']); + return path; + }, + }, + 'single-node', +); + +G6.registerNode( + 'animate-circle', + { + setState(name, value, item) { + const shape = item.get('keyShape'); + const label = shape.get('parent').get('children')[1]; + if (name === 'disappearing' && value) { + shape.animate( + (ratio) => { + return { + opacity: 1 - ratio, + r: shape.attr('r') * (1 - ratio), + }; + }, + { + duration: 200, + }, + ); + label.animate( + (ratio) => { + return { + opacity: 1 - ratio, + }; + }, + { + duration: 500, + }, + ); + } else if (name === 'appearing' && value) { + const r = item.getModel().size / 2; + shape.animate( + (ratio) => { + return { + opacity: ratio, + r: r * ratio, + fill: shape.attr('fill'), + }; + }, + { + duration: 300, + }, + ); + label.animate( + (ratio) => { + return { + opacity: ratio, + }; + }, + { + duration: 500, + }, + ); + } + }, + }, + 'circle', +); + +G6.registerEdge( + 'animate-line', + { + drawShape(cfg, group) { + const self = this; + let shapeStyle = self.getShapeStyle(cfg); + shapeStyle = mix(shapeStyle, { + opacity: 0, + strokeOpacity: 0, + }); + const keyShape = group.addShape('path', { + attrs: shapeStyle, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + return keyShape; + }, + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + shape.animate( + (ratio) => { + const opacity = ratio * cfg.style.opacity; + const strokeOpacity = ratio * cfg.style.strokeOpacity; + return { + opacity: ratio || opacity, + strokeOpacity: ratio || strokeOpacity, + }; + }, + { + duration: 300, + }, + ); + }, + setState(name, value, item) { + const shape = item.get('keyShape'); + if (name === 'disappearing' && value) { + shape.animate( + (ratio) => { + return { + opacity: 1 - ratio, + strokeOpacity: 1 - ratio, + }; + }, + { + duration: 200, + }, + ); + } + }, + }, + 'line', +); + +graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + layout: layoutCfg, + modes: { + default: ['drag-canvas'], + }, + defaultNode: { + type: 'bubble', + size: 30, + labelCfg: { + position: 'center', + style: { + fill: 'white', + fontStyle: 'bold', + }, + }, + }, + defaultEdge: { + color: '#888', + type: 'animate-line', + }, +}); + +function translate(x, y) { + let moveX = x; + let moveY = y; + + /* 获得当前偏移量*/ + const group = graph.get('group'); + const bbox = group.getBBox(); + const leftTopPoint = graph.getCanvasByPoint(bbox.minX, bbox.minY); + const rightBottomPoint = graph.getCanvasByPoint(bbox.maxX, bbox.maxY); + /* 如果 x 轴在区域内,不允许左右超过100 */ + if (x < 0 && leftTopPoint.x - x > LIMIT_OVERFLOW_WIDTH) { + moveX = 0; + } + if (x > 0 && rightBottomPoint.x - x < width - LIMIT_OVERFLOW_WIDTH) { + moveX = 0; + } + + if (y < 0 && leftTopPoint.y - y > LIMIT_OVERFLOW_HEIGHT) { + moveY = 0; + } + if (y > 0 && rightBottomPoint.y - y < height - LIMIT_OVERFLOW_HEIGHT) { + moveY = 0; + } + graph.translate(-moveX, -moveY); +} + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; +} +graph.on('node:dragstart', (e) => { + graph.layout(); + refreshDragedNodePosition(e); +}); +graph.on('node:drag', (e) => { + refreshDragedNodePosition(e); +}); +graph.on('node:dragend', (e) => { + e.item.get('model').fx = null; + e.item.get('model').fy = null; +}); + +const loadData = (data) => { + const layoutController = graph.get('layoutController'); + layoutController.layoutCfg.nodeStrength = 30; + layoutController.layoutCfg.collideStrength = 0.8; + layoutController.layoutCfg.alphaDecay = 0.01; + nodes = data.nodes; + edges = data.edges; + + showNodes = []; + showEdges = []; + nodeMap = new Map(); + edgesMap = new Map(); + // find the roots + nodes.forEach((node) => { + if (!node.neighbor) { + node.label = node.text; + node.color = Colors[node.type]; + node.style = { + fill: Colors[node.type], + lineWidth: 0, + }; + let labelColor = '#fff'; + if (node.type === 'adj') { + labelColor = Colors.noun; + } + node.labelCfg = { + style: { + fontSize: 15, + fill: labelColor, + fontWeight: 300, + }, + }; + node.x = Math.random() * 800; + node.y = Math.random() * 800; + showNodes.push(node); + } + nodeMap.set(node.id, node); + }); + + mapNodeSizeAndFontSize(showNodes, 'count', [40, 120]); + showNodes.forEach((snode) => { + if (snode.size < 80) { + snode.type = 'circle'; + } + }); + + edges.forEach((edge) => { + // map the id + edge.id = `${edge.source}-${edge.target}`; + edge.style = { + lineWidth: 0.5, + opacity: 1, + strokeOpacity: 1, + }; + edgesMap.set(edge.id, edge); + }); + graph.data({ + nodes: showNodes, + edges: showEdges, + }); + graph.render(); +}; + +fetch('https://gw.alipayobjects.com/os/basement_prod/0a749386-8593-44a2-a132-81dfa1fc3158.json') + .then((res) => res.json()) + .then((data) => { + loadData(data); + }); + +// click root to expand +graph.on('node:click', (e) => { + curShowNodes = []; + curShowEdges = []; + const item = e.item; + const model = item.getModel(); + if (model.neighbor) { + return; + } + // if clicked a root, hide unrelated items and show the related items + if (!model.neighbor) { + const layoutController = graph.get('layoutController'); + const forceLayout = layoutController.layoutMethods[0]; + forceLayout.forceSimulation.stop(); + // light the level 0 nodes + showNodes.forEach((snode) => { + if (snode.x < 0.5 * width) { + snode.x = 300; + } else { + snode.x = width - 300; + } + }); + model.x = width / 2; + model.y = height / 2; + // animatively hide the items which are going to disappear + if (curShowEdges.length) { + curShowEdges.forEach((csedge) => { + const item = graph.findById(csedge.id); + item && graph.setItemState(item, 'disappearing', true); + }); + } + curShowNodes.forEach((csnode) => { + const item = graph.findById(csnode.id); + item && graph.setItemState(item, 'disappearing', true); + }); + graph.positionsAnimate(); + + // reset curShowNodes nad curShowEdges + curShowNodes = []; + curShowEdges = []; + + // click on the same node which is the current focus node, hide the small nodes, change the layout parameters to roots view + if (currentFocus && currentFocus.id === model.id) { + currentFocus = undefined; + layoutController.layoutCfg.nodeStrength = 100; + layoutController.layoutCfg.collideStrength = 0.8; + layoutController.layoutCfg.alphaDecay = 0.01; + } else { + // click other focus node, hide the current small nodes and show the related nodes + currentFocus = model; + // change data after the original items disappearing + const layoutController = graph.get('layoutController'); + layoutController.layoutCfg.nodeStrength = () => { + return -80; + }; + layoutController.layoutCfg.collideStrength = 0.2; + layoutController.layoutCfg.linkDistance = (d) => { + if (!d.source.neighbor && !d.target.neighbor) return 150; + return 80; + }; + layoutController.layoutCfg.edgeStrength = () => { + return 2; + }; + + curShowNodesMap = new Map(); + // find the nodes which are the descendants of clicked model + edges.forEach((edge) => { + let nodeId = ''; + let node; + if (edge.source === model.id) { + nodeId = edge.target; + } else if (edge.target === model.id) { + nodeId = edge.source; + } else { + return; + } + nodes.forEach((tnode) => { + if (tnode.id === nodeId) { + node = tnode; + } + }); + if (!node) return; + const randomAngle = Math.random() * 2 * Math.PI; + node.x = model.x + (Math.cos(randomAngle) * model.size) / 2 + 10; + node.y = model.y + (Math.sin(randomAngle) * model.size) / 2 + 10; + // const dist = (model.x - node.x) * (model.x - node.x) + (model.y - node.y) * (model.y - node.y); + + if (!node.style) node.style = {}; + node.style.lineWidth = 0; + node.style.opacity = 1; + if (node.neighbor) { + node.type = 'animate-circle'; + node.label = node.text; + const color = model.style.fill; + node.color = color; + node.style.fill = '#fff'; + node.style.lineWidth = 1; + node.size = 30; + node.labelCfg = { + style: { + fontSize: 13, + lineHeight: 19, + fill: '#697B8C', + }, + position: 'center', + }; + } + curShowNodes.push(node); + curShowNodesMap.set(node.id, node); + + // add the edge connect from model to node which exists in edges + const edgeId = `${model.id}-${node.id}`; + const medge = edgesMap.get(edgeId); + if (medge) { + medge.color = model.color; + curShowEdges.push(medge); + } + }); + + edges.forEach((edge) => { + if (edge.source === model.id || edge.target === model.id) { + curShowEdges.push(edge); + } + }); + } + setTimeout(() => { + graph.changeData({ + nodes: showNodes.concat(curShowNodes), + edges: showEdges.concat(curShowEdges), + }); + const nodeItems = graph.getNodes(); + const edgeItems = graph.getEdges(); + edgeItems.forEach((item) => { + graph.clearItemStates(item); + }); + nodeItems.forEach((item) => { + graph.clearItemStates(item); + graph.setItemState(item, 'appearing', true); + }); + }, 400); + } +}); +graph.on('canvas:click', () => { + currentFocus = undefined; + const forceLayout = graph.get('layoutController').layoutMethods[0]; + forceLayout.forceSimulation.stop(); + const nodeItems = graph.getNodes(); + const edgeItems = graph.getEdges(); + if (highlighting) { + highlighting = false; + } else { + nodeItems.forEach((item) => { + const model = item.getModel(); + if (model.neighbor) { + graph.setItemState(item, 'disappearing', true); + } + }); + edgeItems.forEach((item) => { + graph.setItemState(item, 'disappearing', true); + }); + curShowNodes = []; + curShowEdges = []; + setTimeout(() => { + const layoutController = graph.get('layoutController'); + layoutController.layoutCfg.nodeStrength = 100; + layoutController.layoutCfg.collideStrength = 0.8; + layoutController.layoutCfg.alphaDecay = 0.01; + + graph.changeData({ + nodes: showNodes, + edges: showEdges, + }); + }, 400); + } +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/case/graphDemos/demo/customFlow.js b/packages/site/examples/case/graphDemos/demo/customFlow.js new file mode 100644 index 0000000000..301f1fb88d --- /dev/null +++ b/packages/site/examples/case/graphDemos/demo/customFlow.js @@ -0,0 +1,302 @@ +import G6 from '@antv/g6'; +/** + * by Zhihui Hu + * https://www.zhihu.com/people/wisdommm + */ + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +/** ====== data preparation ======= */ +// the first layer nodes +const baseData = { + nodes: [ + { id: "intention1", time: "2022/03/20", title: "User Intend", expandRange: [0, 2], show: false, startPosition: 0, endPosition: 0 }, + { id: "intention2", time: "2022/03/20", title: "User Intend", expandRange: [1, 3], show: false, startPosition: 0, endPosition: 0 }, + { id: "intention3", time: "2022/03/20", title: "User Intend", expandRange: [3, 5], show: false, startPosition: 0, endPosition: 0 }, + ], + edges: [ + { id: "edge1", source: "intention1", target: "intention2" }, + { id: "edge2", source: "intention2", target: "intention3" }, + ] +}; +baseData.nodes.forEach(node => { + node.type = 'customNode'; +}); +// layout for the first layer nodes +const gridLayout = new G6.Layout['grid']({ + rows: 1, + width, + sortBy: 'id' +}); +gridLayout.init(baseData); +gridLayout.execute() + +// the second layer nodes +const rangeData = { + nodes: [ + { id: "0", label: "Like" }, + { id: "1", label: "Follow" }, + { id: "2", label: "Collect" }, + { id: "3", label: "Shop" }, + { id: "4", label: "Pay" }, + { id: "5", label: "Comment" } + ], +}; +rangeData.nodes.forEach(node => node.visible = false); + + +const graphData = { + nodes: baseData.nodes.concat(rangeData.nodes), + edges: baseData.edges +} + +/** ====== custom a node type ======= */ +G6.registerNode('customNode', + { + draw(cfg, group) { + group.shapeMap = {}; + const rect = group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: 50, + lineWidth: 1, + fillOpacity: 1, + radius: 12, + stroke: 'rgba(0,0,0,0.2)', + lineWidth: 1, + fill: '#fff', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-intention', + }); + // title + group.addShape('text', { + attrs: { + x: 0, + y: -10, + text: cfg.title, + fontSize: 14, + fill: '#000', + fontWeight: 'bold', + textAlign: 'center' + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-title', + }); + // time + group.addShape('text', { + attrs: { + x: 0, + y: 20, + text: cfg.time, + fontSize: 14, + fill: '#999', + textAlign: 'center' + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-time', + }); + if (cfg.expandRange) { + // expand button + group.addShape('rect', { + attrs: { + x: -32.5, + y: 35, + width: 65, + height: 20, + radius: 8, + stroke: '#FF6107', + lineWidth: 1, + fill: '#FFF', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rectBtn', + }); + // button text + group.addShape('text', { + attrs: { + x: 0, + y: 51, + text: cfg.show ? '- collapse' : '+ expand', + fill: '#FF6107', + fontSize: 12, + textAlign: 'center' + }, + capture: false, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rectBtn-text', + }); + group.shapeMap['rectBtn-text'] = expandShape; + } + + const expandShape = group.addShape('polygon', { + attrs: { + points: [ + [30, 60], + [-30, 60], + [cfg.startPosition, 200], + [cfg.endPosition, 200], + ], + fill: 'l(90) 0:rgba(255,97,7,0.18) 1:rgba(255,97,7,0)', + opacity: 0.5 + }, + visible: false, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'polygon-shape', + }); + expandShape.toBack(); + group.shapeMap['polygon-shape'] = expandShape; + + return rect; + }, + update: (cfg, item) => { + const group = item.getContainer(); + const expandText = group.shapeMap?.['rectBtn-text'] || group.find(e => e.get('name') === 'rectBtn-text'); + expandText.attr({ + text: cfg.show ? '- collapse' : '+ expand' + }); + const expandShape = group.shapeMap?.['polygon-shape'] || group.find(e => e.get('name') === 'polygon-shape'); + if (cfg.show) { + expandShape.set('visible', true); + expandShape.attr({ + points: [ + [30, 60], + [-30, 60], + [cfg.startPosition, 200], + [cfg.endPosition, 200], + ], + opacity: 0, + }); + expandShape.animate({ opacity: 1 }, { duration: 300, repeat: false }); + } else { + expandShape.set('visible', false); + expandShape.animate({ opacity: 0 }, { duration: 300, repeat: false }); + } + } + }, + 'single-node' +); + + +/** ====== init the graph ======= */ +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas'] + }, + defaultNode: { + type: 'circle', + style: { + r: 30, + fill: '#fff', + stroke: '#ccc', + lineWidth: 1, + }, + }, + defaultEdge: { + style: { + color: "#fff", + stroke: "#FF6107", + lineWidth: 12, + opacity: 0.6 + } + } +}); +graph.read(graphData); + + +/** ====== bind listener ======= */ +graph.on('rectBtn:click', (e) => { + const model = e.item.getModel(); + const { expandRange } = model; + if (expandRange) { + showRoute(model); + } +}); +const showRoute = (nodeData) => { + nodeData.show = !nodeData.show; + + let { nodes, edges } = graphData; + const routeNodes = []; + const routeEdges = []; + const routeNodesMap = {}; + + // the nodes will be shown in the second layer + let showRangeIds = new Set(); + baseData.nodes.forEach(node => { + if (!node.show) return; + const { expandRange } = node; + for (let i = +expandRange[0]; i < +expandRange[1] + 1; i++) { + showRangeIds.add(i); + } + }); + showRangeIds = Array.from(showRangeIds); + showRangeIds.sort((a, b) => a - b); + + const rangeLayoutNodes = []; + rangeData.nodes.forEach(node => { + const showIdx = showRangeIds.indexOf(+node.id); + if (showIdx > -1) { + graph.showItem(node.id); + rangeLayoutNodes.push(node); + } + else graph.hideItem(node.id); + }); + + // layout for the second row nodes + const rangeGridLayout = new G6.Layout['grid']({ + rows: 1, + width: 1000, + begin: [0, 200] + }); + rangeGridLayout.init({ nodes: rangeLayoutNodes, edges: [] }); + rangeGridLayout.execute(); + rangeLayoutNodes.forEach(node => graph.update(node.id, { x: node.x, y: node.y })) + + // update the 'show', 'startPosition', and 'endPosition' for first layer node + baseData.nodes.forEach((node) => { + const { id: nodeId, expandRange, x } = node; + if (!node.show) { + graph.updateItem(nodeId, { show: false }); + return; + } + const [start, end] = expandRange; + graph.updateItem(nodeId, { + show: true, + // the position for the bottom vertexes of the range polygon + startPosition: graph.findById(`${start}`).getModel().x - 30 - x, + endPosition: graph.findById(`${end}`).getModel().x + 30 - x, + }); + }); + + // remove the blue range edges + graph.getEdges().forEach(edge => { + if (edge.getModel().tag === 'range') graph.removeItem(edge); + }); + + // add new blue range edges + showRangeIds.forEach((id, i) => { + if (i === 0) return; + graph.addItem('edge', { + id: `edge-${Math.random()}`, + source: `${showRangeIds[i - 1]}`, + target: `${id}`, + tag: 'range', + style: { + lineWidth: 2, + stroke: '#8CC2FC' + } + }) + }) +} + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; \ No newline at end of file diff --git a/packages/site/examples/case/graphDemos/demo/decisionBubbles.js b/packages/site/examples/case/graphDemos/demo/decisionBubbles.js new file mode 100644 index 0000000000..1c048e8387 --- /dev/null +++ b/packages/site/examples/case/graphDemos/demo/decisionBubbles.js @@ -0,0 +1,933 @@ +import G6 from '@antv/g6'; +/** + * by Shiwu + */ +let showNodes = []; +let showEdges = []; +let curShowNodes = []; +let curShowEdges = []; +let nodes = []; +let edges = []; +let nodeMap = new Map(); +let edgesMap = new Map(); +let curShowNodesMap = new Map(); +let highlighting = false; +let currentFocus; +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const LIMIT_OVERFLOW_WIDTH = width; +const LIMIT_OVERFLOW_HEIGHT = height; + +const mapNodeSize = (nodes, propertyName, visualRange) => { + let minp = 9999999999; + let maxp = -9999999999; + nodes.forEach((node) => { + minp = node[propertyName] < minp ? node[propertyName] : minp; + maxp = node[propertyName] > maxp ? node[propertyName] : maxp; + }); + const rangepLength = maxp - minp; + const rangevLength = visualRange[1] - visualRange[0]; + nodes.forEach((node) => { + node.size = ((node[propertyName] - minp) / rangepLength) * rangevLength + visualRange[0]; + }); +}; + +const lightColors = [ + '#8FE9FF', + '#87EAEF', + '#FFC9E3', + '#A7C2FF', + '#FFA1E3', + '#FFE269', + '#BFCFEE', + '#FFA0C5', + '#D5FF86', +]; +const darkColors = [ + '#7DA8FF', + '#44E6C1', + '#FF68A7', + '#7F86FF', + '#AE6CFF', + '#FF5A34', + '#5D7092', + '#FF6565', + '#6BFFDE', +]; +const uLightColors = [ + '#CFF6FF', + '#BCFCFF', + '#FFECF5', + '#ECFBFF', + '#EAD9FF', + '#FFF8DA', + '#DCE2EE', + '#FFE7F0', + '#EEFFCE', +]; +const uDarkColors = [ + '#CADBFF', + '#A9FFEB', + '#FFC4DD', + '#CACDFF', + '#FFD4F2', + '#FFD3C9', + '#EBF2FF', + '#FFCBCB', + '#CAFFF3', +]; + +const gColors = []; +const unlightColorMap = new Map(); +lightColors.forEach((lcolor, i) => { + gColors.push('l(0) 0:' + lcolor + ' 1:' + darkColors[i]); + unlightColorMap.set(gColors[i], 'l(0) 0:' + uLightColors[i] + ' 1:' + uDarkColors[i]); +}); + +let graph; +const layoutCfg = { + type: 'force', + nodeSize: (d) => { + return d.size / 2 + 5; + }, + nodeStrength: 2500, + collideStrength: 0.8, + alphaDecay: 0.01, + preventOverlap: true, + onTick: () => { + const nodeItems = graph.getNodes(); + const height = graph.get('height'); + const width = graph.get('width'); + const padding = 10; + nodeItems.forEach((item) => { + const model = item.getModel(); + if (model.x > width - padding) model.x = width - padding; + else if (model.x < padding) model.x = padding; + + if (model.y > height - padding) model.y = height - padding; + else if (model.y < padding) model.y = padding; + }); + }, +}; + +G6.registerBehavior('double-finger-drag-canvas', { + getEvents: function getEvents() { + return { + wheel: 'onWheel', + }; + }, + + onWheel: (ev) => { + if (ev.ctrlKey) { + const canvas = graph.get('canvas'); + const point = canvas.getPointByClient(ev.clientX, ev.clientY); + let ratio = graph.getZoom(); + if (ev.wheelDelta > 0) { + ratio = ratio + ratio * 0.05; + } else { + ratio = ratio - ratio * 0.05; + } + graph.zoomTo(ratio, { + x: point.x, + y: point.y, + }); + } else { + const x = ev.deltaX || ev.movementX; + const y = ev.deltaY || ev.movementY || (-ev.wheelDelta * 125) / 3; + translate(x, y); + } + ev.preventDefault(); + }, +}); + +G6.registerNode( + 'bubble', + { + drawShape(cfg, group) { + const self = this; + const r = cfg.size / 2; + // a circle by path + const path = [ + ['M', -r, 0], + ['C', -r, r / 2, -r / 2, r, 0, r], + ['C', r / 2, r, r, r / 2, r, 0], + ['C', r, -r / 2, r / 2, -r, 0, -r], + ['C', -r / 2, -r, -r, -r / 2, -r, 0], + ['Z'], + ]; + const keyShape = group.addShape('path', { + attrs: { + x: 0, + y: 0, + path, + fill: cfg.color || 'steelblue', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + + const mask = group.addShape('path', { + attrs: { + x: 0, + y: 0, + path, + opacity: 0.25, + fill: cfg.color || 'steelblue', + shadowColor: cfg.color.split(' ')[2].substr(2), + shadowBlur: 40, + shadowOffsetX: 0, + shadowOffsetY: 30, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'mask-shape', + }); + + const spNum = 10; // split points number + const directions = [], + rs = []; + self.changeDirections(spNum, directions); + for (let i = 0; i < spNum; i++) { + const rr = r + directions[i] * ((Math.random() * r) / 1000); // +-r/6, the sign according to the directions + if (rs[i] < 0.97 * r) rs[i] = 0.97 * r; + else if (rs[i] > 1.03 * r) rs[i] = 1.03 * r; + rs.push(rr); + } + keyShape.animate( + () => { + const path = self.getBubblePath(r, spNum, directions, rs); + return { path }; + }, + { + repeat: true, + duration: 10000, + }, + ); + + const directions2 = [], + rs2 = []; + self.changeDirections(spNum, directions2); + for (let i = 0; i < spNum; i++) { + const rr = r + directions2[i] * ((Math.random() * r) / 1000); // +-r/6, the sign according to the directions + if (rs2[i] < 0.97 * r) rs2[i] = 0.97 * r; + else if (rs2[i] > 1.03 * r) rs2[i] = 1.03 * r; + rs2.push(rr); + } + mask.animate( + () => { + const path = self.getBubblePath(r, spNum, directions2, rs2); + return { path }; + }, + { + repeat: true, + duration: 10000, + }, + ); + return keyShape; + }, + changeDirections(num, directions) { + for (let i = 0; i < num; i++) { + if (!directions[i]) { + const rand = Math.random(); + const dire = rand > 0.5 ? 1 : -1; + directions.push(dire); + } else { + directions[i] = -1 * directions[i]; + } + } + return directions; + }, + getBubblePath(r, spNum, directions, rs) { + const path = []; + const cpNum = spNum * 2; // control points number + const unitAngle = (Math.PI * 2) / spNum; // base angle for split points + let angleSum = 0; + const sps = []; + const cps = []; + for (let i = 0; i < spNum; i++) { + const speed = 0.001 * Math.random(); + rs[i] = rs[i] + directions[i] * speed * r; // +-r/6, the sign according to the directions + if (rs[i] < 0.97 * r) { + rs[i] = 0.97 * r; + directions[i] = -1 * directions[i]; + } else if (rs[i] > 1.03 * r) { + rs[i] = 1.03 * r; + directions[i] = -1 * directions[i]; + } + const spX = rs[i] * Math.cos(angleSum); + const spY = rs[i] * Math.sin(angleSum); + sps.push({ x: spX, y: spY }); + for (let j = 0; j < 2; j++) { + const cpAngleRand = unitAngle / 3; + const cpR = rs[i] / Math.cos(cpAngleRand); + const sign = j === 0 ? -1 : 1; + const x = cpR * Math.cos(angleSum + sign * cpAngleRand); + const y = cpR * Math.sin(angleSum + sign * cpAngleRand); + cps.push({ x, y }); + } + angleSum += unitAngle; + } + path.push(['M', sps[0].x, sps[0].y]); + for (let i = 1; i < spNum; i++) { + path.push([ + 'C', + cps[2 * i - 1].x, + cps[2 * i - 1].y, + cps[2 * i].x, + cps[2 * i].y, + sps[i].x, + sps[i].y, + ]); + } + path.push(['C', cps[cpNum - 1].x, cps[cpNum - 1].y, cps[0].x, cps[0].y, sps[0].x, sps[0].y]); + path.push(['Z']); + return path; + }, + setState(name, value, item) { + const shape = item.get('keyShape'); + if (name === 'dark') { + if (value) { + if (shape.attr('fill') !== '#fff') { + shape.oriFill = shape.attr('fill'); + const uColor = unlightColorMap.get(shape.attr('fill')); + shape.attr('fill', uColor); + } else { + shape.attr('opacity', 0.2); + } + } else { + if (shape.attr('fill') !== '#fff') { + shape.attr('fill', shape.oriFill || shape.attr('fill')); + } else { + shape.attr('opacity', 1); + } + } + } + }, + }, + 'single-node', +); + +G6.registerNode( + 'animate-circle', + { + setState(name, value, item) { + const shape = item.get('keyShape'); + const label = shape.get('parent').get('children')[1]; + if (name === 'disappearing' && value) { + shape.animate( + (ratio) => { + return { + opacity: 1 - ratio, + r: shape.attr('r') * (1 - ratio), + }; + }, + { + duration: 200, + }, + ); + label.animate( + (ratio) => { + return { + opacity: 1 - ratio, + }; + }, + { + duration: 500, + }, + ); + } else if (name === 'appearing' && value) { + const r = item.getModel().size / 2; + shape.animate( + (ratio) => { + return { + opacity: ratio, + r: r * ratio, + fill: shape.attr('fill'), + }; + }, + { + duration: 300, + }, + ); + label.animate( + { + onFrame(ratio) { + return { + opacity: ratio, + }; + }, + }, + { + duration: 300, + }, + ); + } else if (name === 'dark') { + if (value) { + if (shape.attr('fill') !== '#fff') { + shape.oriFill = shape.attr('fill'); + const uColor = unlightColorMap.get(shape.attr('fill')); + shape.attr('fill', uColor); + } else { + shape.attr('opacity', 0.2); + label.attr('fill', '#A3B1BF'); + } + } else { + if (shape.attr('fill') !== '#fff') { + shape.attr('fill', shape.oriFill || shape.attr('fill')); + } else { + shape.attr('opacity', 1); + label.attr('fill', '#697B8C'); + } + } + } + }, + }, + 'circle', +); + +G6.registerEdge( + 'animate-line', + { + drawShape(cfg, group) { + const self = this; + let shapeStyle = self.getShapeStyle(cfg); + shapeStyle = Object.assign(shapeStyle, { + opacity: 0, + strokeOpacity: 0, + }); + const keyShape = group.addShape('path', { + attrs: shapeStyle, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + return keyShape; + }, + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + shape.animate( + (ratio) => { + const opacity = ratio * cfg.style.opacity; + const strokeOpacity = ratio * cfg.style.strokeOpacity; + return { + opacity: ratio || opacity, + strokeOpacity: ratio || strokeOpacity, + }; + }, + { + duration: 300, + }, + ); + }, + setState(name, value, item) { + const shape = item.get('keyShape'); + if (name === 'disappearing' && value) { + shape.animate( + (ratio) => { + return { + opacity: 1 - ratio, + strokeOpacity: 1 - ratio, + }; + }, + { + duration: 200, + }, + ); + } else if (name === 'dark') { + if (value) shape.attr('opacity', 0.2); + else shape.attr('opacity', 1); + } + }, + }, + 'line', +); + +graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + layout: layoutCfg, + modes: { + default: ['drag-canvas'], + }, + defaultNode: { + type: 'bubble', + size: 95, + labelCfg: { + position: 'center', + style: { + fill: 'white', + fontStyle: 'bold', + }, + }, + }, + defaultEdge: { + color: '#888', + type: 'animate-line', //'animate-line' + }, +}); +graph.get('canvas').set('localRefresh', false); + +function translate(x, y) { + let moveX = x; + let moveY = y; + + /* 获得当前偏移量*/ + const group = graph.get('group'); + const bbox = group.getBBox(); + const leftTopPoint = graph.getCanvasByPoint(bbox.minX, bbox.minY); + const rightBottomPoint = graph.getCanvasByPoint(bbox.maxX, bbox.maxY); + /* 如果 x 轴在区域内,不允许左右超过100 */ + if (x < 0 && leftTopPoint.x - x > LIMIT_OVERFLOW_WIDTH) { + moveX = 0; + } + if (x > 0 && rightBottomPoint.x - x < width - LIMIT_OVERFLOW_WIDTH) { + moveX = 0; + } + + if (y < 0 && leftTopPoint.y - y > LIMIT_OVERFLOW_HEIGHT) { + moveY = 0; + } + if (y > 0 && rightBottomPoint.y - y < height - LIMIT_OVERFLOW_HEIGHT) { + moveY = 0; + } + graph.translate(-moveX, -moveY); +} + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; +} +graph.on('node:dragstart', (e) => { + graph.layout(); + refreshDragedNodePosition(e); +}); +graph.on('node:drag', (e) => { + refreshDragedNodePosition(e); +}); +graph.on('node:dragend', (e) => { + e.item.get('model').fx = null; + e.item.get('model').fy = null; +}); + +const loadData = (data) => { + const layoutController = graph.get('layoutController'); + layoutController.layoutCfg.nodeStrength = 2500; + layoutController.layoutCfg.collideStrength = 0.8; + layoutController.layoutCfg.alphaDecay = 0.01; + nodes = data.nodes; + edges = data.edges; + + showNodes = []; + showEdges = []; + nodeMap = new Map(); + edgesMap = new Map(); + // find the roots + nodes.forEach((node) => { + if (node.level === 0) { + node.color = gColors[showNodes.length % gColors.length]; + node.style = { + fill: gColors[showNodes.length % gColors.length], + lineWidth: 0, + }; + node.labelCfg = { + style: { + fontSize: 25, + fill: '#fff', + fontWeight: 300, + }, + }; + node.x = Math.random() * 800; + node.y = Math.random() * 800; + showNodes.push(node); + } + if (!node.isLeaf) { + const num = node.childrenNum ? `\n(${node.childrenNum})` : ''; + node.label = `${node.name}${num}`; + } else { + node.label = node.name; + } + nodeMap.set(node.id, node); + }); + + mapNodeSize(showNodes, 'childrenNum', [120, 180]); + + // map the color to F nodes, same to its parent + nodes.forEach((node) => { + if (node.level !== 0 && !node.isLeaf) { + const parent = nodeMap.get(node.tags[0]); + node.color = parent.color; + node.style = { + fill: parent.color, + }; + } + }); + edges.forEach((edge) => { + // map the id + edge.id = `${edge.source}-${edge.target}`; + edge.style = { + lineWidth: 0.5, + opacity: 1, + strokeOpacity: 1, + }; + edgesMap.set(edge.id, edge); + }); + graph.data({ + nodes: showNodes, + edges: showEdges, + }); + graph.render(); +}; + +graph.on('node:mouseenter', (e) => { + const item = e.item; + const model = item.getModel(); + if (model.level === 0) { + return; + } + highlighting = true; + graph.setAutoPaint(false); + const nodeItems = graph.getNodes(); + const edgeItems = graph.getEdges(); + nodeItems.forEach((node) => { + graph.setItemState(node, 'dark', true); + node.getModel().light = false; + }); + graph.setItemState(item, 'dark', false); + model.light = true; + const tags = model.tags; + const findTagsMap = new Map(); + let mid = 0; + + let fTag = ''; + // if the model is F node, find the leaves of it + if (!model.isLeaf && model.level !== 0) { + fTag = model.tag; + nodeItems.forEach((item) => { + const itemModel = item.getModel(); + if (!itemModel.isLeaf) return; + const modelTags = itemModel.tags; + modelTags.forEach((mt) => { + const mts = mt.split('-'); + if (mts[1] === fTag) { + graph.setItemState(item, 'dark', false); + itemModel.light = true; + } + }); + }); + } + + // find the tags + tags.forEach((t) => { + const ts = t.split('-'); + findTagsMap.set(ts[0], mid); + mid++; + if (ts[1]) { + findTagsMap.set(ts[1], mid); + mid++; + } + }); + // find the nodes with tag === tags[?] + nodeItems.forEach((item) => { + const node = item.getModel(); + if (findTagsMap.get(node.tag) !== undefined) { + graph.setItemState(item, 'dark', false); + node.light = true; + } + }); + edgeItems.forEach((item) => { + const source = item.getSource().getModel(); + const target = item.getTarget().getModel(); + if (source.light && target.light) { + graph.setItemState(item, 'dark', false); + } else { + graph.setItemState(item, 'dark', true); + } + }); + graph.paint(); + graph.setAutoPaint(true); +}); + +graph.on('node:mouseleave', () => { + if (highlighting) { + const nodeItems = graph.getNodes(); + const edgeItems = graph.getEdges(); + highlighting = false; + nodeItems.forEach((item) => { + graph.setItemState(item, 'dark', false); + }); + edgeItems.forEach((item) => { + graph.setItemState(item, 'dark', false); + }); + } +}); + +fetch('https://gw.alipayobjects.com/os/bmw-prod/fc6e64fc-be94-40fb-b9e2-2d13dd414f38.json') + .then((res) => res.json()) + .then((data) => { + loadData(data); + }); + +// click root to expand +graph.on('node:click', (e) => { + curShowNodes = []; + curShowEdges = []; + const item = e.item; + const model = item.getModel(); + if (!model.isLeaf && model.level !== 0) { + return; + } + // if clicked a root, hide unrelated items and show the related items + if (model.level === 0) { + const layoutController = graph.get('layoutController'); + const forceLayout = layoutController.layoutMethods[0]; + forceLayout.forceSimulation.stop(); + // light the level 0 nodes + showNodes.forEach((snode) => { + const item = graph.findById(snode.id); + graph.setItemState(item, 'dark', false); + if (snode.x < 0.5 * width) { + snode.x = 300; + } else { + snode.x = width - 300; + } + }); + model.x = width / 2; + model.y = height / 2; + // animatively hide the items which are going to disappear + if (curShowEdges.length) { + curShowEdges.forEach((csedge) => { + const item = graph.findById(csedge.id); + item && graph.setItemState(item, 'disappearing', true); + }); + } + curShowNodes.forEach((csnode) => { + const item = graph.findById(csnode.id); + item && graph.setItemState(item, 'disappearing', true); + }); + graph.positionsAnimate(); + + // reset curShowNodes nad curShowEdges + curShowNodes = []; + curShowEdges = []; + + // click on the same node which is the current focus node, hide the small nodes, change the layout parameters to roots view + if (currentFocus && currentFocus.id === model.id) { + currentFocus = undefined; + layoutController.layoutCfg.nodeStrength = 2500; + layoutController.layoutCfg.collideStrength = 0.8; + layoutController.layoutCfg.alphaDecay = 0.01; + } else { + // click other focus node, hide the current small nodes and show the related nodes + currentFocus = model; + // change data after the original items disappearing + const layoutController = graph.get('layoutController'); + layoutController.layoutCfg.nodeStrength = () => { + return -80; + }; + layoutController.layoutCfg.collideStrength = 0.2; + layoutController.layoutCfg.linkDistance = (d) => { + if (d.source.level !== 0) return 120; + const length = 250; + return length; + }; + layoutController.layoutCfg.edgeStrength = () => { + return 2; + }; + + const tag = model.tag; + const findTags = []; + curShowNodesMap = new Map(); + // find the nodes which are the descendants of clicked model + nodes.forEach((node) => { + if (!node.tags) return; + const tags = node.tags; + const tlength = tags.length; + let isChild = false; + const parents = []; + for (let i = 0; i < tlength; i++) { + const ts = tags[i].split('-'); + if (ts[0] === tag) { + isChild = true; + } + parents.push(nodeMap.get(ts[0])); + } + if (isChild) { + const randomAngle = Math.random() * 2 * Math.PI; + node.x = model.x + (Math.cos(randomAngle) * model.size) / 2 + 10; + node.y = model.y + (Math.sin(randomAngle) * model.size) / 2 + 10; + // const dist = (model.x - node.x) * (model.x - node.x) + (model.y - node.y) * (model.y - node.y); + + if (!node.style) node.style = {}; + node.style.lineWidth = 0; + node.style.opacity = 1; + if (node.isLeaf) { + node.type = 'animate-circle'; + let color = 'l(0)'; + const parentsNum = parents.length; + parents.forEach((parent, i) => { + const parentColor = parent.color.split(' ')[1].substr(2); + color += ` ${i / (parentsNum - 1)}:${parentColor}`; + }); + if (parentsNum === 1) { + color = model.color.split(' ')[1].substr(2); + } + node.color = color; + node.style.fill = color; + node.style.fill = '#fff'; + node.style.lineWidth = 1; + node.size = 60; + node.labelCfg = { + style: { + fontSize: 11, + lineHeight: 19, + fill: '#697B8C', + }, + position: 'center', + }; + } else if (node.level !== 0) { + node.type = 'circle'; // 'bubble'; + node.size = 95; + if (!node.style) node.style = {}; + node.color = model.color; + node.style.fill = model.color; + node.labelCfg = { + style: { + fill: '#fff', + fontSize: 14, + }, + position: 'center', + }; + } + curShowNodes.push(node); + curShowNodesMap.set(node.id, node); + + // add the edge connect from model to node which exists in edges + const edgeId = `${model.id}-${node.id}`; + const edge = edgesMap.get(edgeId); + if (edge) { + edge.color = model.color; + curShowEdges.push(edge); + } + tags.forEach((t) => { + const ts = t.split('-'); + if (ts[0] !== tag) { + findTags.push(ts[0]); + } + if (ts[1]) { + findTags.push(ts[1]); + } + }); + } + }); + + // find the nodes which are the ancestors of the current curShowNodes + nodes.forEach((node) => { + const findTagsLength = findTags.length; + for (let i = 0; i < findTagsLength; i++) { + if (node.tag === findTags[i] && curShowNodesMap.get(node.id) === undefined) { + curShowNodes.push(node); + curShowNodesMap.set(node.id, node); + return; + } + } + }); + + // find the edges whose target end source are in the curShowNodes + curShowNodes.forEach((nu, i) => { + const lu = nu.level; + curShowNodes.forEach((nv, j) => { + if (j <= i) return; + const lv = nv.level; + let edgeId; + if (lu < lv) { + edgeId = `${nu.id}-${nv.id}`; + } else { + edgeId = `${nv.id}-${nu.id}`; + } + let color = model.color; + if (nu.isLeaf) { + if (nv.level === 0 && nv.tag !== model.tag) color = '#DFE5EB'; + else if (!nv.isLeaf && nv.tags[0] !== model.tag) { + color = '#DFE5EB'; + } + } else if (nv.isLeaf) { + if (nu.level === 0 && nu.tag !== model.tag) color = '#DFE5EB'; + else if (!nu.isLeaf && nu.tags[0] !== model.tag) { + color = '#DFE5EB'; + } + } + const edge = edgesMap.get(edgeId); + if (edge) { + edge.color = color; + curShowEdges.push(edge); + } + }); + }); + } + setTimeout(() => { + graph.changeData({ + nodes: showNodes.concat(curShowNodes), + edges: showEdges.concat(curShowEdges), + }); + const nodeItems = graph.getNodes(); + const edgeItems = graph.getEdges(); + edgeItems.forEach((item) => { + graph.clearItemStates(item); + }); + nodeItems.forEach((item) => { + graph.clearItemStates(item); + graph.setItemState(item, 'appearing', true); + }); + }, 400); + } +}); +graph.on('canvas:click', () => { + currentFocus = undefined; + const forceLayout = graph.get('layoutController').layoutMethods[0]; + forceLayout.forceSimulation.stop(); + const nodeItems = graph.getNodes(); + const edgeItems = graph.getEdges(); + if (highlighting) { + highlighting = false; + nodeItems.forEach((item) => { + graph.setItemState(item, 'dark', false); + }); + edgeItems.forEach((item) => { + graph.setItemState(item, 'dark', false); + }); + } else { + nodeItems.forEach((item) => { + const model = item.getModel(); + if (model.level === 0) { + graph.setItemState(item, 'dark', false); + } else { + graph.setItemState(item, 'disappearing', true); + } + }); + edgeItems.forEach((item) => { + graph.setItemState(item, 'disappearing', true); + }); + curShowNodes = []; + curShowEdges = []; + setTimeout(() => { + const layoutController = graph.get('layoutController'); + layoutController.layoutCfg.nodeStrength = 2500; + layoutController.layoutCfg.collideStrength = 0.8; + layoutController.layoutCfg.alphaDecay = 0.01; + + graph.changeData({ + nodes: showNodes, + edges: showEdges, + }); + }, 400); + } +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/case/graphDemos/demo/donutTransfer.js b/packages/site/examples/case/graphDemos/demo/donutTransfer.js new file mode 100644 index 0000000000..0295320d50 --- /dev/null +++ b/packages/site/examples/case/graphDemos/demo/donutTransfer.js @@ -0,0 +1,201 @@ +import G6 from '@antv/g6'; + +/** + * by Shiwu + */ + +const data = { + nodes: [ + { + id: 'person A', + label: 'person A', + // the attributes for drawing donut + donutAttrs: { + 'income': 10, + 'outcome': 20, + 'unknown': 25 + }, + }, + { + id: 'person B', + label: 'person B', + donutAttrs: { + 'income': 20, + 'outcome': 10, + 'unknown': 5 + }, + }, + { + id: 'person C', + label: 'person C', + donutAttrs: { + 'income': 200, + 'outcome': 20, + 'unknown': 25 + }, + }, + { + id: 'person D', + label: 'person D', + donutAttrs: { + 'income': 50, + 'outcome': 10, + 'unknown': 15 + }, + }, + { + id: 'person E', + label: 'person E', + donutAttrs: { + 'income': 80, + 'outcome': 40, + 'unknown': 45 + }, + }, + { + id: 'person F', + label: 'person F', + donutAttrs: { + 'income': 90, + 'outcome': 110, + 'unknown': 15 + }, + }, + ], + edges: [ + {source: 'person C', target: 'person F', size: 10}, + {source: 'person B', target: 'person A', size: 5}, + {source: 'person D', target: 'person E', size: 20}, + {source: 'person D', target: 'person C', size: 5}, + {source: 'person B', target: 'person C', size: 10}, + {source: 'person A', target: 'person C', size: 5}, + ] +}; + +data.edges.forEach(edge=> { + edge.label = `Transfer $${edge.size}` +}) + +const colors = { + 'income': '#61DDAA', + 'outcome': '#F08BB4', + 'unknown': '#65789B' +} + +data.nodes.forEach(node => { + node.donutColorMap = colors; + node.size = 0; + Object.keys(node.donutAttrs).forEach(key => { + node.size += node.donutAttrs[key]; + }) + node.size = Math.sqrt(node.size) * 5 +}) + + +const legendData = { + nodes: [{ + id: 'income', + label: 'Income', + order: 0, + style: { + fill: '#61DDAA', + } + }, { + id: 'outcome', + label: 'Outcome', + order: 2, + style: { + fill: '#F08BB4', + } + }, { + id: 'unknown', + label: 'Unknown', + order: 2, + style: { + fill: '#65789B', + } + }] +} +const legend = new G6.Legend({ + data: legendData, + align: 'center', + layout: 'horizontal', // vertical + position: 'bottom-left', + vertiSep: 12, + horiSep: 24, + offsetY: -24, + padding: [4, 16, 8, 16], + containerStyle: { + fill: '#ccc', + lineWidth: 1 + }, + title: ' ', + titleConfig: { + offsetY: -8, + }, +}); + + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + plugins: [legend], + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'radial', + focusNode: 'li', + linkDistance: 200, + unitRadius: 200 + }, + defaultEdge: { + style: { + endArrow: true + }, + labelCfg: { + autoRotate: true, + style: { + stroke: "#fff", + lineWidth: 5 + } + } + }, + defaultNode: { + type: 'donut', + style: { + lineWidth: 0, + }, + labelCfg: { + position: 'bottom', + }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('node:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getNodes().forEach((node) => { + graph.clearItemStates(node); + }); +}); diff --git a/packages/site/examples/case/graphDemos/demo/edgeBundling.js b/packages/site/examples/case/graphDemos/demo/edgeBundling.js new file mode 100644 index 0000000000..938c304844 --- /dev/null +++ b/packages/site/examples/case/graphDemos/demo/edgeBundling.js @@ -0,0 +1,176 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +/** + * 该示例演示边绑定 + * by 十吾 + */ + +insertCss(` + .g6-tooltip { + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +`); + +const lightBlue = 'rgb(119, 243, 252)'; +const llightBlue16 = '#C8FDFC'; +const lightOrange = 'rgb(230, 100, 64)'; +const llightOrange16 = '#FFAA86'; + +fetch('https://gw.alipayobjects.com/os/basement_prod/7ba82250-8367-4351-82b2-d48604cd2261.json') + .then((res) => res.json()) + .then((data) => { + const nodes = data.nodes; + const edges = data.edges; + nodes.forEach((n) => { + n.y = -n.y; + n.degree = 0; + n.inDegree = 0; + n.outDegree = 0; + }); + // compute the degree of each node + const nodeIdMap = new Map(); + nodes.forEach((node) => { + nodeIdMap.set(node.id, node); + }); + edges.forEach((e) => { + const source = nodeIdMap.get(e.source); + const target = nodeIdMap.get(e.target); + source.outDegree++; + target.inDegree++; + source.degree++; + target.degree++; + if (e.sytle === undefined) e.style = {}; + e.style.lineWidth = 0.7; + e.style.strokeOpacity = 0.1; + e.style.stroke = `l(0) 0:${llightOrange16} 1:${llightBlue16}`; + if (nodeIdMap.get(e.source).x < nodeIdMap.get(e.target).x) { + e.style.stroke = `l(0) 0:${llightBlue16} 1:${llightOrange16}`; + } + }); + let maxDegree = -9999; + let minDegree = 9999; + nodes.forEach((n) => { + if (maxDegree < n.degree) maxDegree = n.degree; + if (minDegree > n.degree) minDegree = n.degree; + }); + const sizeRange = [1, 20]; + const sizeDataRange = [minDegree, maxDegree]; + scaleNodeProp(nodes, 'size', 'degree', sizeDataRange, sizeRange); + + // customize the node + G6.registerNode( + 'pie-node', + { + draw: (cfg, group) => { + const radius = cfg.size / 2; + const inPercentage = cfg.inDegree / cfg.degree; + const inAngle = inPercentage * Math.PI * 2; + const inArcEnd = [radius * Math.cos(inAngle), radius * Math.sin(inAngle)]; + let isInBigArc = 1; + let isOutBigArc = 0; + if (inAngle > Math.PI) { + isInBigArc = 0; + isOutBigArc = 1; + } + const fanIn = group.addShape('path', { + attrs: { + path: [ + ['M', radius, 0], + ['A', radius, radius, 0, isInBigArc, 0, inArcEnd[0], inArcEnd[1]], + ['L', 0, 0], + ['Z'], + ], + stroke: lightOrange, + lineWidth: 0, + fill: lightOrange, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'fan-in-path', + }); + + group.addShape('path', { + attrs: { + path: [ + ['M', inArcEnd[0], inArcEnd[1]], + ['A', radius, radius, 0, isOutBigArc, 0, radius, 0], + ['L', 0, 0], + ['Z'], + ], + stroke: lightBlue, + lineWidth: 0, + fill: lightBlue, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'fan-out-path', + }); + return fanIn; + }, + }, + 'single-node', + ); + + const edgeBundling = new G6.Bundling({ + bundleThreshold: 0.6, + K: 100, + }); + + const container = document.getElementById('container'); + container.style.background = '#000'; + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [edgeBundling], + fitView: true, + defaultNode: { + type: 'pie-node', + size: 3, + color: 'steelblue', + style: { + lineWidth: 0, + fill: 'steelblue', + }, + }, + modes: { + default: [ + 'drag-canvas', + 'zoom-canvas', + { + type: 'tooltip', + formatText(model) { + const text = `Longitude: ${model.lon} \n Latitude: ${model.lat}`; + return text; + }, + shouldUpdate: (e) => true, + }, + ], + }, + }); + + edgeBundling.bundling(data); + graph.data(data); + graph.render(); + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); + +function scaleNodeProp(nodes, propName, refPropName, dataRange, outRange) { + const outLength = outRange[1] - outRange[0]; + const dataLength = dataRange[1] - dataRange[0]; + nodes.forEach((n) => { + n[propName] = ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0]; + }); +} diff --git a/packages/site/examples/case/graphDemos/demo/graphinsight.js b/packages/site/examples/case/graphDemos/demo/graphinsight.js new file mode 100644 index 0000000000..4d541912f7 --- /dev/null +++ b/packages/site/examples/case/graphDemos/demo/graphinsight.js @@ -0,0 +1,23 @@ +const container = document.getElementById('container'); + +const iframe = document.createElement('iframe'); +iframe.id = 'graphinsight-bank-demo'; + +iframe.src = + 'https://codesandbox.io/embed/condescending-platform-mt37im?fontsize=14&hidenavigation=1&theme=dark'; +iframe.style = + 'width:100%; height:100%; min-height:500px; border:0; border-radius: 4px; overflow:hidden;'; +iframe.allow = + 'accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking'; +iframe.sandbox = + 'allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts'; +iframe.title = 'condescending-graphinsight-bank'; + +const info = document.createElement('div'); +info.innerHTML = ` +

友情提示,如果CodeSandbox打开比较慢,可以访问 「GraphInsight」 在线查看, +它是 AntV 团队基于 G6 研发的一款图分析应用低代码搭建工具与分析洞察产品,它既可以帮助用户在线实现关系数据的可视化,也可帮助开发者一键导出图画布SDK,极大提高研发效率 +

+`; +container.appendChild(info); +container.appendChild(iframe); diff --git a/packages/site/examples/case/graphDemos/demo/largeGraph.js b/packages/site/examples/case/graphDemos/demo/largeGraph.js new file mode 100644 index 0000000000..6ca1c35025 --- /dev/null +++ b/packages/site/examples/case/graphDemos/demo/largeGraph.js @@ -0,0 +1,1718 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +// 实际开发中把 window.AntVUtil 换成从 @antv/util 引入的相关模块 +// replace window.AntVUtil.isObject with +// import { isNumber, isArray } from '@antv/util'; +const { isNumber, isArray } = window.AntVUtil; + +insertCss(` + .g6-component-contextmenu { + position: absolute; + z-index: 2; + list-style-type: none; + background-color: #363b40; + border-radius: 6px; + font-size: 14px; + color: hsla(0,0%,100%,.85); + width: fit-content; + transition: opacity .2s; + text-align: center; + padding: 0px 20px 0px 20px; + box-shadow: 0 5px 18px 0 rgba(0, 0, 0, 0.6); + border: 0px; + } + .g6-component-contextmenu ul { + padding-left: 0px; + margin: 0; + } + .g6-component-contextmenu li { + cursor: pointer; + list-style-type: none; + list-style: none; + margin-left: 0; + line-height: 38px; + } + .g6-component-contextmenu li:hover { + color: #aaaaaa; + } +`); + +const { labelPropagation, louvain, findShortestPath } = G6.Algorithm; +const { uniqueId } = G6.Util; + +const NODESIZEMAPPING = 'degree'; +const SMALLGRAPHLABELMAXLENGTH = 5; +let labelMaxLength = SMALLGRAPHLABELMAXLENGTH; +const DEFAULTNODESIZE = 20; +const DEFAULTAGGREGATEDNODESIZE = 53; +const NODE_LIMIT = 40; // TODO: find a proper number for maximum node number on the canvas + +let graph = null; +let currentUnproccessedData = { nodes: [], edges: [] }; +let nodeMap = {}; +let aggregatedNodeMap = {}; +let hiddenItemIds = []; // 隐藏的元素 id 数组 +let largeGraphMode = true; +let cachePositions = {}; +let manipulatePosition = undefined; +let descreteNodeCenter; +let layout = { + type: '', + instance: null, + destroyed: true, +}; +let expandArray = []; +let collapseArray = []; +let shiftKeydown = false; +let CANVAS_WIDTH = 800, + CANVAS_HEIGHT = 800; + +const duration = 2000; +const animateOpacity = 0.6; +const animateBackOpacity = 0.1; +const virtualEdgeOpacity = 0.1; +const realEdgeOpacity = 0.2; + +const darkBackColor = 'rgb(43, 47, 51)'; +const disableColor = '#777'; +const theme = 'dark'; +const subjectColors = [ + '#5F95FF', // blue + '#61DDAA', + '#65789B', + '#F6BD16', + '#7262FD', + '#78D3F8', + '#9661BC', + '#F6903D', + '#008685', + '#F08BB4', +]; + +const colorSets = G6.Util.getColorSetsBySubjectColors( + subjectColors, + darkBackColor, + theme, + disableColor, +); + +const global = { + node: { + style: { + fill: '#2B384E', + }, + labelCfg: { + style: { + fill: '#acaeaf', + stroke: '#191b1c', + }, + }, + stateStyles: { + focus: { + fill: '#2B384E', + }, + }, + }, + edge: { + style: { + stroke: '#acaeaf', + realEdgeStroke: '#acaeaf', //'#f00', + realEdgeOpacity, + strokeOpacity: realEdgeOpacity, + }, + labelCfg: { + style: { + fill: '#acaeaf', + realEdgeStroke: '#acaeaf', //'#f00', + realEdgeOpacity: 0.5, + stroke: '#191b1c', + }, + }, + stateStyles: { + focus: { + stroke: '#fff', // '#3C9AE8', + }, + }, + }, +}; + +// Custom super node +G6.registerNode( + 'aggregated-node', + { + draw(cfg, group) { + let width = 53, + height = 27; + const style = cfg.style || {}; + const colorSet = cfg.colorSet || colorSets[0]; + + // halo for hover + group.addShape('rect', { + attrs: { + x: -width * 0.55, + y: -height * 0.6, + width: width * 1.1, + height: height * 1.2, + fill: colorSet.mainFill, + opacity: 0.9, + lineWidth: 0, + radius: (height / 2 || 13) * 1.2, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'halo-shape', + visible: false, + }); + + // focus stroke for hover + group.addShape('rect', { + attrs: { + x: -width * 0.55, + y: -height * 0.6, + width: width * 1.1, + height: height * 1.2, + fill: colorSet.mainFill, // '#3B4043', + stroke: '#AAB7C4', + lineWidth: 1, + lineOpacty: 0.85, + radius: (height / 2 || 13) * 1.2, + }, + name: 'stroke-shape', + visible: false, + }); + + const keyShape = group.addShape('rect', { + attrs: { + ...style, + x: -width / 2, + y: -height / 2, + width, + height, + fill: colorSet.mainFill, // || '#3B4043', + stroke: colorSet.mainStroke, + lineWidth: 2, + cursor: 'pointer', + radius: height / 2 || 13, + lineDash: [2, 2], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'aggregated-node-keyShape', + }); + + let labelStyle = {}; + if (cfg.labelCfg) { + labelStyle = Object.assign(labelStyle, cfg.labelCfg.style); + } + group.addShape('text', { + attrs: { + text: `${cfg.count}`, + x: 0, + y: 0, + textAlign: 'center', + textBaseline: 'middle', + cursor: 'pointer', + fontSize: 12, + fill: '#fff', + opacity: 0.85, + fontWeight: 400, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'count-shape', + className: 'count-shape', + draggable: true, + }); + + // tag for new node + if (cfg.new) { + group.addShape('circle', { + attrs: { + x: width / 2 - 3, + y: -height / 2 + 3, + r: 4, + fill: '#6DD400', + lineWidth: 0.5, + stroke: '#FFFFFF', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'typeNode-tag-circle', + }); + } + return keyShape; + }, + setState: (name, value, item) => { + const group = item.get('group'); + if (name === 'layoutEnd' && value) { + const labelShape = group.find((e) => e.get('name') === 'text-shape'); + if (labelShape) labelShape.set('visible', true); + } else if (name === 'hover') { + if (item.hasState('focus')) { + return; + } + const halo = group.find((e) => e.get('name') === 'halo-shape'); + const keyShape = item.getKeyShape(); + const colorSet = item.getModel().colorSet || colorSets[0]; + if (value) { + halo && halo.show(); + keyShape.attr('fill', colorSet.activeFill); + } else { + halo && halo.hide(); + keyShape.attr('fill', colorSet.mainFill); + } + } else if (name === 'focus') { + const stroke = group.find((e) => e.get('name') === 'stroke-shape'); + const keyShape = item.getKeyShape(); + const colorSet = item.getModel().colorSet || colorSets[0]; + if (value) { + stroke && stroke.show(); + keyShape.attr('fill', colorSet.selectedFill); + } else { + stroke && stroke.hide(); + keyShape.attr('fill', colorSet.mainFill); + } + } + }, + update: undefined, + }, + 'single-node', +); + +// Custom real node +G6.registerNode( + 'real-node', + { + draw(cfg, group) { + let r = 30; + if (isNumber(cfg.size)) { + r = cfg.size / 2; + } else if (isArray(cfg.size)) { + r = cfg.size[0] / 2; + } + const style = cfg.style || {}; + const colorSet = cfg.colorSet || colorSets[0]; + + // halo for hover + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: r + 5, + fill: style.fill || colorSet.mainFill || '#2B384E', + opacity: 0.9, + lineWidth: 0, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'halo-shape', + visible: false, + }); + + // focus stroke for hover + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: r + 5, + fill: style.fill || colorSet.mainFill || '#2B384E', + stroke: '#fff', + strokeOpacity: 0.85, + lineWidth: 1, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'stroke-shape', + visible: false, + }); + + const keyShape = group.addShape('circle', { + attrs: { + ...style, + x: 0, + y: 0, + r, + fill: colorSet.mainFill, + stroke: colorSet.mainStroke, + lineWidth: 2, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'aggregated-node-keyShape', + }); + + let labelStyle = {}; + if (cfg.labelCfg) { + labelStyle = Object.assign(labelStyle, cfg.labelCfg.style); + } + + if (cfg.label) { + const text = cfg.label; + let labelStyle = {}; + let refY = 0; + if (cfg.labelCfg) { + labelStyle = Object.assign(labelStyle, cfg.labelCfg.style); + refY += cfg.labelCfg.refY || 0; + } + let offsetY = 0; + const fontSize = labelStyle.fontSize < 8 ? 8 : labelStyle.fontSize; + const lineNum = cfg.labelLineNum || 1; + offsetY = lineNum * (fontSize || 12); + group.addShape('text', { + attrs: { + text, + x: 0, + y: r + refY + offsetY + 5, + textAlign: 'center', + textBaseLine: 'alphabetic', + cursor: 'pointer', + fontSize, + fill: '#fff', + opacity: 0.85, + fontWeight: 400, + stroke: global.edge.labelCfg.style.stroke, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', + className: 'text-shape', + }); + } + + // tag for new node + if (cfg.new) { + group.addShape('circle', { + attrs: { + x: r - 3, + y: -r + 3, + r: 4, + fill: '#6DD400', + lineWidth: 0.5, + stroke: '#FFFFFF', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'typeNode-tag-circle', + }); + } + + return keyShape; + }, + setState: (name, value, item) => { + const group = item.get('group'); + if (name === 'layoutEnd' && value) { + const labelShape = group.find((e) => e.get('name') === 'text-shape'); + if (labelShape) labelShape.set('visible', true); + } else if (name === 'hover') { + if (item.hasState('focus')) { + return; + } + const halo = group.find((e) => e.get('name') === 'halo-shape'); + const keyShape = item.getKeyShape(); + const colorSet = item.getModel().colorSet || colorSets[0]; + if (value) { + halo && halo.show(); + keyShape.attr('fill', colorSet.activeFill); + } else { + halo && halo.hide(); + keyShape.attr('fill', colorSet.mainFill); + } + } else if (name === 'focus') { + const stroke = group.find((e) => e.get('name') === 'stroke-shape'); + const label = group.find((e) => e.get('name') === 'text-shape'); + const keyShape = item.getKeyShape(); + const colorSet = item.getModel().colorSet || colorSets[0]; + if (value) { + stroke && stroke.show(); + keyShape.attr('fill', colorSet.selectedFill); + label && label.attr('fontWeight', 800); + } else { + stroke && stroke.hide(); + keyShape.attr('fill', colorSet.mainFill); // '#2B384E' + label && label.attr('fontWeight', 400); + } + } + }, + update: undefined, + }, + 'aggregated-node', +); // 这样可以继承 aggregated-node 的 setState + +// Custom the quadratic edge for multiple edges between one node pair +G6.registerEdge( + 'custom-quadratic', + { + setState: (name, value, item) => { + const group = item.get('group'); + const model = item.getModel(); + if (name === 'focus') { + const back = group.find((ele) => ele.get('name') === 'back-line'); + if (back) { + back.stopAnimate(); + back.remove(); + back.destroy(); + } + const keyShape = group.find((ele) => ele.get('name') === 'edge-shape'); + const arrow = model.style.endArrow; + if (value) { + if (keyShape.cfg.animation) { + keyShape.stopAnimate(true); + } + keyShape.attr({ + strokeOpacity: animateOpacity, + opacity: animateOpacity, + stroke: '#fff', + endArrow: { + ...arrow, + stroke: '#fff', + fill: '#fff', + }, + }); + if (model.isReal) { + const { lineWidth, path, endArrow, stroke } = keyShape.attr(); + const back = group.addShape('path', { + attrs: { + lineWidth, + path, + stroke, + endArrow, + opacity: animateBackOpacity, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'back-line', + }); + back.toBack(); + const length = keyShape.getTotalLength(); + keyShape.animate( + (ratio) => { + // the operations in each frame. Ratio ranges from 0 to 1 indicating the prograss of the animation. Returns the modified configurations + const startLen = ratio * length; + // Calculate the lineDash + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + { + repeat: true, // Whether executes the animation repeatly + duration, // the duration for executing once + }, + ); + } else { + let index = 0; + const lineDash = keyShape.attr('lineDash'); + const totalLength = lineDash[0] + lineDash[1]; + keyShape.animate( + () => { + index++; + if (index > totalLength) { + index = 0; + } + const res = { + lineDash, + lineDashOffset: -index, + }; + // returns the modified configurations here, lineDash and lineDashOffset here + return res; + }, + { + repeat: true, // whether executes the animation repeatly + duration, // the duration for executing once + }, + ); + } + } else { + keyShape.stopAnimate(); + const stroke = '#acaeaf'; + const opacity = model.isReal ? realEdgeOpacity : virtualEdgeOpacity; + keyShape.attr({ + stroke, + strokeOpacity: opacity, + opacity, + endArrow: { + ...arrow, + stroke, + fill: stroke, + }, + }); + } + } + }, + }, + 'quadratic', +); + +// Custom the line edge for single edge between one node pair +G6.registerEdge( + 'custom-line', + { + setState: (name, value, item) => { + const group = item.get('group'); + const model = item.getModel(); + if (name === 'focus') { + const keyShape = group.find((ele) => ele.get('name') === 'edge-shape'); + const back = group.find((ele) => ele.get('name') === 'back-line'); + if (back) { + back.stopAnimate(); + back.remove(); + back.destroy(); + } + const arrow = model.style.endArrow; + if (value) { + if (keyShape.cfg.animation) { + keyShape.stopAnimate(true); + } + keyShape.attr({ + strokeOpacity: animateOpacity, + opacity: animateOpacity, + stroke: '#fff', + endArrow: { + ...arrow, + stroke: '#fff', + fill: '#fff', + }, + }); + if (model.isReal) { + const { path, stroke, lineWidth } = keyShape.attr(); + const back = group.addShape('path', { + attrs: { + path, + stroke, + lineWidth, + opacity: animateBackOpacity, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'back-line', + }); + back.toBack(); + const length = keyShape.getTotalLength(); + keyShape.animate( + (ratio) => { + // the operations in each frame. Ratio ranges from 0 to 1 indicating the prograss of the animation. Returns the modified configurations + const startLen = ratio * length; + // Calculate the lineDash + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + { + repeat: true, // Whether executes the animation repeatly + duration, // the duration for executing once + }, + ); + } else { + const lineDash = keyShape.attr('lineDash'); + const totalLength = lineDash[0] + lineDash[1]; + let index = 0; + keyShape.animate( + () => { + index++; + if (index > totalLength) { + index = 0; + } + const res = { + lineDash, + lineDashOffset: -index, + }; + // returns the modified configurations here, lineDash and lineDashOffset here + return res; + }, + { + repeat: true, // whether executes the animation repeatly + duration, // the duration for executing once + }, + ); + } + } else { + keyShape.stopAnimate(); + const stroke = '#acaeaf'; + const opacity = model.isReal ? realEdgeOpacity : virtualEdgeOpacity; + keyShape.attr({ + stroke, + strokeOpacity: opacity, + opacity: opacity, + endArrow: { + ...arrow, + stroke, + fill: stroke, + }, + }); + } + } + }, + }, + 'single-edge', +); + +const descendCompare = (p) => { + // 这是比较函数 + return function (m, n) { + const a = m[p]; + const b = n[p]; + return b - a; // 降序 + }; +}; + +const clearFocusItemState = (graph) => { + if (!graph) return; + clearFocusNodeState(graph); + clearFocusEdgeState(graph); +}; + +// 清除图上所有节点的 focus 状态及相应样式 +const clearFocusNodeState = (graph) => { + const focusNodes = graph.findAllByState('node', 'focus'); + focusNodes.forEach((fnode) => { + graph.setItemState(fnode, 'focus', false); // false + }); +}; + +// 清除图上所有边的 focus 状态及相应样式 +const clearFocusEdgeState = (graph) => { + const focusEdges = graph.findAllByState('edge', 'focus'); + focusEdges.forEach((fedge) => { + graph.setItemState(fedge, 'focus', false); + }); +}; + +// 截断长文本。length 为文本截断后长度,elipsis 是后缀 +const formatText = (text, length = 5, elipsis = '...') => { + if (!text) return ''; + if (text.length > length) { + return `${text.substr(0, length)}${elipsis}`; + } + return text; +}; + +const labelFormatter = (text, minLength = 10) => { + if (text && text.split('').length > minLength) return `${text.substr(0, minLength)}...`; + return text; +}; + +const processNodesEdges = ( + nodes, + edges, + width, + height, + largeGraphMode, + edgeLabelVisible, + isNewGraph = false, +) => { + if (!nodes || nodes.length === 0) return {}; + const currentNodeMap = {}; + let maxNodeCount = -Infinity; + const paddingRatio = 0.3; + const paddingLeft = paddingRatio * width; + const paddingTop = paddingRatio * height; + nodes.forEach((node) => { + node.type = node.level === 0 ? 'real-node' : 'aggregated-node'; + node.isReal = node.level === 0 ? true : false; + node.label = `${node.id}`; + node.labelLineNum = undefined; + node.oriLabel = node.label; + node.label = formatText(node.label, labelMaxLength, '...'); + node.degree = 0; + node.inDegree = 0; + node.outDegree = 0; + if (currentNodeMap[node.id]) { + console.warn('node exists already!', node.id); + node.id = `${node.id}${Math.random()}`; + } + currentNodeMap[node.id] = node; + if (node.count > maxNodeCount) maxNodeCount = node.count; + const cachePosition = cachePositions ? cachePositions[node.id] : undefined; + if (cachePosition) { + node.x = cachePosition.x; + node.y = cachePosition.y; + node.new = false; + } else { + node.new = isNewGraph ? false : true; + if (manipulatePosition && !node.x && !node.y) { + node.x = manipulatePosition.x + 30 * Math.cos(Math.random() * Math.PI * 2); + node.y = manipulatePosition.y + 30 * Math.sin(Math.random() * Math.PI * 2); + } + } + }); + + let maxCount = -Infinity; + let minCount = Infinity; + // let maxCount = 0; + edges.forEach((edge) => { + // to avoid the dulplicated id to nodes + if (!edge.id) edge.id = uniqueId('edge'); + else if (edge.id.split('-')[0] !== 'edge') edge.id = `edge-${edge.id}`; + // TODO: delete the following line after the queried data is correct + if (!currentNodeMap[edge.source] || !currentNodeMap[edge.target]) { + console.warn('edge source target does not exist', edge.source, edge.target, edge.id); + return; + } + const sourceNode = currentNodeMap[edge.source]; + const targetNode = currentNodeMap[edge.target]; + + if (!sourceNode || !targetNode) + console.warn('source or target is not defined!!!', edge, sourceNode, targetNode); + + // calculate the degree + sourceNode.degree++; + targetNode.degree++; + sourceNode.outDegree++; + targetNode.inDegree++; + + if (edge.count > maxCount) maxCount = edge.count; + if (edge.count < minCount) minCount = edge.count; + }); + + nodes.sort(descendCompare(NODESIZEMAPPING)); + const maxDegree = nodes[0].degree || 1; + + const descreteNodes = []; + nodes.forEach((node, i) => { + // assign the size mapping to the outDegree + const countRatio = node.count / maxNodeCount; + const isRealNode = node.level === 0; + node.size = isRealNode ? DEFAULTNODESIZE : DEFAULTAGGREGATEDNODESIZE; + node.isReal = isRealNode; + node.labelCfg = { + position: 'bottom', + offset: 5, + style: { + fill: global.node.labelCfg.style.fill, + fontSize: 6 + countRatio * 6 || 12, + stroke: global.node.labelCfg.style.stroke, + lineWidth: 3, + }, + }; + + if (!node.degree) { + descreteNodes.push(node); + } + }); + + const countRange = maxCount - minCount; + const minEdgeSize = 1; + const maxEdgeSize = 7; + const edgeSizeRange = maxEdgeSize - minEdgeSize; + edges.forEach((edge) => { + // set edges' style + const targetNode = currentNodeMap[edge.target]; + + const size = ((edge.count - minCount) / countRange) * edgeSizeRange + minEdgeSize || 1; + edge.size = size; + + const arrowWidth = Math.max(size / 2 + 2, 3); + const arrowLength = 10; + const arrowBeging = targetNode.size + arrowLength; + let arrowPath = `M ${arrowBeging},0 L ${arrowBeging + arrowLength},-${arrowWidth} L ${arrowBeging + arrowLength + },${arrowWidth} Z`; + let d = targetNode.size / 2 + arrowLength; + if (edge.source === edge.target) { + edge.type = 'loop'; + arrowPath = undefined; + } + const sourceNode = currentNodeMap[edge.source]; + const isRealEdge = targetNode.isReal && sourceNode.isReal; + edge.isReal = isRealEdge; + const stroke = isRealEdge ? global.edge.style.realEdgeStroke : global.edge.style.stroke; + const opacity = isRealEdge + ? global.edge.style.realEdgeOpacity + : global.edge.style.strokeOpacity; + const dash = Math.max(size, 2); + const lineDash = isRealEdge ? undefined : [dash, dash]; + edge.style = { + stroke, + strokeOpacity: opacity, + cursor: 'pointer', + lineAppendWidth: Math.max(edge.size || 5, 5), + fillOpacity: 1, + lineDash, + endArrow: arrowPath + ? { + path: arrowPath, + d, + fill: stroke, + strokeOpacity: 0, + } + : false, + }; + edge.labelCfg = { + autoRotate: true, + style: { + stroke: global.edge.labelCfg.style.stroke, + fill: global.edge.labelCfg.style.fill, + lineWidth: 4, + fontSize: 12, + lineAppendWidth: 10, + opacity: 1, + }, + }; + if (!edge.oriLabel) edge.oriLabel = edge.label; + if (largeGraphMode || !edgeLabelVisible) edge.label = ''; + else { + edge.label = labelFormatter(edge.label, labelMaxLength); + } + + // arrange the other nodes around the hub + const sourceDis = sourceNode.size / 2 + 20; + const targetDis = targetNode.size / 2 + 20; + if (sourceNode.x && !targetNode.x) { + targetNode.x = sourceNode.x + sourceDis * Math.cos(Math.random() * Math.PI * 2); + } + if (sourceNode.y && !targetNode.y) { + targetNode.y = sourceNode.y + sourceDis * Math.sin(Math.random() * Math.PI * 2); + } + if (targetNode.x && !sourceNode.x) { + sourceNode.x = targetNode.x + targetDis * Math.cos(Math.random() * Math.PI * 2); + } + if (targetNode.y && !sourceNode.y) { + sourceNode.y = targetNode.y + targetDis * Math.sin(Math.random() * Math.PI * 2); + } + + if (!sourceNode.x && !sourceNode.y && manipulatePosition) { + sourceNode.x = manipulatePosition.x + 30 * Math.cos(Math.random() * Math.PI * 2); + sourceNode.y = manipulatePosition.y + 30 * Math.sin(Math.random() * Math.PI * 2); + } + if (!targetNode.x && !targetNode.y && manipulatePosition) { + targetNode.x = manipulatePosition.x + 30 * Math.cos(Math.random() * Math.PI * 2); + targetNode.y = manipulatePosition.y + 30 * Math.sin(Math.random() * Math.PI * 2); + } + }); + + descreteNodeCenter = { + x: width - paddingLeft, + y: height - paddingTop, + }; + descreteNodes.forEach((node) => { + if (!node.x && !node.y) { + node.x = descreteNodeCenter.x + 30 * Math.cos(Math.random() * Math.PI * 2); + node.y = descreteNodeCenter.y + 30 * Math.sin(Math.random() * Math.PI * 2); + } + }); + + G6.Util.processParallelEdges(edges, 12.5, 'custom-quadratic', 'custom-line'); + return { + maxDegree, + edges, + }; +}; + +const getForceLayoutConfig = (graph, largeGraphMode, configSettings) => { + let { + linkDistance, + edgeStrength, + nodeStrength, + nodeSpacing, + preventOverlap, + nodeSize, + collideStrength, + alpha, + alphaDecay, + alphaMin, + } = configSettings || { preventOverlap: true }; + + if (!linkDistance && linkDistance !== 0) linkDistance = 225; + if (!edgeStrength && edgeStrength !== 0) edgeStrength = 50; + if (!nodeStrength && nodeStrength !== 0) nodeStrength = 200; + if (!nodeSpacing && nodeSpacing !== 0) nodeSpacing = 5; + + const config = { + type: 'gForce', + minMovement: 0.01, + maxIteration: 5000, + preventOverlap, + damping: 0.99, + linkDistance: (d) => { + let dist = linkDistance; + const sourceNode = nodeMap[d.source] || aggregatedNodeMap[d.source]; + const targetNode = nodeMap[d.target] || aggregatedNodeMap[d.target]; + // // 两端都是聚合点 + // if (sourceNode.level && targetNode.level) dist = linkDistance * 3; + // // 一端是聚合点,一端是真实节点 + // else if (sourceNode.level || targetNode.level) dist = linkDistance * 1.5; + if (!sourceNode.level && !targetNode.level) dist = linkDistance * 0.3; + return dist; + }, + edgeStrength: (d) => { + const sourceNode = nodeMap[d.source] || aggregatedNodeMap[d.source]; + const targetNode = nodeMap[d.target] || aggregatedNodeMap[d.target]; + // 聚合节点之间的引力小 + if (sourceNode.level && targetNode.level) return edgeStrength / 2; + // 聚合节点与真实节点之间引力大 + if (sourceNode.level || targetNode.level) return edgeStrength; + return edgeStrength; + }, + nodeStrength: (d) => { + // 给离散点引力,让它们聚集 + if (d.degree === 0) return -10; + // 聚合点的斥力大 + if (d.level) return nodeStrength * 2; + return nodeStrength; + }, + nodeSize: (d) => { + if (!nodeSize && d.size) return d.size; + return 50; + }, + nodeSpacing: (d) => { + if (d.degree === 0) return nodeSpacing * 2; + if (d.level) return nodeSpacing; + return nodeSpacing; + }, + onLayoutEnd: () => { + if (largeGraphMode) { + graph.getEdges().forEach((edge) => { + if (!edge.oriLabel) return; + edge.update({ + label: labelFormatter(edge.oriLabel, labelMaxLength), + }); + }); + } + }, + tick: () => { + graph.refreshPositions(); + }, + }; + + if (nodeSize) config['nodeSize'] = nodeSize; + if (collideStrength) config['collideStrength'] = collideStrength; + if (alpha) config['alpha'] = alpha; + if (alphaDecay) config['alphaDecay'] = alphaDecay; + if (alphaMin) config['alphaMin'] = alphaMin; + + return config; +}; + +const hideItems = (graph) => { + hiddenItemIds.forEach((id) => { + graph.hideItem(id); + }); +}; + +const showItems = (graph) => { + graph.getNodes().forEach((node) => { + if (!node.isVisible()) graph.showItem(node); + }); + graph.getEdges().forEach((edge) => { + if (!edge.isVisible()) edge.showItem(edge); + }); + hiddenItemIds = []; +}; + +const handleRefreshGraph = ( + graph, + graphData, + width, + height, + largeGraphMode, + edgeLabelVisible, + isNewGraph, +) => { + if (!graphData || !graph) return; + clearFocusItemState(graph); + // reset the filtering + graph.getNodes().forEach((node) => { + if (!node.isVisible()) node.show(); + }); + graph.getEdges().forEach((edge) => { + if (!edge.isVisible()) edge.show(); + }); + + let nodes = [], + edges = []; + + nodes = graphData.nodes; + const processRes = processNodesEdges( + nodes, + graphData.edges || [], + width, + height, + largeGraphMode, + edgeLabelVisible, + isNewGraph, + ); + + edges = processRes.edges; + + graph.changeData({ nodes, edges }); + + hideItems(graph); + graph.getNodes().forEach((node) => { + node.toFront(); + }); + + // layout.instance.stop(); + // force 需要使用不同 id 的对象才能进行全新的布局,否则会使用原来的引用。因此复制一份节点和边作为 force 的布局数据 + layout.instance.init({ + nodes: graphData.nodes, + edges, + }); + + layout.instance.minMovement = 0.0001; + // layout.instance.getCenter = d => { + // const cachePosition = cachePositions[d.id]; + // if (!cachePosition && (d.x || d.y)) return [d.x, d.y, 10]; + // else if (cachePosition) return [cachePosition.x, cachePosition.y, 10]; + // return [width / 2, height / 2, 10]; + // } + layout.instance.getMass = (d) => { + const cachePosition = cachePositions[d.id]; + if (cachePosition) return 5; + return 1; + }; + layout.instance.execute(); + return { nodes, edges }; +}; + +const getMixedGraph = ( + aggregatedData, + originData, + nodeMap, + aggregatedNodeMap, + expandArray, + collapseArray, +) => { + let nodes = [], + edges = []; + + const expandMap = {}, + collapseMap = {}; + expandArray.forEach((expandModel) => { + expandMap[expandModel.id] = true; + }); + collapseArray.forEach((collapseModel) => { + collapseMap[collapseModel.id] = true; + }); + + aggregatedData.clusters.forEach((cluster, i) => { + if (expandMap[cluster.id]) { + nodes = nodes.concat(cluster.nodes); + aggregatedNodeMap[cluster.id].expanded = true; + } else { + nodes.push(aggregatedNodeMap[cluster.id]); + aggregatedNodeMap[cluster.id].expanded = false; + } + }); + originData.edges.forEach((edge) => { + const isSourceInExpandArray = expandMap[nodeMap[edge.source].clusterId]; + const isTargetInExpandArray = expandMap[nodeMap[edge.target].clusterId]; + if (isSourceInExpandArray && isTargetInExpandArray) { + edges.push(edge); + } else if (isSourceInExpandArray) { + const targetClusterId = nodeMap[edge.target].clusterId; + const vedge = { + source: edge.source, + target: targetClusterId, + id: uniqueId('edge'), + label: '', + }; + edges.push(vedge); + } else if (isTargetInExpandArray) { + const sourceClusterId = nodeMap[edge.source].clusterId; + const vedge = { + target: edge.target, + source: sourceClusterId, + id: uniqueId('edge'), + label: '', + }; + edges.push(vedge); + } + }); + aggregatedData.clusterEdges.forEach((edge) => { + if (expandMap[edge.source] || expandMap[edge.target]) return; + else edges.push(edge); + }); + return { nodes, edges }; +}; + +const getNeighborMixedGraph = ( + centerNodeModel, + step, + originData, + clusteredData, + currentData, + nodeMap, + aggregatedNodeMap, + maxNeighborNumPerNode = 5, +) => { + // update the manipulate position for center gravity of the new nodes + manipulatePosition = { x: centerNodeModel.x, y: centerNodeModel.y }; + + // the neighborSubGraph does not include the centerNodeModel. the elements are all generated new nodes and edges + const neighborSubGraph = generateNeighbors(centerNodeModel, step, maxNeighborNumPerNode); + // update the origin data + originData.nodes = originData.nodes.concat(neighborSubGraph.nodes); + originData.edges = originData.edges.concat(neighborSubGraph.edges); + // update the origin nodeMap + neighborSubGraph.nodes.forEach((node) => { + nodeMap[node.id] = node; + }); + // update the clusteredData + const clusterId = centerNodeModel.clusterId; + clusteredData.clusters.forEach((cluster) => { + if (cluster.id !== clusterId) return; + cluster.nodes = cluster.nodes.concat(neighborSubGraph.nodes); + cluster.sumTot += neighborSubGraph.edges.length; + }); + // update the count + aggregatedNodeMap[clusterId].count += neighborSubGraph.nodes.length; + + currentData.nodes = currentData.nodes.concat(neighborSubGraph.nodes); + currentData.edges = currentData.edges.concat(neighborSubGraph.edges); + return currentData; +}; + +const generateNeighbors = (centerNodeModel, step, maxNeighborNumPerNode = 5) => { + if (step <= 0) return undefined; + let nodes = [], + edges = []; + const clusterId = centerNodeModel.clusterId; + const centerId = centerNodeModel.id; + const neighborNum = Math.ceil(Math.random() * maxNeighborNumPerNode); + for (let i = 0; i < neighborNum; i++) { + const neighborNode = { + id: uniqueId('node'), + clusterId, + level: 0, + colorSet: centerNodeModel.colorSet, + }; + nodes.push(neighborNode); + const dire = Math.random() > 0.5; + const source = dire ? centerId : neighborNode.id; + const target = dire ? neighborNode.id : centerId; + const neighborEdge = { + id: uniqueId('edge'), + source, + target, + label: `${source}-${target}`, + }; + edges.push(neighborEdge); + const subNeighbors = generateNeighbors(neighborNode, step - 1, maxNeighborNumPerNode); + if (subNeighbors) { + nodes = nodes.concat(subNeighbors.nodes); + edges = edges.concat(subNeighbors.edges); + } + } + return { nodes, edges }; +}; + +const getExtractNodeMixedGraph = ( + extractNodeData, + originData, + nodeMap, + aggregatedNodeMap, + currentUnproccessedData, +) => { + const extractNodeId = extractNodeData.id; + // const extractNodeClusterId = extractNodeData.clusterId; + // push to the current rendering data + currentUnproccessedData.nodes.push(extractNodeData); + // update the count of aggregatedNodeMap, when to revert? + // aggregatedNodeMap[extractNodeClusterId].count --; + + // extract the related edges + originData.edges.forEach((edge) => { + if (edge.source === extractNodeId) { + const targetClusterId = nodeMap[edge.target].clusterId; + if (!aggregatedNodeMap[targetClusterId].expanded) { + // did not expand, create an virtual edge fromt he extract node to the cluster + currentUnproccessedData.edges.push({ + id: uniqueId('edge'), + source: extractNodeId, + target: targetClusterId, + }); + } else { + // if the cluster is already expanded, push the origin edge + currentUnproccessedData.edges.push(edge); + } + } else if (edge.target === extractNodeId) { + const sourceClusterId = nodeMap[edge.source].clusterId; + if (!aggregatedNodeMap[sourceClusterId].expanded) { + // did not expand, create an virtual edge fromt he extract node to the cluster + currentUnproccessedData.edges.push({ + id: uniqueId('edge'), + target: extractNodeId, + source: sourceClusterId, + }); + } else { + // if the cluster is already expanded, push the origin edge + currentUnproccessedData.edges.push(edge); + } + } + }); + return currentUnproccessedData; +}; + +const examAncestors = (model, expandedArray, length, keepTags) => { + for (let i = 0; i < length; i++) { + const expandedNode = expandedArray[i]; + if (!keepTags[i] && model.parentId === expandedNode.id) { + keepTags[i] = true; // 需要被保留 + examAncestors(expandedNode, expandedArray, length, keepTags); + break; + } + } +}; + +const manageExpandCollapseArray = (nodeNumber, model, collapseArray, expandArray) => { + manipulatePosition = { x: model.x, y: model.y }; + + // 维护 expandArray,若当前画布节点数高于上限,移出 expandedArray 中非 model 祖先的节点) + if (nodeNumber > NODE_LIMIT) { + // 若 keepTags[i] 为 true,则 expandedArray 的第 i 个节点需要被保留 + const keepTags = {}; + const expandLen = expandArray.length; + // 检查 X 的所有祖先并标记 keepTags + examAncestors(model, expandArray, expandLen, keepTags); + // 寻找 expandedArray 中第一个 keepTags 不为 true 的点 + let shiftNodeIdx = -1; + for (let i = 0; i < expandLen; i++) { + if (!keepTags[i]) { + shiftNodeIdx = i; + break; + } + } + // 如果有符合条件的节点,将其从 expandedArray 中移除 + if (shiftNodeIdx !== -1) { + let foundNode = expandArray[shiftNodeIdx]; + if (foundNode.level === 2) { + let foundLevel1 = false; + // 找到 expandedArray 中 parentId = foundNode.id 且 level = 1 的第一个节点 + for (let i = 0; i < expandLen; i++) { + const eNode = expandArray[i]; + if (eNode.parentId === foundNode.id && eNode.level === 1) { + foundLevel1 = true; + foundNode = eNode; + expandArray.splice(i, 1); + break; + } + } + // 若未找到,则 foundNode 不变, 直接删去 foundNode + if (!foundLevel1) expandArray.splice(shiftNodeIdx, 1); + } else { + // 直接删去 foundNode + expandArray.splice(shiftNodeIdx, 1); + } + // const removedNode = expandedArray.splice(shiftNodeIdx, 1); // splice returns an array + const idSplits = foundNode.id.split('-'); + let collapseNodeId; + // 去掉最后一个后缀 + for (let i = 0; i < idSplits.length - 1; i++) { + const str = idSplits[i]; + if (collapseNodeId) collapseNodeId = `${collapseNodeId}-${str}`; + else collapseNodeId = str; + } + const collapseNode = { + id: collapseNodeId, + parentId: foundNode.id, + level: foundNode.level - 1, + }; + collapseArray.push(collapseNode); + } + } + + const currentNode = { + id: model.id, + level: model.level, + parentId: model.parentId, + }; + + // 加入当前需要展开的节点 + expandArray.push(currentNode); + + graph.get('canvas').setCursor('default'); + return { expandArray, collapseArray }; +}; + +const cacheNodePositions = (nodes) => { + const positionMap = {}; + const nodeLength = nodes.length; + for (let i = 0; i < nodeLength; i++) { + const node = nodes[i].getModel(); + positionMap[node.id] = { + x: node.x, + y: node.y, + level: node.level, + }; + } + return positionMap; +}; + +const stopLayout = () => { + layout.instance.stop(); +}; + +const bindListener = (graph) => { + graph.on('keydown', (evt) => { + const code = evt.key; + if (!code) { + return; + } + if (code.toLowerCase() === 'shift') { + shiftKeydown = true; + } else { + shiftKeydown = false; + } + }); + graph.on('keyup', (evt) => { + const code = evt.key; + if (!code) { + return; + } + if (code.toLowerCase() === 'shift') { + shiftKeydown = false; + } + }); + graph.on('node:mouseenter', (evt) => { + const { item } = evt; + const model = item.getModel(); + const currentLabel = model.label; + model.oriFontSize = model.labelCfg.style.fontSize; + item.update({ + label: model.oriLabel, + }); + model.oriLabel = currentLabel; + graph.setItemState(item, 'hover', true); + item.toFront(); + }); + + graph.on('node:mouseleave', (evt) => { + const { item } = evt; + const model = item.getModel(); + const currentLabel = model.label; + item.update({ + label: model.oriLabel, + }); + model.oriLabel = currentLabel; + graph.setItemState(item, 'hover', false); + }); + + graph.on('edge:mouseenter', (evt) => { + const { item } = evt; + const model = item.getModel(); + const currentLabel = model.label; + item.update({ + label: model.oriLabel, + }); + model.oriLabel = currentLabel; + item.toFront(); + item.getSource().toFront(); + item.getTarget().toFront(); + }); + + graph.on('edge:mouseleave', (evt) => { + const { item } = evt; + const model = item.getModel(); + const currentLabel = model.label; + item.update({ + label: model.oriLabel, + }); + model.oriLabel = currentLabel; + }); + // click node to show the detail drawer + graph.on('node:click', (evt) => { + stopLayout(); + if (!shiftKeydown) clearFocusItemState(graph); + else clearFocusEdgeState(graph); + const { item } = evt; + + // highlight the clicked node, it is down by click-select + graph.setItemState(item, 'focus', true); + + if (!shiftKeydown) { + // 将相关边也高亮 + const relatedEdges = item.getEdges(); + relatedEdges.forEach((edge) => { + graph.setItemState(edge, 'focus', true); + }); + } + }); + + // click edge to show the detail of integrated edge drawer + graph.on('edge:click', (evt) => { + stopLayout(); + if (!shiftKeydown) clearFocusItemState(graph); + const { item } = evt; + // highlight the clicked edge + graph.setItemState(item, 'focus', true); + }); + + // click canvas to cancel all the focus state + graph.on('canvas:click', (evt) => { + clearFocusItemState(graph); + console.log(graph.getGroup(), graph.getGroup().getBBox(), graph.getGroup().getCanvasBBox()); + }); +}; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const descriptionDiv = document.createElement('div'); + descriptionDiv.innerHTML = `Click【HERE】To Full Demo +
+ 点击【这里】进入完整 Demo`; + descriptionDiv.style.textAlign = 'right'; + descriptionDiv.style.color = '#fff'; + descriptionDiv.style.position = 'absolute'; + descriptionDiv.style.right = '32px'; + descriptionDiv.style.marginTop = '8px'; + container.appendChild(descriptionDiv); + + container.style.backgroundColor = '#2b2f33'; + + CANVAS_WIDTH = container.scrollWidth; + CANVAS_HEIGHT = (container.scrollHeight || 500) - 30; + + nodeMap = {}; + const clusteredData = louvain(data, false, 'weight'); + const aggregatedData = { nodes: [], edges: [] }; + clusteredData.clusters.forEach((cluster, i) => { + cluster.nodes.forEach((node) => { + node.level = 0; + node.label = node.id; + node.type = ''; + node.colorSet = colorSets[i]; + nodeMap[node.id] = node; + }); + const cnode = { + id: cluster.id, + type: 'aggregated-node', + count: cluster.nodes.length, + level: 1, + label: cluster.id, + colorSet: colorSets[i], + idx: i, + }; + aggregatedNodeMap[cluster.id] = cnode; + aggregatedData.nodes.push(cnode); + }); + clusteredData.clusterEdges.forEach((clusterEdge) => { + const cedge = { + ...clusterEdge, + size: Math.log(clusterEdge.count), + label: '', + id: uniqueId('edge'), + }; + if (cedge.source === cedge.target) { + cedge.type = 'loop'; + cedge.loopCfg = { + dist: 20, + }; + } else cedge.type = 'line'; + aggregatedData.edges.push(cedge); + }); + + data.edges.forEach((edge) => { + edge.label = `${edge.source}-${edge.target}`; + edge.id = uniqueId('edge'); + }); + + currentUnproccessedData = aggregatedData; + + const { edges: processedEdges } = processNodesEdges( + currentUnproccessedData.nodes, + currentUnproccessedData.edges, + CANVAS_WIDTH, + CANVAS_HEIGHT, + largeGraphMode, + true, + true, + ); + + const contextMenu = new G6.Menu({ + shouldBegin(evt) { + if (evt.target && evt.target.isCanvas && evt.target.isCanvas()) return true; + if (evt.item) return true; + return false; + }, + getContent(evt) { + const { item } = evt; + if (evt.target && evt.target.isCanvas && evt.target.isCanvas()) { + return `
    +
  • Show all Hidden Items
  • +
  • Collapse all Clusters
  • +
`; + } else if (!item) return; + const itemType = item.getType(); + const model = item.getModel(); + if (itemType && model) { + if (itemType === 'node') { + if (model.level !== 0) { + return `
    +
  • Expand the Cluster
  • +
  • Hide the Node
  • +
`; + } else { + return `
    +
  • Collapse the Cluster
  • +
  • Find 1-degree Neighbors
  • +
  • Find 2-degree Neighbors
  • +
  • Find 3-degree Neighbors
  • +
  • Hide the Node
  • +
`; + } + } else { + return `
    +
  • Hide the Edge
  • +
`; + } + } + }, + handleMenuClick: (target, item) => { + const model = item && item.getModel(); + const liIdStrs = target.id.split('-'); + let mixedGraphData; + switch (liIdStrs[0]) { + case 'hide': + graph.hideItem(item); + hiddenItemIds.push(model.id); + break; + case 'expand': + const newArray = manageExpandCollapseArray( + graph.getNodes().length, + model, + collapseArray, + expandArray, + ); + expandArray = newArray.expandArray; + collapseArray = newArray.collapseArray; + mixedGraphData = getMixedGraph( + clusteredData, + data, + nodeMap, + aggregatedNodeMap, + expandArray, + collapseArray, + ); + break; + case 'collapse': + const aggregatedNode = aggregatedNodeMap[model.clusterId]; + manipulatePosition = { x: aggregatedNode.x, y: aggregatedNode.y }; + collapseArray.push(aggregatedNode); + for (let i = 0; i < expandArray.length; i++) { + if (expandArray[i].id === model.clusterId) { + expandArray.splice(i, 1); + break; + } + } + mixedGraphData = getMixedGraph( + clusteredData, + data, + nodeMap, + aggregatedNodeMap, + expandArray, + collapseArray, + ); + break; + case 'collapseAll': + expandArray = []; + collapseArray = []; + mixedGraphData = getMixedGraph( + clusteredData, + data, + nodeMap, + aggregatedNodeMap, + expandArray, + collapseArray, + ); + break; + case 'neighbor': + const expandNeighborSteps = parseInt(liIdStrs[1]); + mixedGraphData = getNeighborMixedGraph( + model, + expandNeighborSteps, + data, + clusteredData, + currentUnproccessedData, + nodeMap, + aggregatedNodeMap, + 10, + ); + break; + case 'show': + showItems(graph); + break; + default: + break; + } + if (mixedGraphData) { + cachePositions = cacheNodePositions(graph.getNodes()); + currentUnproccessedData = mixedGraphData; + handleRefreshGraph( + graph, + currentUnproccessedData, + CANVAS_WIDTH, + CANVAS_HEIGHT, + largeGraphMode, + true, + false, + ); + } + }, + // offsetX and offsetY include the padding of the parent container + // 需要加上父级容器的 padding-left 16 与自身偏移量 10 + offsetX: 16 + 10, + // 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10 + offsetY: 0, + // the types of items that allow the menu show up + // 在哪些类型的元素上响应 + itemTypes: ['node', 'edge', 'canvas'], + }); + + graph = new G6.Graph({ + container: 'container', + width: CANVAS_WIDTH, + height: CANVAS_HEIGHT, + linkCenter: true, + minZoom: 0.1, + groupByTypes: false, + modes: { + default: [ + { + type: 'drag-canvas', + enableOptimize: true, + }, + { + type: 'zoom-canvas', + enableOptimize: true, + optimizeZoom: 0.01, + }, + 'drag-node', + 'shortcuts-call', + ], + lassoSelect: [ + { + type: 'zoom-canvas', + enableOptimize: true, + optimizeZoom: 0.01, + }, + { + type: 'lasso-select', + selectedState: 'focus', + trigger: 'drag', + }, + ], + fisheyeMode: [], + }, + defaultNode: { + type: 'aggregated-node', + size: DEFAULTNODESIZE, + }, + plugins: [contextMenu], + }); + + graph.get('canvas').set('localRefresh', false); + + const layoutConfig = getForceLayoutConfig(graph, largeGraphMode); + layoutConfig.center = [CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2]; + layout.instance = new G6.Layout['gForce'](layoutConfig); + layout.instance.init({ + nodes: currentUnproccessedData.nodes, + edges: processedEdges, + }); + layout.instance.execute(); + + bindListener(graph); + graph.data({ nodes: aggregatedData.nodes, edges: processedEdges }); + graph.render(); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + const container = document.getElementById('container'); + if (!container) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; diff --git a/packages/site/examples/case/graphDemos/demo/meta.json b/packages/site/examples/case/graphDemos/demo/meta.json new file mode 100644 index 0000000000..f4d2a55274 --- /dev/null +++ b/packages/site/examples/case/graphDemos/demo/meta.json @@ -0,0 +1,80 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "graphinsight.js", + "title": { + "zh": "GraphInsight 案例", + "en": "GraphInsight Case" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_0d75e8/afts/img/A*eXL9T6xqPlUAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "largeGraph.js", + "title": { + "zh": "大规模图下钻式探索", + "en": "Large Graph Exploration" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*a6N9Q6q44Q0AAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "christmasBubbles.js", + "title": { + "zh": "圣诞推文可视化", + "en": "Christmas Bubbles" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*yRU1QZbcQN4AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "edgeBundling.js", + "title": { + "zh": "美国迁徙图边绑定", + "en": "Edge Bundling" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*z9iXQq_kcrYAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "decisionBubbles.js", + "title": { + "zh": "图表决策", + "en": "Decision Bubbles" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wvofQ6DuC0YAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "simplifyCluster.js", + "title": { + "zh": "聚类的折叠/扩展", + "en": "Collapse/Expand Cluster" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*yU5sRLivdLIAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "donutTransfer.js", + "title": { + "zh": "甜甜圈转账图", + "en": "Donut Transfer" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*YK6yRIfKQaIAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "customFlow.js", + "title": { + "zh": "自定义分类图", + "en": "Custom Category Graph" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*hk1QTqVIHnIAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "sequence.js", + "title": { + "zh": "时序图", + "en": "Sequence Graph" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2eo7So4AY4IAAAAAAAAAAAAADmJ7AQ" + } + ] +} \ No newline at end of file diff --git a/packages/site/examples/case/graphDemos/demo/sequence.js b/packages/site/examples/case/graphDemos/demo/sequence.js new file mode 100644 index 0000000000..9a8bff7482 --- /dev/null +++ b/packages/site/examples/case/graphDemos/demo/sequence.js @@ -0,0 +1,252 @@ + +/** + * by @WontonCat from GitHub + */ + + +import G6 from '@antv/g6'; + +const sequenceStyle = { + nodeHeight: 30, // Object节点高度 + nodeWidth: 150, // Object节点宽度 + nodeMargin: 10, // Object节点间距 + activeWidth: 10, // active区域宽度 + activeHeight: 40 // active区域高度 +}; + +/* 已知条件 */ +const data = [ + { + source: "A", + target: "B", + message: "Hello B, how are you?" + }, + { + source: "A", + target: "C", + message: "Hello C, how are you?" + }, + { + source: "B", + target: "A", + message: "Great!" + } +]; + +// 数据处理 +const nodeArr = [ + ...new Set( + data.map((item) => item.source).concat(data.map((item) => item.target)) + ) +]; + +const sequenceData = { + nodes: [], + edges: [] +}; + +const sequenceNodes = []; + +const getSequenceNode = (data, nodeArr, sequenceStyle) => { + const { nodeWidth, activeHeight, nodeMargin, nodeHeight } = sequenceStyle; + // 计算整体高度 + const height = (data.length + 1) * activeHeight + nodeHeight; + // 生成上下节点 + nodeArr.forEach((nodeName, index) => { + const x = index * (nodeWidth + nodeMargin) + nodeWidth * 0.5; + sequenceData.nodes.push( + { + name: nodeName, + type: "sequence-node", + id: `${nodeName}-start`, + x: x, + y: 0 + }, + { + name: nodeName, + type: "sequence-node", + id: `${nodeName}-end`, + x: x, + y: height + } + ); + // 下节点中心点坐标 + sequenceNodes[nodeName] = x; + // 节点关系( 连线 ) + sequenceData.edges.push({ + source: `${nodeName}-start`, + target: `${nodeName}-end` + }); + }); +}; + +const getSequenceLine = (data, sequenceStyle) => { + const { activeHeight, nodeHeight } = sequenceStyle; + data.forEach((node, index) => { + const height = index * activeHeight + activeHeight + nodeHeight * 0.5; + sequenceData.nodes.push( + { + type: "active-node", + id: `${node.source}-${index}`, + x: sequenceNodes[node.source], + y: height + }, + { + type: "active-node", + id: `${node.target}-${index}`, + x: sequenceNodes[node.target], + y: height + } + ); + sequenceData.edges.push({ + text: node.message, + source: `${node.source}-${index}`, + target: `${node.target}-${index}`, + type: "sequence-line-arrow" + }); + }); +}; + +// Object +getSequenceNode(data, nodeArr, sequenceStyle); +// Message连线 +getSequenceLine(data, sequenceStyle); + +G6.registerNode( + "sequence-node", + { + drawShape(cfg, group) { + console.log(3344); + + const { nodeHeight, nodeWidth } = sequenceStyle; + // 矩形框框 + const nodeBox = group.addShape("rect", { + attrs: { + x: -nodeWidth * 0.5, + y: -nodeHeight * 0.5, + width: nodeWidth, + height: nodeHeight, + fill: "#e6f7ff", + stroke: "#91d5ff", + radius: 3 + } + }); + + group.addShape("text", { + attrs: { + text: cfg.name, + x: 0, + y: 0, + fontSize: 9, + textAlign: "center", + textBaseline: "middle", + fill: "#555" + } + }); + return nodeBox; + } + }, + "extend-node" +); + +G6.registerEdge( + "sequence-line-arrow", + { + draw: function draw(cfg, group) { + const { startPoint, endPoint } = cfg; + const { activeWidth } = sequenceStyle; + const isFromLine = endPoint.x - startPoint.x > 0; + const newEndPoint = { + x: isFromLine + ? endPoint.x - activeWidth * 0.5 + : endPoint.x + activeWidth * 0.5, + y: endPoint.y + }; + + const newStartPoint = { + x: isFromLine + ? startPoint.x + activeWidth * 0.5 + : startPoint.x - activeWidth * 0.5, + y: startPoint.y + }; + + const path = this.getPath([newStartPoint, newEndPoint]); + + group.addShape("path", { + attrs: { + path: path, + lineWidth: 1, + stroke: "#555", + cursor: "pointer", + endArrow: { + path: "M 0,0 L 8,4 L 8,-4 Z", + fill: "#333" + }, + lineAppendWidth: 10 // 可点击范围 + } + }); + + const shape = group.addShape("text", { + attrs: { + text: cfg.text, + x: (startPoint.x + endPoint.x) * 0.5, + y: startPoint.y - 4, + fontSize: 10, + textAlign: "center", + textBaseline: "bottom", + fill: "#096dd9", + fontWeight: "400" + } + }); + return shape; + }, + update: null + }, + "line" +); + +G6.registerNode( + "active-node", + { + drawShape(cfg, group) { + const { activeHeight, activeWidth } = sequenceStyle; + console.log(1122); + const nodeBox = group.addShape("rect", { + attrs: { + x: -activeWidth * 0.5, + y: -activeHeight * 0.5, + width: activeWidth, + height: activeHeight, + fill: "#91d5ff" + } + }); + return nodeBox; + } + }, + "extend-node" +); +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const graph = new G6.Graph({ + container: "container", + width, + height, + fitView: true, + defaultNode: { + anchorPoints: [[0.5, 0.5]] + }, + defaultEdge: { + type: "line" + } +}); + +graph.data(sequenceData); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; \ No newline at end of file diff --git a/packages/site/examples/case/graphDemos/demo/simplifyCluster.js b/packages/site/examples/case/graphDemos/demo/simplifyCluster.js new file mode 100644 index 0000000000..3ad2a59271 --- /dev/null +++ b/packages/site/examples/case/graphDemos/demo/simplifyCluster.js @@ -0,0 +1,356 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-tooltip { + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +`); + +/** + * 该示例演示 Fruchterman 布局算法的聚类效果,以及点击交互进行聚类的聚合和扩散效果 + * by 十吾 + */ +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Reading data & Doing layout......'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); +const width = container.scrollWidth; +const height = container.scrollHeight - 30; + +const colors = ['#f5222d', '#faad14', '#a0d911', '#13c2c2', '#1890ff', '#b37feb', '#eb2f96']; +const beginColor = '#5b8c00'; // green +const endColor = '#ff4d4f'; // red + +fetch('https://gw.alipayobjects.com/os/basement_prod/7bacd7d1-4119-4ac1-8be3-4c4b9bcbc25f.json') + .then((res) => res.json()) + .then((data) => { + const nodes = data.nodes; + const edges = data.edges; + + const nodeMap = new Map(); + const clusterMap = new Map(); + let cidx = 0; + nodes.forEach(function (n) { + nodeMap.set(n.id, n); + let region = n.region.split(' '); + if (n.region === 'East Asia') region = n.region; + else region = region[region.length - 1]; + + if (clusterMap.get(region) === undefined) { + clusterMap.set(region, cidx); + cidx++; + } + const clusterId = clusterMap.get(region); + const color = colors[clusterId % colors.length]; + n.style = { + color, + fill: color, + stroke: '#666', + lineWidth: 0.6, + }; + n.cluster = clusterId; + n.importValue = 0; + n.exportValue = 0; + }); + // map the value of + edges.forEach(function (e) { + if (e.value === '') e.value = 0; + const v = parseFloat(e.value); + nodeMap.get(e.source).exportValue += v; + nodeMap.get(e.target).importValue += v; + }); + mapValueToProp(nodes, 'exportValue', 'size', [2, 12]); + const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'fruchterman', + maxIteration: 8000, + gravity: 10, + clustering: true, + clusterGravity: 30, + workerEnabled: true, + }, + fitView: true, + linkCenter: true, + defaultNode: { + type: 'circle', + size: 5, + }, + defaultEdge: { + type: 'quadratic', + }, + modes: { + default: [ + 'drag-node', + 'drag-canvas', + { + type: 'tooltip', + formatText: function formatText(model) { + let name = ''; + let countries = ''; + if (model.name) name = model.name + '
'; + if (model.countries) countries = '
Number of Countries: ' + model.countries; + const text = `${name} Export Value: ${model.exportValue}(1000USD)
Region: ${model.region}
Cluster: ${model.cluster} ${countries}`; + return text; + }, + + shouldUpdate: function shouldUpdate() { + return true; + }, + }, + ], + }, + }); + + graph.on('afterlayout', function () { + descriptionDiv.innerHTML = 'Done!'; + }); + + graph.data(data); + graph.render(); + + const edgeItems = graph.getEdges(); + edgeItems.forEach(function (e) { + const lineWidth = 0.4; + const strokeOpacity = 0.2; + let stroke = 'l(0) 0:' + beginColor + ' 1:' + endColor; + const sourceModel = e.getSource().getModel(); + const targetModel = e.getTarget().getModel(); + if (sourceModel.x > targetModel.x) { + stroke = 'l(0) 0:' + endColor + ' 1:' + beginColor; + } + e.update({ + style: { + lineWidth, + strokeOpacity, + stroke, + }, + }); + }); + graph.paint(); + + graph.on('node:click', function (e) { + const targetItem = e.item; + const model = targetItem.getModel(); + const graphEdges = graph.getEdges(); + const graphNodes = graph.getNodes(); + // click on the cluster node + if (model.id.substr(0, 7) === 'cluster') { + graphNodes.forEach(function (gn) { + const gnModel = gn.getModel(); + // show the common nodes + if (gnModel.cluster === model.cluster && gnModel.id.substr(0, 7) !== 'cluster') { + gn.show(); + } + // remove the cluster nodes + if (gnModel.id === model.id) graph.removeItem(gn); + }); + + graphEdges.forEach(function (ge) { + const sourceModel = ge.get('sourceNode').getModel(); + const targetModel = ge.get('targetNode').getModel(); + // show the common edges + if ( + (sourceModel.cluster === model.cluster && sourceModel.id.substr(0, 7) !== 'cluster') || + (targetModel.cluster === model.cluster && targetModel.id.substr(0, 7) !== 'cluster') + ) { + ge.show(); + // add the edges to other cluster nodes + if (!ge.get('sourceNode').get('visible') && sourceModel.cluster !== model.cluster) { + let c1 = beginColor, + c2 = endColor; + if (model.x > targetModel.x) { + c1 = endColor; + c2 = beginColor; + } + graph.addItem('edge', { + source: 'cluster' + sourceModel.cluster, + target: targetModel.id, + id: 'cluster-edge-' + ge.id, + style: { + stroke: 'l(0) 0:' + c1 + ' 1:' + c2, + lineWidth: 0.4, + strokeOpacity: 0.2, + }, + }); + } else if ( + ge.get('targetNode').get('visible') && + targetModel.cluster !== model.cluster + ) { + let _c = beginColor, + _c2 = endColor; + if (sourceModel.x > model.x) { + _c = endColor; + _c2 = beginColor; + } + graph.addItem('edge', { + source: sourceModel.id, + target: 'cluster' + targetModel.id, + id: 'cluster-edge-' + ge.id, + style: { + stroke: 'l(0) 0:' + _c + ' 1:' + _c2, + lineWidth: 0.4, + strokeOpacity: 0.2, + }, + }); + } + // hide the edges to the common nodes in other clusters + if (!ge.get('sourceNode').get('visible') || !ge.get('targetNode').get('visible')) { + ge.hide(); + } + } + // remove the cluster edges + if (sourceModel.id === model.id || targetModel.id === model.id) { + graph.removeItem(ge); + } + }); + } else { + // click on the common node, cllapse them + // calculate the cluster center + const center = { + x: 0, + y: 0, + count: 0, + exportValue: 0, + }; + nodes.forEach(function (n) { + if (n.cluster === model.cluster) { + center.x += n.x; + center.y += n.y; + center.count++; + center.exportValue += n.exportValue; + } + }); + center.x /= center.count; + center.y /= center.count; + // add cluster node on the center + const size = center.count * 1; + const clusterNodeId = 'cluster' + model.cluster; + const color = colors[model.cluster % colors.length]; + const regionStrs = model.region.split(' '); + let region = regionStrs[regionStrs.length - 1]; + if (model.region === 'East Asia') region = model.region; + let labelPosition = 'center'; + if (region.length > size) labelPosition = 'left'; + graph.addItem('node', { + x: center.x, + y: center.y, + id: clusterNodeId, + cluster: model.cluster, + region, + countries: center.count, + exportValue: center.exportValue, + style: { + color, + fill: color, + stroke: '#666', + lineWidth: 0.6, + }, + size, + label: region, + labelCfg: { + style: { + fontSize: 8.5, + }, + position: labelPosition, + }, + }); + + // add edges about the cluster + graphEdges.forEach(function (ge) { + const sourceModel = ge.get('sourceNode').getModel(); + const targetModel = ge.get('targetNode').getModel(); + if (!ge.get('sourceNode').get('visible') || !ge.get('targetNode').get('visible')) return; + if (sourceModel.cluster === model.cluster && targetModel.cluster !== model.cluster) { + let c1 = beginColor, + c2 = endColor; + if (center.x > targetModel.x) { + c1 = endColor; + c2 = beginColor; + } + graph.addItem('edge', { + source: clusterNodeId, + target: targetModel.id, + id: 'cluster-edge-' + ge.id, + style: { + stroke: 'l(0) 0:' + c1 + ' 1:' + c2, + lineWidth: 0.4, + strokeOpacity: 0.2, + }, + }); + } else if ( + targetModel.cluster === model.cluster && + sourceModel.cluster !== model.cluster + ) { + let _c3 = beginColor, + _c4 = endColor; + if (sourceModel.x > center.x) { + _c3 = endColor; + _c4 = beginColor; + } + graph.addItem('edge', { + source: sourceModel.id, + target: clusterNodeId, + id: 'cluster-edge-' + ge.id, + style: { + stroke: 'l(0) 0:' + _c3 + ' 1:' + _c4, + lineWidth: 0.4, + strokeOpacity: 0.2, + }, + }); + } + }); + + // hide the common nodes in the cluster + graphNodes.forEach(function (gn) { + if ( + gn.getModel().cluster === model.cluster && + gn.getModel().id.substr(0, 7) !== 'cluster' + ) + gn.hide(); + }); + // hide the common edges about cluster + graphEdges.forEach(function (ge) { + if (!ge.get('sourceNode').get('visible') || !ge.get('targetNode').get('visible')) + ge.hide(); + }); + } + }); + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); + +function mapValueToProp(items, valueName, propName, range) { + const valueRange = [9999999999, -9999999999]; + items.forEach(function (n) { + if (n[valueName] > valueRange[1]) valueRange[1] = n[valueName]; + if (n[valueName] < valueRange[0]) valueRange[0] = n[valueName]; + }); + const valueLength = valueRange[1] - valueRange[0]; + const rLength = range[1] - range[0]; + const propNameStrs = propName.split('.'); + if (propNameStrs[0] === 'style' && propNameStrs.length > 1) { + items.forEach(function (n) { + if (n.style === undefined) n.style = {}; + n.style[propNameStrs[1]] = + (rLength * (n[valueName] - valueRange[0])) / valueLength + range[0]; + }); + } else { + items.forEach(function (n) { + n[propNameStrs[0]] = (rLength * (n[valueName] - valueRange[0])) / valueLength + range[0]; + }); + } +} diff --git a/packages/site/examples/case/graphDemos/index.en.md b/packages/site/examples/case/graphDemos/index.en.md new file mode 100644 index 0000000000..45f3ff52dc --- /dev/null +++ b/packages/site/examples/case/graphDemos/index.en.md @@ -0,0 +1,93 @@ +--- +title: General Graph Demos +order: 0 +--- + +## General Graph Demos + +The data of general graph is not a hierarchical tree data, there might be cycles in general graph data. The differences between general graph and tree graph can be refered to [Introduction to General Graph and Tree Graph](/en/docs/manual/middle/layout/graph-layout#introduction) + +### Top 100 Words at Christmas + +This demo shows the top 100 hot words on Tweeter at Christmas eve and Christmas day. You can drag and click the nodes to explore the context of the top words. + +**'Easter Eggs'**: It is might not a real Easter Eggs since it looks like a homework: + +After implementing the graph visualization above, we found a Bug: There are lots of context nodes for ‘christmas’. It begins to swing violently as the layout was about to converge after being expanded. Welcome PR on GitHub to solve it! + + +### American Airlines with Bundling + +An American airlines graph with edge bundling powered by G6. Edge bundling helps developers to reduce the visual clutter. On large graph, edge bundling helps developers to reduce the visual clutter, and visualizes the graph with clearer trend and structures. To show more statistical information, we draw each node with a pie chart which indicates the ratio of arrival(orange) and leaving(cyan) airlines on the corresponding city. The gradient colors of the edges map the direction of airlines. The tooltip with longitude and latitude information will show up when user hovers on each node. + + + + + + +### Visualization Decision + +This is a interactive graph visualization which asisits users to find out an appropriate visualization method for their usage. The demo is combined with lots of features of G6, including custom node, custom edge, force directed layout, data change, interactions, and so on. It is applied on homepage of AntV. + +### Collapse/Expand Cluster + +This demo shows interactively collapse and expand clusters with Fruchterman layout. + +Try to click a node to collapse and expand the corresponding cluster. + + +### Large Graph Exploration + +Source Code + +Some research has found that the graph visulization is readable and interactable for end users under 500 nodes. To reach this principle for large graph, we clustering the source data by LOUVAIN algorithm, and visualize the aggregated graph first. Then, end users are able to do drilling down exploration. If the number of nodes still large on aggregated graph, we can do multi-level aggregation. To control the number of rendering nodes, the earliest expanded cluster will be collapsed automatically. These rules also help us to avoid overloaded computation and rendering on front-end. + +#### Definition + +This Demo shows the main stream of a large graph visualization solution, including demonstration, interaction, analysis. And it has a user interface, data processing set, analysis process recall, and algorithm algorithm analysis. + +#### When to Use + +In the senario of large graph visualization, the rendering and computing abilities of front-end are limit. It is hard to ensure smooth real-time interaction and analysis in large data. This solution will be a good way to handle these problem. + +#### Legend + +- Aggregated Node: A aggregated node indicates a cluster calculated by LOUVAIN, it contains several real nodes. The number on the node indicates the real nodes number of this cluster
+ +- Real Node: It is a real node of source data. The Color indicates its cluster.
+ +- Nodes with Green Dot: The green dot on the right-top of the node indicates that the node is newly added compared to last result.
+ +- Aggregated Edge: At least one end node is an aggregated node.
+ +- Real Edge: Both end nodes are real nodes. + +#### Graph Interaction + + +    + +

Each 'Aggregated Node' represents a cluster generated by LOUVAIN, it contains several 'Real Node' 。 + +**「Right Click」** any node or edge on the graph, a corresponding contextmenu will show up. + +Right click and select 'Collapse the Cluster' to collapse it, or select 'Find k-Degree Neighbor', A neighbor graph of the selected node will be merged into the current graph. + +You can also right click and select 'Collapse the Cluster' to collapse it, or select 'Find k-Degree Neighbor', A neighbor graph of the selected node will be merged into the current graph. + +#### Canavs Menu + +_It is only shown in [Full Screen Mode](/en/largegraph)_

There is a set of assistant tools on the canvas menu, which is on the left top of the canvas. From left to right, they are:
+ +- Show/Hide Edge Labels; +- Fisheye Lens; +- Lasso Select Mode; +- Find the Shortest Path (by clicking select two end nodes); +- Zoom-out; +- Fit the Graph to the View Port; +- Zoom-in; +- Search a Node(by typing the id). + +#### Notice + +The demo shows a small mocked dataset just for demonstration. Besides the functions introduced above, there are lots of other functions. We hope it is helpful for you. Explore it and have fun! diff --git a/packages/site/examples/case/graphDemos/index.zh.md b/packages/site/examples/case/graphDemos/index.zh.md new file mode 100644 index 0000000000..8769efd0af --- /dev/null +++ b/packages/site/examples/case/graphDemos/index.zh.md @@ -0,0 +1,90 @@ +--- +title: 一般图场景案例 +order: 0 +--- + +## 一般图场景案例 + +一般图是指非树嵌套结构的、可能存在环路的数据结构展示的图。一般图与树图的区别详见 [一般图与树图介绍](/zh/docs/manual/middle/layout/graph-layout#简介) + +### 图表决策 + +这是一个带交互的图表决策图,辅助用户“按使用目的”寻找合适的可视化方式。该 demo 结合了自定义节点、自定义边、力导向布局、数据切换、交互等功能,已应用在 AntV 官网。 + +### 聚类的折叠/扩展 + +使用 Fruchterman 布局实现交互式折叠/扩展聚类。尝试点击节点以折叠/扩展一个聚类。 + + +### 圣诞推文可视化 + +该案例展示了圣诞夜与圣诞节网友们在 Tweeter 上发送的推文中,出现频率 top 100 的单词。可以通过拖拽、点击等交互,查看单词的上下文。 + +**”彩蛋“**:别人家的彩蛋是彩蛋,而我们的"彩蛋"是个课堂作业: + +实现了该 demo 后,我们发现了一个小 Bug:由于 ’christmas‘ 的上下文节点众多,展开它后,在布局即将收敛时出现了疯狂的抖动鬼畜。欢迎在 GitHub 上提 PR 修复它。 + +
+ + + +### 美国航线边绑定 + +使用 G6 可视化美国航线图,使用边绑定工具降低视觉混乱。在大规模图上,使用边绑定可以降低视觉混乱,更清晰地展示图的整体走势、结构,突出航线频繁的城市,它们可能是重要的交通枢纽,并展示更多的统计信息,以便观察者进行分析。图中颜色映射航班的飞行方向(出发(橙红色)与降落(青色))。节点大小表示到达与离开该城市的航班总数量,每个节点使用了饼图展示达到(橙红色)和离开(青色)航班的比例。并增加 hover 的交互,使用 tooltip 展示每个城市的经纬度。 + + +### 大规模图下钻式探索 + +源代码 + +一些科学研究表明,不超过 500 个节点的图可视化是适合终端用户阅读和交互式探索的。根据这个原则,在大规模图上,我们将元数据中的节点通过 LOUVAIN 聚类算法进行聚合。首先展示被聚合后的图,然后用户可以通过展开聚合节点进行下钻式探索。如果一次聚合后的节点数仍然庞大,可以进行多层次的聚合。为了控制渲染节点的数量,展开多个聚类后,最早被展开的聚类将会被自动收起。这一方案除了满足上述原则,还能减少前端计算和渲染的负担。 + +#### 定义 + +该 Demo 简要演示了一种针对大规模图数据的一整套展示、交互、分析方案,包括:用户交互界面,数据处理流程,交互式探索,分析过程回溯,算法分析。 + +#### 何时使用 + +在大规模数据场景中,前端浏览器的计算、渲染性能有限,很难保证流畅的实时交互,又需要基于海量的关系数据进行探索分析时。该方案可以解决该问题。 + +#### 图例 + +- 聚合节点:代表由 Louvain 算法计算出的一个聚类,包含多个真实节点。中间的数字代表了该聚类所包含的真实节点个数。
+ +- 真实节点:原数据中的节点。颜色代表所属聚类。
+ +- 带有绿点标记的节点: 相较于上一次的结果,该标记标识了本次更新的结果中新增的聚合节点或真实节点。
+ +- 聚合边:至少一个端点是聚合节点。
+ +- 真实边: 两个端点都是真实节点。 + +#### 图交互 + + +    + +

每个“聚合点” 代表了一个 LOUVAIN 计算出的聚类,包含多个“真实节点” 。 + +**「右击」** 任意节点或边,一个相对应的上下文菜单将会出现。 + +右击 并选择“展开聚合节点”,聚合节点将会被该聚类中的真实节点替代,这就是下钻式探索。 + +你也可以通过右击 并选择“聚合该聚类”将已经展开的节点聚合;或选择 “寻找 k 度邻居”,被选中点的 k 度邻居节点将会被融合到当前图中。 + +#### 画布菜单 + +_请在[全屏模式](/zh/largegraph)下体验_

在画布左上角,有一个画布菜单,包含一系列辅助探索的工具。从左到右,它们分别是:
+ +- 显示/隐藏边标签; +- 鱼眼放大镜; +- 拉索选择模式; +- 寻找最短路径(按 SHIFT 点选两个端点); +- 缩小; +- 使图内容适应视窗; +- 放大; +- 搜索一个节点(输入 ID)。 + +#### 注意 + +该 demo 仅为展示大规模图可视化方案,因此使用的数据是一个较小的、模拟的数据集。除了上述内容外,还有很多其他的功能。愉快地探索吧。希望它对你有所帮助。 diff --git a/packages/site/examples/case/others/demo/meta.json b/packages/site/examples/case/others/demo/meta.json new file mode 100644 index 0000000000..0d294db3a8 --- /dev/null +++ b/packages/site/examples/case/others/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "metroLines.js", + "title": { + "zh": "地铁线路图", + "en": "Animated Metro Map" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*1MS4TbqujQAAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/case/others/demo/metroLines.js b/packages/site/examples/case/others/demo/metroLines.js new file mode 100644 index 0000000000..9e7c5caa11 --- /dev/null +++ b/packages/site/examples/case/others/demo/metroLines.js @@ -0,0 +1,270 @@ +import G6 from '@antv/g6'; +/** + * 该示例演示自定义节点和边实现动态地铁图效果 + * by 十吾 + */ +const colors = [ + 'rgb(64, 174, 247)', + 'rgb(108, 207, 169)', + 'rgb(157, 223, 125)', + 'rgb(240, 198, 74)', + 'rgb(221, 158, 97)', + 'rgb(141, 163, 112)', + 'rgb(115, 136, 220)', + 'rgb(133, 88, 219)', + 'rgb(203, 135, 226)', + 'rgb(227, 137, 163)', +]; +// custom the node +G6.registerNode( + 'breath-node', + { + afterDraw(cfg, group) { + const r = cfg.size / 2; + const back1 = group.addShape('circle', { + zIndex: -3, + attrs: { + x: 0, + y: 0, + r, + fill: cfg.color || (cfg.style && cfg.style.fill), + opacity: 0.6, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'back1-shape', + }); + const back2 = group.addShape('circle', { + zIndex: -2, + attrs: { + x: 0, + y: 0, + r, + fill: cfg.color, + // 为了显示清晰,随意设置了颜色 + opacity: 0.6, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'back2-shape', + }); + const back3 = group.addShape('circle', { + zIndex: -1, + attrs: { + x: 0, + y: 0, + r, + fill: cfg.color, + opacity: 0.6, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'back3-shape', + }); + group.sort(); // 排序,根据zIndex 排序 + const delayBase = Math.random() * 2000; + back1.animate( + { + // 逐渐放大,并消失 + r: r + 10, + opacity: 0.0, + }, + { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: delayBase, // 无延迟 + }, + ); + back2.animate( + { + // 逐渐放大,并消失 + r: r + 10, + opacity: 0.0, + }, + { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: delayBase + 1000, // 1 秒延迟 + }, + ); + back3.animate( + { + // 逐渐放大,并消失 + r: r + 10, + opacity: 0.0, + }, + { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: delayBase + 2000, // 2 秒延迟 + }, + ); + }, + }, + 'circle', +); + +// custom the edge +G6.registerEdge( + 'running-polyline', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = shape.getTotalLength(); + let circleCount = Math.ceil(length / 20); + circleCount = circleCount === 0 ? 1 : circleCount; + + const _loop = function _loop(i) { + const delay = Math.random() * 1000; + const start = shape.getPoint(i / circleCount); + const circle = group.addShape('circle', { + attrs: { + x: start.x, + y: start.y, + r: 0.8, + fill: '#A0F3AF', + shadowColor: '#fff', + shadowBlur: 30, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + circle.animate( + (ratio) => { + ratio += i / circleCount; + if (ratio > 1) { + ratio %= 1; + } + const tmpPoint = shape.getPoint(ratio); + return { + x: tmpPoint.x, + y: tmpPoint.y, + }; + }, + { + repeat: true, + duration: 10 * length, + easing: 'easeCubic', + delay, + }, + ); + }; + + for (let i = 0; i < circleCount; i++) { + _loop(i); + } + }, + }, + 'polyline', +); + +const graph = new G6.Graph({ + container: 'container', + width: 500, + height: 500, + modes: { + default: [ + { + type: 'edge-tooltip', + formatText: function formatText(model) { + const text = model.class; + return text; + }, + }, + ], + }, + defaultNode: { + type: 'breath-node', + size: 3, + style: { + lineWidth: 0, + fill: 'rgb(240, 223, 83)', + }, + }, + defaultEdge: { + type: 'running-polyline', + size: 1, + color: 'rgb(14,142,63)', + style: { + opacity: 0.4, + lineAppendWidth: 3, + }, + }, +}); + +const graphSize = [500, 500]; +fetch('https://gw.alipayobjects.com/os/basement_prod/8c2353b0-99a9-4a93-a5e1-3e7df1eac64f.json') + .then((res) => res.json()) + .then((data) => { + const nodes = data.nodes; + const edges = data.edges; + const classMap = new Map(); + let classId = 0; + nodes.forEach(function (node) { + node.y = -node.y; + }); + edges.forEach(function (edge) { + edge.id = `edge-${edge.id}`; + // edge cluster + if (edge.class && classMap.get(edge.class) === undefined) { + classMap.set(edge.class, classId); + classId++; + } + const cid = classMap.get(edge.class); + edge.color = colors[cid % colors.length]; + const controlPoints = edge.controlPoints; + + controlPoints.forEach(function (cp) { + cp.y = -cp.y; + }); + }); + scaleNodesPoints(nodes, edges, graphSize); + graph.data(data); + graph.render(); + }); + +graph.get('container').style.background = '#000'; +graph.get('container').style.backgroundImage = + 'url("https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*G23iRqkiibIAAAAAAAAAAABkARQnAQ")'; +graph.get('container').style.backgroundSize = '500px 500px'; +graph.get('container').style.backgroundRepeat = 'no-repeat'; + +function scaleNodesPoints(nodes, edges, graphSize) { + const size = graphSize[0] < graphSize[1] ? graphSize[0] : graphSize[1]; + let minX = 99999999999999999; + let maxX = -99999999999999999; + let minY = 99999999999999999; + let maxY = -99999999999999999; + nodes.forEach(function (node) { + if (node.x > maxX) maxX = node.x; + if (node.x < minX) minX = node.x; + if (node.y > maxY) maxY = node.y; + if (node.y < minY) minY = node.y; + }); + + edges.forEach(function (edge) { + const controlPoints = edge.controlPoints; + controlPoints.forEach(function (cp) { + if (cp.x > maxX) maxX = cp.x; + if (cp.x < minX) minX = cp.x; + if (cp.y > maxY) maxY = cp.y; + if (cp.y < minY) minY = cp.y; + }); + }); + + const xScale = maxX - minX; + const yScale = maxY - minY; + nodes.forEach(function (node) { + node.orix = node.x; + node.oriy = node.y; + node.x = ((node.x - minX) / xScale) * size; + node.y = ((node.y - minY) / yScale) * size; + }); + edges.forEach(function (edge) { + const controlPoints = edge.controlPoints; + controlPoints.forEach(function (cp) { + cp.x = ((cp.x - minX) / xScale) * size; + cp.y = ((cp.y - minY) / yScale) * size; + }); + }); +} diff --git a/packages/site/examples/case/others/index.en.md b/packages/site/examples/case/others/index.en.md new file mode 100644 index 0000000000..dada304c0e --- /dev/null +++ b/packages/site/examples/case/others/index.en.md @@ -0,0 +1,9 @@ +--- +title: Others +order: 3 +--- + +### Animated Metro Map + +Metro map with animated edges. The demo below shows the metro map with custom animated edges. + diff --git a/packages/site/examples/case/others/index.zh.md b/packages/site/examples/case/others/index.zh.md new file mode 100644 index 0000000000..48452637e7 --- /dev/null +++ b/packages/site/examples/case/others/index.zh.md @@ -0,0 +1,9 @@ +--- +title: 其它 +order: 3 +--- + +### 地铁线路图 + +基于 G6 实现的地铁线路图。演示自定义带动画的边以实现地铁线路的流动效果。 + diff --git a/packages/site/examples/case/treeDemos/API.en.md b/packages/site/examples/case/treeDemos/API.en.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/site/examples/case/treeDemos/API.zh.md b/packages/site/examples/case/treeDemos/API.zh.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/site/examples/case/treeDemos/demo/australiaFire.js b/packages/site/examples/case/treeDemos/demo/australiaFire.js new file mode 100644 index 0000000000..1fa418b225 --- /dev/null +++ b/packages/site/examples/case/treeDemos/demo/australiaFire.js @@ -0,0 +1,457 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + #legendContainer{ + position: absolute; + top: 92px; + left: 8px; + width: 100px; + height: 100px; + } + .g6-tooltip { + border-radius: 6px; + font-size: 12px; + color: #fff; + background-color: #000; + padding: 2px 8px; + text-align: center; + } + #time { + position: absolute; + color: #fff; + font-size: 25px; + top: 80px; + width: 700px; + text-align: center; + height: auto; + } +`); + +const LANG = 'en'; // 'zh' +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graphDiv = document.getElementById('container'); + +const timeDiv = document.createElement('div'); +timeDiv.id = 'time'; +timeDiv.innerHTML = '11th January, 2020'; +graphDiv.parentNode.appendChild(timeDiv); + +const colors = ['#FD5854', '#FDA25A', '#FFD574', '#3A5A3C']; +const imgs = { + 'state-New South Wales': { + src: 'https://gw.alipayobjects.com/zos/basement_prod/828aec79-8123-4ca7-baa8-1422a964003a.svg', + width: 183, + height: 146, + }, + 'state-Victoria': { + src: 'https://gw.alipayobjects.com/zos/basement_prod/463d3b0c-b03f-40b3-b6e9-6309b5d637cf.svg', + width: 116, + height: 88, + }, + 'state-Queensland': { + src: 'https://gw.alipayobjects.com/zos/basement_prod/617bc829-50e5-4537-978e-81bf424cb8fd.svg', + width: 215, + height: 311, + }, + 'state-Western Australia': { + src: 'https://gw.alipayobjects.com/zos/basement_prod/c8b4cbb0-57fd-4c18-bbd1-4993b52e5048.svg', + width: 221, + height: 357, + }, + 'state-South Australia': { + src: 'https://gw.alipayobjects.com/zos/basement_prod/6582ef8e-e5ce-4815-9183-b6a70caeb1db.svg', + width: 169, + height: 198, + }, + 'state-Tasmania': { + src: 'https://gw.alipayobjects.com/zos/basement_prod/056fb079-58c1-4697-bb66-15f08512cfb8.svg', + width: 50, + height: 45, + }, + 'state-Northern Territory': { + src: 'https://gw.alipayobjects.com/zos/basement_prod/a616ae60-ebe6-4e61-b085-1a207237e193.svg', + width: 140, + height: 243, + }, + 'country-australia': { + src: 'https://gw.alipayobjects.com/zos/basement_prod/85c5e2e2-c015-495a-8710-3c881c49a3ed.svg', + width: 559, + height: 464, + }, +}; +const mapImgScale = 0.2; +let minPop = Infinity; +let maxPop = -Infinity; +let minBrightness = Infinity; +let maxBrightness = -Infinity; +const leafSizeRange = [10, 100]; + +G6.registerNode( + 'circle-bar', + { + drawShape(cfg, group) { + const dist2Ori = Math.sqrt(cfg.x * cfg.x + cfg.y * cfg.y); + const vecToOrin = [cfg.x / dist2Ori, cfg.y / dist2Ori]; + const startPoint = [(vecToOrin[0] * cfg.size) / 2, (vecToOrin[1] * cfg.size) / 2]; + const scale = Math.sqrt(cfg.data.firePointNums[0]); + const path = [ + ['M', startPoint[0], startPoint[1]], + ['L', vecToOrin[0] * scale + startPoint[0], vecToOrin[1] * scale + startPoint[1]], + ]; + + let fillColor = colors[3]; + if (cfg.data.fireBrightnesses[0]) { + const normalizedBrightness = + (cfg.data.fireBrightnesses[0] - minBrightness) / (maxBrightness - minBrightness); // [0, 1]; + const colorIdx = Math.floor(normalizedBrightness * 2); + fillColor = colors[colorIdx]; + } + let count = 0; + const timeDiv = document.getElementById('time'); + + const keyShape = group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: cfg.size / 2, + fill: fillColor, + shadowColor: fillColor, + shadowBlur: 20, + }, + }); + const bar = group.addShape('path', { + attrs: { + path, + lineWidth: 2, + fill: fillColor, + stroke: fillColor, + shadowColor: fillColor, + shadowBlur: 20, + }, + }); + + setInterval( + () => { + // if (count >= 7) return; + bar.stopAnimate(); + count++; + if (cfg.id === 'city-Sydney') { + timeDiv.innerHTML = `${(count % 8) + 11}th January, 2020`; + } + const firePointNum = cfg.data.firePointNums[count % 8] || 0; + const targetScale = Math.sqrt(firePointNum); + const targetPath = [ + ['M', startPoint[0], startPoint[1]], + [ + 'L', + vecToOrin[0] * targetScale + startPoint[0], + vecToOrin[1] * targetScale + startPoint[1], + ], + ]; + + fillColor = colors[3]; + if (cfg.data.fireBrightnesses[count % 8]) { + const normalizedBrightness = + (cfg.data.fireBrightnesses[count % 8] - minBrightness) / + (maxBrightness - minBrightness); // [0, 1]; + const colorIdx = Math.floor(normalizedBrightness * 2); + fillColor = colors[colorIdx]; + } + bar.animate( + { + path: targetPath, + stroke: fillColor, + fill: fillColor, + shadowColor: fillColor, + }, + { + repeat: false, + duration: 300, + }, + ); + + keyShape.stopAnimate(); + keyShape.animate( + { + fill: fillColor, + shadowColor: fillColor, + }, + { + repeat: false, + duration: 300, + }, + ); + }, + 1300, + 'easeCubic', + ); + + return keyShape; + }, + }, + 'circle', +); + +const tooltip = new G6.Tooltip({ + // offsetX and offsetY include the padding of the parent container + offsetX: 10, + offsetY: 10, + // the types of items that allow the tooltip show up + // 允许出现 tooltip 的 item 类型 + itemTypes: ['node'], + // custom the tooltip's content + // 自定义 tooltip 内容 + getContent: (e) => { + const outDiv = document.createElement('div'); + const populationDes = LANG === 'en' ? 'Population' : '人口总数'; + const model = e.item.getModel(); + const name = `${model.xlabel}
${populationDes}: ${model.population}`; + outDiv.style.width = 'fit-content'; + //outDiv.style.padding = '0px 0px 20px 0px'; + outDiv.innerHTML = `
${name}
`; + return outDiv; + }, +}); + +fetch('https://gw.alipayobjects.com/os/basement_prod/d676014a-0a11-4ea9-9af4-4038bae3c0a1.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + container.style.backgroundColor = '#000'; + container.style.textAlign = 'center'; + const graph = new G6.TreeGraph({ + container: 'container', + width: height < width ? height : width, + height: height < width ? height : width, + fitView: true, + fitViewPadding: 80, + linkCenter: true, + defaultNode: { + size: 30, + style: { + lineWidth: 0, + fill: '#3A5A3C', + }, + }, + defaultEdge: { + color: '#FEAB58', + style: { + lineWidth: 0.3, + shadowBlur: 100, + shadowColor: '#FF4654', + }, + }, + nodeStateStyles: { + hover: { + stroke: '#fff', + lineWidth: 1, + }, + }, + modes: { + default: ['drag-canvas', 'zoom-canvas'], + }, + layout: { + type: 'dendrogram', + direction: 'LR', + radial: true, + nodeSep: 10, + rankSep: 100, + }, + plugins: [tooltip], + }); + + graph.node((node) => { + const xlabel = node.label; + let type = 'circle'; + if (node.isLeaf) type = 'circle-bar'; + else if (node.img) type = 'image'; + let label = node.isLeaf ? '' : node.label; + switch (label) { + case 'Australian Capital Territory': + label = 'Australian\nCapital Territory'; + break; + case 'South Australia': + label = 'South\nAustralia'; + break; + case 'New South Wales': + label = 'New South\nWales'; + break; + case 'Northern Territory': + label = 'Northern\nTerritory'; + break; + default: + break; + } + return { + type, + xlabel, + label, + labelCfg: { + position: 'center', + style: { + fill: '#fff', + fontSize: 10, + }, + }, + }; + }); + + G6.Util.traverseTree(data, (item) => { + if (minPop > item.population) minPop = item.population; + if (maxPop < item.population) maxPop = item.population; + if (item.data && item.data.fireBrightnesses) { + item.data.fireBrightnesses.forEach((b) => { + if (minBrightness > b && b) minBrightness = b; + if (maxBrightness < b) maxBrightness = b; + }); + } + }); + const sizeScale = leafSizeRange[1] - leafSizeRange[0]; + G6.Util.traverseTree(data, (item) => { + if (!item.isLeaf) { + if (imgs[item.id]) { + item.img = imgs[item.id].src; + item.size = [imgs[item.id].width * mapImgScale, imgs[item.id].height * mapImgScale]; + } else { + item.size = 10; + } + item.style = { + shadowColor: '#FF4654', + shadowBlur: 200, + }; + } else { + item.size = ((item.population - minPop) / (maxPop - minPop)) * sizeScale + leafSizeRange[0]; + } + if (item.id === 'country-australia') { + item.size = [559 * mapImgScale, 464 * mapImgScale]; + item.style.shadowBlur = 200; + } + }); + + graph.data(data); + graph.render(); + + graph.on('node:mouseenter', (e) => { + graph.setItemState(e.item, 'hover', true); + }); + graph.on('node:mouseleave', () => { + graph.getNodes().forEach((node) => { + graph.setItemState(node, 'hover', false); + }); + }); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); + +const legendContainer = document.createElement('div'); +legendContainer.id = 'legendContainer'; +const legendGraphDiv = document.createElement('div'); +legendGraphDiv.id = 'legend'; +legendContainer.appendChild(legendGraphDiv); +graphDiv.parentNode.appendChild(legendContainer); +const legendGraph = new G6.Graph({ + container: 'legend', + width: 200, + height: 200, + defaultNode: { + size: 10, + type: 'circle', + labelCfg: { + position: 'right', + offset: 10, + style: { + fill: '#fff', + }, + }, + }, +}); +const legendX = 20; +const legendBeginY = 50; +const legendYPadding = 25; +const legendData = { + nodes: [ + { + id: 'level1', + x: legendX, + y: legendBeginY, + label: '受灾情况严重', + label_en: 'Severely Affected', + style: { + fill: '#FD5854', + lineWidth: 0, + }, + }, + { + id: 'level2', + x: legendX, + y: legendBeginY + legendYPadding, + label_en: 'Affected', + label: '受灾情况一般', + style: { + fill: '#FDA25A', + lineWidth: 0, + }, + }, + { + id: 'level3', + label: '受灾情况较轻', + label_en: 'Lightly Affected', + x: legendX, + y: legendBeginY + legendYPadding * 2, + style: { + fill: '#FFD574', + lineWidth: 0, + }, + }, + { + id: 'level4', + label: '火灾未涉及', + label_en: 'Not Affected', + x: legendX, + y: legendBeginY + legendYPadding * 3, + style: { + fill: '#3A5A3C', + lineWidth: 0, + }, + }, + { + id: 'legendSize', + label: '圆面积代表城市人口总数', + label_en: 'Node Size - Population', + x: legendX, + y: legendBeginY + legendYPadding * 4, + size: 15, + style: { + fill: '#3A5A3C', + lineWidth: 0, + }, + }, + { + id: 'legendBar', + label: '受灾点数量', + label_en: 'Bar Height - # Fire Points', + x: legendX, + y: legendBeginY + legendYPadding * 5 + 10, + type: 'rect', + size: [2, 30], + style: { + fill: '#3A5A3C', + lineWidth: 0, + }, + }, + ], +}; +if (LANG === 'en') { + legendData.nodes.forEach((node) => { + node.label = node.label_en; + }); +} +legendGraph.data(legendData); +legendGraph.render(); diff --git a/packages/site/examples/case/treeDemos/demo/customFlow.js b/packages/site/examples/case/treeDemos/demo/customFlow.js new file mode 100644 index 0000000000..ba8773cd4d --- /dev/null +++ b/packages/site/examples/case/treeDemos/demo/customFlow.js @@ -0,0 +1,360 @@ +import G6 from '@antv/g6'; + +// 实际开发中把 window.AntVUtil 换成从 @antv/util 引入的相关模块 +// replace window.AntVUtil.isObject with +// import { isObject } from '@antv/util'; +const isObject = window.AntVUtil.isObject; + +/** + * Fund Transfer Demo + * by 十吾 + */ +const colorMap = { + A: '#72CC4A', + B: '#1A91FF', + C: '#FFAA15', +}; +const data = { + nodes: [ + { + id: '1', + label: 'Company1', + }, + { + id: '2', + label: 'Company2', + }, + { + id: '3', + label: 'Company3', + }, + { + id: '4', + label: 'Company4', + }, + { + id: '5', + label: 'Company5', + }, + { + id: '6', + label: 'Company6', + }, + { + id: '7', + label: 'Company7', + }, + { + id: '8', + label: 'Company8', + }, + { + id: '9', + label: 'Company9', + }, + ], + edges: [ + { + source: '1', + target: '2', + data: { + type: 'A', + amount: '100,000 Yuan', + date: '2019-08-03', + }, + }, + { + source: '1', + target: '3', + data: { + type: 'B', + amount: '100,000 Yuan', + date: '2019-08-03', + }, + }, + { + source: '2', + target: '5', + data: { + type: 'C', + amount: '100,000 Yuan', + date: '2019-08-03', + }, + }, + { + source: '5', + target: '6', + data: { + type: 'B', + amount: '100,000 Yuan', + date: '2019-08-03', + }, + }, + { + source: '3', + target: '4', + data: { + type: 'C', + amount: '100,000 Yuan', + date: '2019-08-03', + }, + }, + { + source: '4', + target: '7', + data: { + type: 'B', + amount: '100,000 Yuan', + date: '2019-08-03', + }, + }, + { + source: '1', + target: '8', + data: { + type: 'B', + amount: '100,000 Yuan', + date: '2019-08-03', + }, + }, + { + source: '1', + target: '9', + data: { + type: 'C', + amount: '100,000 Yuan', + date: '2019-08-03', + }, + }, + ], +}; + +G6.registerNode( + 'round-rect', + { + drawShape: function drawShape(cfg, group) { + const width = cfg.style.width; + const stroke = cfg.style.stroke; + const rect = group.addShape('rect', { + attrs: { + x: -width / 2, + y: -15, + width, + height: 30, + radius: 15, + stroke, + lineWidth: 1.2, + fillOpacity: 1, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', + }); + group.addShape('circle', { + attrs: { + x: -width / 2, + y: 0, + r: 3, + fill: stroke, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + group.addShape('circle', { + attrs: { + x: width / 2, + y: 0, + r: 3, + fill: stroke, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape2', + }); + return rect; + }, + getAnchorPoints: function getAnchorPoints() { + return [ + [0, 0.5], + [1, 0.5], + ]; + }, + update: function update(cfg, item) { + const group = item.getContainer(); + const children = group.get('children'); + const node = children[0]; + const circleLeft = children[1]; + const circleRight = children[2]; + + const stroke = cfg.style.stroke; + + if (stroke) { + node.attr('stroke', stroke); + circleLeft.attr('fill', stroke); + circleRight.attr('fill', stroke); + } + }, + }, + 'single-node', +); + +G6.registerEdge('fund-polyline', { + itemType: 'edge', + draw: function draw(cfg, group) { + const startPoint = cfg.startPoint; + const endPoint = cfg.endPoint; + + const Ydiff = endPoint.y - startPoint.y; + + const slope = Ydiff !== 0 ? Math.min(500 / Math.abs(Ydiff), 20) : 0; + + const cpOffset = slope > 15 ? 0 : 16; + const offset = Ydiff < 0 ? cpOffset : -cpOffset; + + const line1EndPoint = { + x: startPoint.x + slope, + y: endPoint.y + offset, + }; + const line2StartPoint = { + x: line1EndPoint.x + cpOffset, + y: endPoint.y, + }; + + // 控制点坐标 + const controlPoint = { + x: + ((line1EndPoint.x - startPoint.x) * (endPoint.y - startPoint.y)) / + (line1EndPoint.y - startPoint.y) + + startPoint.x, + y: endPoint.y, + }; + + let path = [ + ['M', startPoint.x, startPoint.y], + ['L', line1EndPoint.x, line1EndPoint.y], + ['Q', controlPoint.x, controlPoint.y, line2StartPoint.x, line2StartPoint.y], + ['L', endPoint.x, endPoint.y], + ]; + + if (Math.abs(Ydiff) <= 5) { + path = [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x, endPoint.y], + ]; + } + + const endArrow = cfg?.style && cfg.style.endArrow ? cfg.style.endArrow : false; + if (isObject(endArrow)) endArrow.fill = stroke; + const line = group.addShape('path', { + attrs: { + path, + stroke: colorMap[cfg.data && cfg.data.type], + lineWidth: 1.2, + endArrow, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + + const labelLeftOffset = 0; + const labelTopOffset = 8; + // amount + const amount = group.addShape('text', { + attrs: { + text: cfg.data && cfg.data.amount, + x: line2StartPoint.x + labelLeftOffset, + y: endPoint.y - labelTopOffset - 2, + fontSize: 14, + textAlign: 'left', + textBaseline: 'middle', + fill: '#000000D9', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape-amount', + }); + // type + group.addShape('text', { + attrs: { + text: cfg.data && cfg.data.type, + x: line2StartPoint.x + labelLeftOffset, + y: endPoint.y - labelTopOffset - amount.getBBox().height - 2, + fontSize: 10, + textAlign: 'left', + textBaseline: 'middle', + fill: '#000000D9', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape-type', + }); + // date + group.addShape('text', { + attrs: { + text: cfg.data && cfg.data.date, + x: line2StartPoint.x + labelLeftOffset, + y: endPoint.y + labelTopOffset + 4, + fontSize: 12, + fontWeight: 300, + textAlign: 'left', + textBaseline: 'middle', + fill: '#000000D9', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape-date', + }); + return line; + }, +}); + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'dagre', + rankdir: 'LR', + nodesep: 30, + ranksep: 100, + }, + modes: { + default: ['drag-canvas'], + }, + defaultNode: { + type: 'round-rect', + labelCfg: { + style: { + fill: '#000000A6', + fontSize: 10, + }, + }, + style: { + stroke: '#72CC4A', + width: 150, + }, + }, + defaultEdge: { + type: 'fund-polyline', + }, +}); + +graph.data(data); +graph.render(); + +const edges = graph.getEdges(); +edges.forEach(function (edge) { + const line = edge.getKeyShape(); + const stroke = line.attr('stroke'); + const targetNode = edge.getTarget(); + targetNode.update({ + style: { + stroke, + }, + }); +}); +graph.paint(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/case/treeDemos/demo/decisionTree.js b/packages/site/examples/case/treeDemos/demo/decisionTree.js new file mode 100644 index 0000000000..4703f9b89b --- /dev/null +++ b/packages/site/examples/case/treeDemos/demo/decisionTree.js @@ -0,0 +1,687 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +// 我们用 insert-css 演示引入自定义样式 +// 推荐将样式添加到自己的样式文件中 +// 若拷贝官方代码,别忘了 npm install insert-css +insertCss(` + .g6-component-tooltip { + background-color: rgba(0,0,0, 0.65); + padding: 10px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + width: fit-content; + color: #fff; + border-radius = 4px; + } +`); + +// mocked data +const mockData = { + id: 'g1', + name: 'Name1', + count: 123456, + label: '538.90', + currency: 'Yuan', + rate: 1.0, + status: 'B', + variableName: 'V1', + variableValue: 0.341, + variableUp: false, + children: [ + { + id: 'g12', + name: 'Deal with LONG label LONG label LONG label LONG label', + count: 123456, + label: '338.00', + rate: 0.627, + status: 'R', + currency: 'Yuan', + variableName: 'V2', + variableValue: 0.179, + variableUp: true, + children: [ + { + id: 'g121', + name: 'Name3', + collapsed: true, + count: 123456, + label: '138.00', + rate: 0.123, + status: 'B', + currency: 'Yuan', + variableName: 'V2', + variableValue: 0.27, + variableUp: true, + children: [ + { + id: 'g1211', + name: 'Name4', + count: 123456, + label: '138.00', + rate: 1.0, + status: 'B', + currency: 'Yuan', + variableName: 'V1', + variableValue: 0.164, + variableUp: false, + children: [], + }, + ], + }, + { + id: 'g122', + name: 'Name5', + collapsed: true, + count: 123456, + label: '100.00', + rate: 0.296, + status: 'G', + currency: 'Yuan', + variableName: 'V1', + variableValue: 0.259, + variableUp: true, + children: [ + { + id: 'g1221', + name: 'Name6', + count: 123456, + label: '40.00', + rate: 0.4, + status: 'G', + currency: 'Yuan', + variableName: 'V1', + variableValue: 0.135, + variableUp: true, + children: [ + { + id: 'g12211', + name: 'Name6-1', + count: 123456, + label: '40.00', + rate: 1.0, + status: 'R', + currency: 'Yuan', + variableName: 'V1', + variableValue: 0.181, + variableUp: true, + children: [], + }, + ], + }, + { + id: 'g1222', + name: 'Name7', + count: 123456, + label: '60.00', + rate: 0.6, + status: 'G', + currency: 'Yuan', + variableName: 'V1', + variableValue: 0.239, + variableUp: false, + children: [], + }, + ], + }, + { + id: 'g123', + name: 'Name8', + collapsed: true, + count: 123456, + label: '100.00', + rate: 0.296, + status: 'DI', + currency: 'Yuan', + variableName: 'V2', + variableValue: 0.131, + variableUp: false, + children: [ + { + id: 'g1231', + name: 'Name8-1', + count: 123456, + label: '100.00', + rate: 1.0, + status: 'DI', + currency: 'Yuan', + variableName: 'V2', + variableValue: 0.131, + variableUp: false, + children: [], + }, + ], + }, + ], + }, + { + id: 'g13', + name: 'Name9', + count: 123456, + label: '100.90', + rate: 0.187, + status: 'B', + currency: 'Yuan', + variableName: 'V2', + variableValue: 0.221, + variableUp: true, + children: [ + { + id: 'g131', + name: 'Name10', + count: 123456, + label: '33.90', + rate: 0.336, + status: 'R', + currency: 'Yuan', + variableName: 'V1', + variableValue: 0.12, + variableUp: true, + children: [], + }, + { + id: 'g132', + name: 'Name11', + count: 123456, + label: '67.00', + rate: 0.664, + status: 'G', + currency: 'Yuan', + variableName: 'V1', + variableValue: 0.241, + variableUp: false, + children: [], + }, + ], + }, + { + id: 'g14', + name: 'Name12', + count: 123456, + label: '100.00', + rate: 0.186, + status: 'G', + currency: 'Yuan', + variableName: 'V2', + variableValue: 0.531, + variableUp: true, + children: [], + }, + ], +}; + +const colors = { + B: '#5B8FF9', + R: '#F46649', + Y: '#EEBC20', + G: '#5BD8A6', + DI: '#A7A7A7', +}; + +// 组件props +const props = { + data: mockData, + config: { + padding: [20, 50], + defaultLevel: 3, + defaultZoom: 0.8, + modes: { default: ['zoom-canvas', 'drag-canvas'] }, + }, +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +// 默认配置 +const defaultConfig = { + width, + height, + modes: { + default: ['zoom-canvas', 'drag-canvas'], + }, + fitView: true, + animate: true, + defaultNode: { + type: 'flow-rect', + }, + defaultEdge: { + type: 'cubic-horizontal', + style: { + stroke: '#CED4D9', + }, + }, + layout: { + type: 'indented', + direction: 'LR', + dropCap: false, + indent: 300, + getHeight: () => { + return 60; + }, + }, +}; + +// 自定义节点、边 +const registerFn = () => { + /** + * 自定义节点 + */ + G6.registerNode( + 'flow-rect', + { + shapeType: 'flow-rect', + draw(cfg, group) { + const { + name = '', + variableName, + variableValue, + variableUp, + label, + collapsed, + currency, + status, + rate + } = cfg; + + const grey = '#CED4D9'; + const rectConfig = { + width: 202, + height: 60, + lineWidth: 1, + fontSize: 12, + fill: '#fff', + radius: 4, + stroke: grey, + opacity: 1, + }; + + const nodeOrigin = { + x: -rectConfig.width / 2, + y: -rectConfig.height / 2, + }; + + const textConfig = { + textAlign: 'left', + textBaseline: 'bottom', + }; + + const rect = group.addShape('rect', { + attrs: { + x: nodeOrigin.x, + y: nodeOrigin.y, + ...rectConfig, + }, + }); + + const rectBBox = rect.getBBox(); + + // label title + group.addShape('text', { + attrs: { + ...textConfig, + x: 12 + nodeOrigin.x, + y: 20 + nodeOrigin.y, + text: name.length > 28 ? name.substr(0, 28) + '...' : name, + fontSize: 12, + opacity: 0.85, + fill: '#000', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'name-shape', + }); + + // price + const price = group.addShape('text', { + attrs: { + ...textConfig, + x: 12 + nodeOrigin.x, + y: rectBBox.maxY - 12, + text: label, + fontSize: 16, + fill: '#000', + opacity: 0.85, + }, + }); + + // label currency + group.addShape('text', { + attrs: { + ...textConfig, + x: price.getBBox().maxX + 5, + y: rectBBox.maxY - 12, + text: currency, + fontSize: 12, + fill: '#000', + opacity: 0.75, + }, + }); + + // percentage + const percentText = group.addShape('text', { + attrs: { + ...textConfig, + x: rectBBox.maxX - 8, + y: rectBBox.maxY - 12, + text: `${((variableValue || 0) * 100).toFixed(2)}%`, + fontSize: 12, + textAlign: 'right', + fill: colors[status], + }, + }); + + // percentage triangle + const symbol = variableUp ? 'triangle' : 'triangle-down'; + const triangle = group.addShape('marker', { + attrs: { + ...textConfig, + x: percentText.getBBox().minX - 10, + y: rectBBox.maxY - 12 - 6, + symbol, + r: 6, + fill: colors[status], + }, + }); + + // variable name + group.addShape('text', { + attrs: { + ...textConfig, + x: triangle.getBBox().minX - 4, + y: rectBBox.maxY - 12, + text: variableName, + fontSize: 12, + textAlign: 'right', + fill: '#000', + opacity: 0.45, + }, + }); + + // bottom line background + const bottomBackRect = group.addShape('rect', { + attrs: { + x: nodeOrigin.x, + y: rectBBox.maxY - 4, + width: rectConfig.width, + height: 4, + radius: [0, 0, rectConfig.radius, rectConfig.radius], + fill: '#E0DFE3', + }, + }); + + // bottom percent + const bottomRect = group.addShape('rect', { + attrs: { + x: nodeOrigin.x, + y: rectBBox.maxY - 4, + width: rate * rectBBox.width, + height: 4, + radius: [0, 0, 0, rectConfig.radius], + fill: colors[status], + }, + }); + + // collapse rect + if (cfg.children && cfg.children.length) { + group.addShape('rect', { + attrs: { + x: rectConfig.width / 2 - 8, + y: -8, + width: 16, + height: 16, + stroke: 'rgba(0, 0, 0, 0.25)', + cursor: 'pointer', + fill: '#fff', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'collapse-back', + modelId: cfg.id, + }); + + // collpase text + group.addShape('text', { + attrs: { + x: rectConfig.width / 2, + y: -1, + textAlign: 'center', + textBaseline: 'middle', + text: collapsed ? '+' : '-', + fontSize: 16, + cursor: 'pointer', + fill: 'rgba(0, 0, 0, 0.25)', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'collapse-text', + modelId: cfg.id, + }); + } + + this.drawLinkPoints(cfg, group); + return rect; + }, + update(cfg, item) { + const { level, status, name } = cfg; + const group = item.getContainer(); + let mask = group.find(ele => ele.get('name') === 'mask-shape'); + let maskLabel = group.find(ele => ele.get('name') === 'mask-label-shape'); + if (level === 0) { + group.get('children').forEach(child => { + if (child.get('name')?.includes('collapse')) return; + child.hide(); + }) + if (!mask) { + mask = group.addShape('rect', { + attrs: { + x: -101, + y: -30, + width: 202, + height: 60, + opacity: 0, + fill: colors[status] + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'mask-shape', + }); + maskLabel = group.addShape('text', { + attrs: { + fill: '#fff', + fontSize: 20, + x: 0, + y: 10, + text: name.length > 28 ? name.substr(0, 16) + '...' : name, + textAlign: 'center', + opacity: 0, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'mask-label-shape', + }); + const collapseRect = group.find(ele => ele.get('name') === 'collapse-back'); + const collapseText = group.find(ele => ele.get('name') === 'collapse-text'); + collapseRect?.toFront(); + collapseText?.toFront(); + } else { + mask.show(); + maskLabel.show(); + } + mask.animate({ opacity: 1 }, 200); + maskLabel.animate({ opacity: 1 }, 200); + return mask; + } else { + group.get('children').forEach(child => { + if (child.get('name')?.includes('collapse')) return; + child.show(); + }) + mask?.animate({ opacity: 0 }, { + duration: 200, + callback: () => mask.hide() + }); + maskLabel?.animate({ opacity: 0 }, { + duration: 200, + callback: () => maskLabel.hide() + }); + } + this.updateLinkPoints(cfg, group); + }, + setState(name, value, item) { + if (name === 'collapse') { + const group = item.getContainer(); + const collapseText = group.find((e) => e.get('name') === 'collapse-text'); + if (collapseText) { + if (!value) { + collapseText.attr({ + text: '-', + }); + } else { + collapseText.attr({ + text: '+', + }); + } + } + } + }, + getAnchorPoints() { + return [ + [0, 0.5], + [1, 0.5], + ]; + }, + }, + 'rect', + ); + + G6.registerEdge( + 'flow-cubic', + { + getControlPoints(cfg) { + let controlPoints = cfg.controlPoints; // 指定controlPoints + if (!controlPoints || !controlPoints.length) { + const { startPoint, endPoint, sourceNode, targetNode } = cfg; + const { x: startX, y: startY, coefficientX, coefficientY } = sourceNode + ? sourceNode.getModel() + : startPoint; + const { x: endX, y: endY } = targetNode ? targetNode.getModel() : endPoint; + let curveStart = (endX - startX) * coefficientX; + let curveEnd = (endY - startY) * coefficientY; + curveStart = curveStart > 40 ? 40 : curveStart; + curveEnd = curveEnd < -30 ? curveEnd : -30; + controlPoints = [ + { x: startPoint.x + curveStart, y: startPoint.y }, + { x: endPoint.x + curveEnd, y: endPoint.y }, + ]; + } + return controlPoints; + }, + getPath(points) { + const path = []; + path.push(['M', points[0].x, points[0].y]); + path.push([ + 'C', + points[1].x, + points[1].y, + points[2].x, + points[2].y, + points[3].x, + points[3].y, + ]); + return path; + }, + }, + 'single-line', + ); +}; + +registerFn(); + +const { data } = props; +let graph = null; + +const initGraph = (data) => { + if (!data) { + return; + } + const { onInit, config } = props; + const tooltip = new G6.Tooltip({ + // offsetX and offsetY include the padding of the parent container + offsetX: 20, + offsetY: 30, + // the types of items that allow the tooltip show up + // 允许出现 tooltip 的 item 类型 + itemTypes: ['node'], + // custom the tooltip's content + // 自定义 tooltip 内容 + getContent: (e) => { + const outDiv = document.createElement('div'); + //outDiv.style.padding = '0px 0px 20px 0px'; + const nodeName = e.item.getModel().name; + let formatedNodeName = ''; + for (let i = 0; i < nodeName.length; i++) { + formatedNodeName = `${formatedNodeName}${nodeName[i]}`; + if (i !== 0 && i % 20 === 0) formatedNodeName = `${formatedNodeName}
`; + } + outDiv.innerHTML = `${formatedNodeName}`; + return outDiv; + }, + shouldBegin: (e) => { + if (e.target.get('name') === 'name-shape' || e.target.get('name') === 'mask-label-shape') return true; + return false; + }, + }); + graph = new G6.TreeGraph({ + container: 'container', + ...defaultConfig, + ...config, + plugins: [tooltip], + }); + if (typeof onInit === 'function') { + onInit(graph); + } + graph.data(data); + graph.render(); + + const handleCollapse = (e) => { + const target = e.target; + const id = target.get('modelId'); + const item = graph.findById(id); + const nodeModel = item.getModel(); + nodeModel.collapsed = !nodeModel.collapsed; + graph.layout(); + graph.setItemState(item, 'collapse', nodeModel.collapsed); + }; + graph.on('collapse-text:click', (e) => { + handleCollapse(e); + }); + graph.on('collapse-back:click', (e) => { + handleCollapse(e); + }); + + // 监听画布缩放,缩小到一定程度,节点显示缩略样式 + let currentLevel = 1; + const briefZoomThreshold = Math.max(graph.getZoom(), 0.5); + graph.on('viewportchange', e => { + if (e.action !== 'zoom') return; + const currentZoom = graph.getZoom(); + let toLevel = currentLevel; + if (currentZoom < briefZoomThreshold) { + toLevel = 0; + } else { + toLevel = 1; + } + if (toLevel !== currentLevel) { + currentLevel = toLevel; + graph.getNodes().forEach(node => { + graph.updateItem(node, { + level: toLevel + }) + }) + } + }); +}; + +initGraph(data); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/case/treeDemos/demo/indentedTree.js b/packages/site/examples/case/treeDemos/demo/indentedTree.js new file mode 100644 index 0000000000..000407b2ce --- /dev/null +++ b/packages/site/examples/case/treeDemos/demo/indentedTree.js @@ -0,0 +1,1572 @@ +import G6 from '@antv/g6'; + +const { Util } = G6; + + +const rawData = { + label: 'Modeling Methods', + id: '0', + isRoot: true, + children: [ + { + label: 'Classification', + id: '0-1', + color: '#5AD8A6', + children: [ + { + label: 'Logistic regression', + id: '0-1-1', + }, + { + label: 'Linear discriminant analysis', + id: '0-1-2', + }, + { + label: 'Rules', + id: '0-1-3', + }, + { + label: 'Decision trees', + id: '0-1-4', + }, + { + label: 'Naive Bayes', + id: '0-1-5', + }, + { + label: 'K nearest neighbor', + id: '0-1-6', + }, + { + label: 'Probabilistic neural network', + id: '0-1-7', + }, + { + label: 'Support vector machine', + id: '0-1-8', + }, + ], + }, + { + label: 'Consensus', + id: '0-2', + color: '#F6BD16', + children: [ + { + label: 'Models diversity', + id: '0-2-1', + children: [ + { + label: 'Different initializations', + id: '0-2-1-1', + }, + { + label: 'Different parameter choices', + id: '0-2-1-2', + }, + { + label: 'Different architectures', + id: '0-2-1-3', + }, + { + label: 'Different modeling methods', + id: '0-2-1-4', + }, + { + label: 'Different training sets', + id: '0-2-1-5', + }, + { + label: 'Different feature sets', + id: '0-2-1-6', + }, + ], + }, + { + label: 'Methods', + id: '0-2-2', + children: [ + { + label: 'Classifier selection', + id: '0-2-2-1', + }, + { + label: 'Classifier fusion', + id: '0-2-2-2', + }, + ], + }, + { + label: 'Common', + id: '0-2-3', + children: [ + { + label: 'Bagging', + id: '0-2-3-1', + }, + { + label: 'Boosting', + id: '0-2-3-2', + }, + { + label: 'AdaBoost', + id: '0-2-3-3', + }, + ], + }, + ], + }, + { + label: 'Regression', + id: '0-3', + color: '#269A99', + children: [ + { + label: 'Multiple linear regression', + id: '0-3-1', + }, + { + label: 'Partial least squares', + id: '0-3-2', + }, + { + label: 'Multi-layer feedforward neural network', + id: '0-3-3', + }, + { + label: 'General regression neural network', + id: '0-3-4', + }, + { + label: 'Support vector regression', + id: '0-3-5', + }, + ], + }, + ], +}; + +const COLORS = ['#5B8FF9', '#F6BD16', '#5AD8A6', '#945FB9', '#E86452', '#6DC8EC', '#FF99C3', '#1E9493', '#FF9845', '#5D7092']; +const BaseConfig = { + nameFontSize: 12, + childCountWidth: 22, + countMarginLeft: 0, + propertyCountWidth: 30, + itemPadding: 16, + selectedIconWidth: 12, + nameMarginLeft: 4, + rootPadding: 18, +}; +const KeyStyle = { + background: { + default: '', + hover: '#e8f7ff', + selected: '#e8f7ff', + actived: '#e8f7ff', + }, + borderColor: { + default: '', + hover: '#1890FF', + selected: '#1890FF', + actived: '#1890FF', + }, +}; +const ChildCountStyle = { + background: { + default: '#fff', + hover: '#E8F7FF', + expanded: '#E6FFFB', + expandedHover: '#CEF5EF', + }, + borderColor: { + default: '#1890ff', + expanded: '#5CDBD3', + }, +}; +const NameStyle = { + color: { + default: 'rgba(0, 0, 0, .65)', + dash: 'rgba(0, 0, 0, .3)', + hover: '#40A8FF', + actived: '#40A8FF', + selected: '#40A8FF', + }, +}; +const MainStyle = { + background: { + default: '#fff', + hover: '#e8f7ff', + actived: '#e8f7ff', + selected: '#e8f7ff', + }, +}; + + +G6.registerNode('treeNode', { + options: { + style: { + fill: '#e8f7ff', + }, + stateStyles: { + hover: { + fillOpacity: 0.6, + }, + selected: {}, + }, + }, + addLabel(group, label, x, y) { + return group.addShape('text', { + attrs: { + text: label, + x: x * 2, + y, + textAlign: 'left', + textBaseline: 'top', + fontFamily: 'PingFangSC-Regular', + }, + cursor: 'pointer', + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'name-text-shape', + }); + }, + addBottomLine(group, rootNode, props) { + if (!rootNode) { + const { x, width, stroke, lineWidth } = props; + return group.addShape('path', { + attrs: { + path: [ + ['M', x - 1, 0], + ['L', width, 0], + ], + stroke, + lineWidth, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'node-path-shape', + }); + } + }, + addHoverBack(group, rootNode, props) { + const { + hasChildren, + width, + selectedIconWidth, + childCountWidth, + selected, + fill, + height, + selectedUrl, + x, + y, + mainX, + mainY + } = props; + + if (!rootNode) { + // hover背景 + let backgroundWidth = hasChildren ? width - childCountWidth : width; + + if (!selected) { + backgroundWidth -= selectedIconWidth; + } + group.addShape('rect', { + attrs: { + x: mainX, + y: mainY, + width: backgroundWidth, + height, + radius: 14, + fill: fill, + cursor: 'pointer', + }, + // capture: false, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'main-shape', + }); + + // 选中状态 + group.addShape('image', { + attrs: { + img: selected ? selectedUrl : '', + x, + y: y - 6, + width: selectedIconWidth, + height: 12, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'main-selected-shape', + }); + } + }, + addName(group, rootNode, props) { + const { + label, + mainX, + rootPadding, + fill, + selected, + selectedIconWidth, + nameMarginLeft, + y + } = props; + if (rootNode) { + group.addShape('text', { + attrs: { + text: label, + x: mainX + rootPadding, + y: 1, + textAlign: 'left', + textBaseline: 'middle', + fill, + fontSize: 12, + fontFamily: 'PingFangSC-Regular', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'root-text-shape', + }); + } else { + group.addShape('text', { + attrs: { + text: label, + x: selected ? mainX + 6 + selectedIconWidth + nameMarginLeft : mainX + 6, + y: y - 5, + textAlign: 'start', + textBaseline: 'top', + fill, + fontSize: 12, + fontFamily: 'PingFangSC-Regular', + cursor: 'pointer' + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'not-root-text-shape', + }); + } + }, + addChildCount(group, rootNode, props) { + const { + hasChildren, width, childCountWidth, collapsed, schemaType + } = props + if (hasChildren && !rootNode) { + const childCountHeight = 12; + const childCountX = width - childCountWidth; + const childCountY = -childCountHeight / 2; + + group.addShape('rect', { + attrs: { + width: childCountWidth, + height: 12, + stroke: collapsed + ? ChildCountStyle.borderColor.default + : ChildCountStyle.borderColor.expanded, + fill: collapsed + ? ChildCountStyle.background.default + : ChildCountStyle.background.expanded, + x: childCountX, + y: childCountY, + radius: 6, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-rect-shape', + }); + group.addShape('text', { + attrs: { + text: schemaType ? `${schemaType.subTypeCount}` : '0', + fill: 'rgba(0, 0, 0, .65)', + x: childCountX + childCountWidth / 2, + y: childCountY + 12, + fontSize: 10, + width: childCountWidth, + textAlign: 'center', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-text-shape', + }); + } + }, + draw(model, group) { + const { collapsed, selected, isInclude, children, label } = model; + const schemaType = model.schemaType; + const hasChildren = children?.length; + const { + childCountWidth, + countMarginLeft, + itemPadding, + selectedIconWidth, + nameMarginLeft, + rootPadding, + } = BaseConfig; + + let width = 0; + const height = 28; + // let x = -width / 2; + const x = 0; + const y = -height / 2; + const borderRadius = 4; + // 名称文本 + const text = this.addLabel(group, label, x, y); + const textWidth = text.getBBox().width; + width = textWidth + itemPadding + selectedIconWidth + nameMarginLeft; + + width = width < minWidth ? minWidth : width; + + if (!rootNode && hasChildren) { + width += countMarginLeft; + width += childCountWidth; + } + + const keyShapeAttrs = { + x, + y, + width, + height, + radius: borderRadius, + fill: undefined, + stroke: undefined, + }; + // keyShape根节点选中样式 + if (rootNode && selected) { + keyShapeAttrs.fill = KeyStyle.background.selected; + keyShapeAttrs.stroke = KeyStyle.borderColor.selected; + } + const keyShape = group.addShape('rect', { + attrs: keyShapeAttrs, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'root-key-shape-rect-shape', + }); + + this.addBottomLine(group, rootNode, { + stroke: '#AAB7C4', + lineWidth: 1, + x, + width + }) + + const mainX = x - 6; + const mainY = -height - 1; + + const hoverBackProps = { + hasChildren, width, selectedIconWidth, + childCountWidth, + selected, + fill: MainStyle.background.default, + height, + selectedUrl, + x, + y, + mainX, + mainY + }; + this.addHoverBack(group, rootNode, hoverBackProps); + + let nameColor = NameStyle.color.default; + if (selected) { + nameColor = NameStyle.color.selected; + } + + if (model.showInclude && !isInclude) { + nameColor = NameStyle.color.dash; + } + + // 名称 + const nameProps = { + label, + mainX, + rootPadding, + fill: nameColor, + selected, + selectedIconWidth, + nameMarginLeft, + y + }; + this.addName(group, rootNode, nameProps); + + // 子类数量 + const childCountProps = { + hasChildren, width, childCountWidth, collapsed, schemaType + } + this.addChildCount(group, rootNode, childCountProps); + + return keyShape; + }, +}); + +G6.registerNode('indentedRoot', { + draw(model, group) { + const rootColor = '#576286'; + const keyShape = group.addShape('rect', { + attrs: { + x: -46, + y: -16, + width: 92, + height: 32, + fill: rootColor, + radius: 2, + stroke: '#5B8FF9', + lineWidth: model.selected ? 2 : 0, + cursor: 'pointer' + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'key-shape' + }) + + if (model.label) { + const text = group.addShape('text', { + attrs: { + text: model.label, + fill: "#fff", + fontSize: 12, + x: 0, + y: 0, + textAlign: 'center', + textBaseline: 'middle', + cursor: 'pointer' + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'root-text-shape' + }); + const textBBox = text.getBBox(); + const width = textBBox.width + 24; + const height = textBBox.height + 12; + keyShape.attr({ + x: -width / 2, + y: -height / 2, + width, + height + }) + } + + const { collapsed, children } = model; + const hasChildren = children?.length; + let clickCircleY = 22; + // 子类数量,绘制圆点在节点正下方 + if (hasChildren) { + const schemaType = model.schemaType; + const childCountGroup = group.addGroup({ + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-group' + }) + childCountGroup.setMatrix([1, 0, 0, 0, 1, 0, 0, clickCircleY, 1]) + const countBackWidth = collapsed ? 26 : 12; + childCountGroup.addShape('rect', { + attrs: { + width: countBackWidth, + height: 12, + radius: 6, + stroke: rootColor, + lineWidth: 2, + fill: collapsed ? rootColor : '#fff', + x: -countBackWidth / 2, + y: -6, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-rect-shape', + }); + const childCountText = childCountGroup.addShape('text', { + attrs: { + text: schemaType ? `${schemaType.subTypeCount}` : '0', + fill: '#fff', + x: 0, + y: 0, + fontSize: 10, + textAlign: 'center', + textBaseline: 'middle', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-text-shape', + }); + const childHoverIcon = childCountGroup.addShape('path', { + attrs: { + stroke: '#fff', + lineWidth: 1, + cursor: 'pointer', + path: [['M', -3, 2], ['L', 0, -2], ['L', 3, 2]] + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-expand-icon', + capture: false + }); + childCountGroup.addShape('path', { + attrs: { + path: [['M', 0, -13], ['L', 0, -6]], + stroke: rootColor, + lineWidth: 2, + } + }) + childHoverIcon.hide(); + + // 连接 count 的线段 + const countLink = group.addShape('path', { + attrs: { + path: [['M', 0, 0], ['L', 0, 11]], + stroke: model.branchColor, + lineWidth: 2, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'count-link' + }); + countLink.toBack(); + + if (collapsed) { + childCountGroup.show(); + childCountText.show(); + countLink.show(); + } + else { + childCountGroup.hide(); + childCountText.hide(); + countLink.hide(); + } + clickCircleY += 16; + } + + // 增加子节点 icon + const addChildIcon = group.addShape('marker', { + attrs: { + x: 0, + y: clickCircleY, + r: 6, + symbol: G6.Marker.expand, + stroke: '#999', + fill: '#fff', + lineWidth: 1, + cursor: 'pointer' + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'add-child-icon', + }); + + addChildIcon.hide(); + // 连接增加子节点 icon 的线段 + const addChildLink = group.addShape('path', { + attrs: { + path: [['M', 0, clickCircleY - 10], ['L', 0, clickCircleY]], + stroke: model.branchColor, + lineWidth: 2, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'add-child-link' + }); + addChildLink.toBack(); + addChildLink.hide(); + + const bbox = keyShape.getBBox(); + const backContainer = group.addShape('path', { + attrs: { + path: hasChildren ? [ + ['M', bbox.minX, bbox.minY], + ['L', bbox.maxX, bbox.minY], + ['L', bbox.maxX, bbox.maxY], + ['L', 10, bbox.maxY], + ['L', 10, bbox.maxY + 28], + ['L', -10, bbox.maxY + 28], + ['L', -10, bbox.maxY], + ['L', bbox.minX, bbox.maxY], + ['Z'] + ] : [ + ['M', bbox.minX, bbox.minY], + ['L', bbox.maxX, bbox.minY], + ['L', bbox.maxX, bbox.maxY], + ['L', 10, bbox.maxY], + ['L', 10, bbox.maxY + 14], + ['L', -10, bbox.maxY + 14], + ['L', -10, bbox.maxY], + ['L', bbox.minX, bbox.maxY], + ['Z'] + ], + fill: '#fff', + opacity: 0 + }, + draggable: true + }) + backContainer.toBack(); + return keyShape; + }, + getAnchorPoints() { + return [ + [0.5, 1], + ]; + }, + update: undefined, + setState(name, value, node) { + if (name === 'closest') { + const keyShape = node.getKeyShape(); + if (value) keyShape.attr('lineWidth', 2); + else if (!node.getModel().selected) keyShape.attr('lineWidth', 1); + } + } +}); + +G6.registerNode('indentedNode', { + addChildCount(group, tag, props) { + const { collapsed, branchColor, count } = props; + let clickCircleY = 10; + // 子类数量 icon,绘制圆点在节点正下方 + if (tag) { + const childCountGroup = group.addGroup({ + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-group' + }); + childCountGroup.setMatrix([1, 0, 0, 0, 1, 0, 0, clickCircleY, 1]) + const countBackWidth = collapsed ? 26 : 12; + childCountGroup.addShape('rect', { + attrs: { + width: countBackWidth, + height: 12, + radius: 6, + stroke: branchColor, + lineWidth: 2, + fill: collapsed ? branchColor : '#fff', + x: -countBackWidth / 2, + y: -6, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-rect-shape', + }); + const childCountText = childCountGroup.addShape('text', { + attrs: { + text: count, + fill: '#fff', + x: 0, + y: 0, + fontSize: 10, + textAlign: 'center', + textBaseline: 'middle', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-text-shape', + }); + const childHoverIcon = childCountGroup.addShape('path', { + attrs: { + stroke: '#fff', + lineWidth: 1, + cursor: 'pointer', + path: [['M', -3, 2], ['L', 0, -2], ['L', 3, 2]] + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-expand-icon', + capture: false + }); + childHoverIcon.hide(); + + // 连接 count 的线段 + const countLink = group.addShape('path', { + attrs: { + path: [['M', 0, 0], ['L', 0, 11]], + stroke: branchColor, + lineWidth: 2, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'count-link' + }); + countLink.toBack(); + + if (collapsed) { + childCountGroup.show(); + childCountText.show(); + countLink.show(); + } + else { + childCountGroup.hide(); + childCountText.hide(); + countLink.hide(); + } + + clickCircleY += 16; + } + + // 增加子节点 icon + const addChildIcon = group.addShape('marker', { + attrs: { + x: 0, + y: clickCircleY, + r: 6, + symbol: G6.Marker.expand, + stroke: '#999', + fill: '#fff', + lineWidth: 1, + cursor: 'pointer' + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'add-child-icon', + }); + addChildIcon.hide(); + + // 连接增加子节点 icon 的线段 + const addChildLink = group.addShape('path', { + attrs: { + path: [['M', 0, clickCircleY - 10], ['L', 0, clickCircleY]], + stroke: branchColor, + lineWidth: 2, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'add-child-link' + }); + addChildLink.toBack(); + addChildLink.hide(); + }, + addHoverBack(group, props) { + const { mainX, mainY, width, height, fill } = props + group.addShape('rect', { + attrs: { + x: mainX, + y: mainY, + width, + height, + radius: 11, + fill, + cursor: 'pointer' + }, + // capture: false, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'main-shape', + draggable: true + }); + }, + addName(group, props) { + const { label, x = 0, y, fill } = props; + return group.addShape('text', { + attrs: { + text: label, + x, + y, + textAlign: 'start', + textBaseline: 'top', + fill, + fontSize: 14, + fontFamily: 'PingFangSC-Regular', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'not-root-text-shape', + draggable: true + }); + }, + draw(model, group) { + const { collapsed, selected, depth, label } = model; + // 是否为根节点 + const rootNode = depth === 0; + // 子节点数量 + const childCount = model.children?.length || 0; + + const { + itemPadding, + nameMarginLeft, + } = BaseConfig; + + let width = 0; + const height = 24; + // let x = -width / 2; + const x = 0; + const y = -height / 2; + const borderRadius = 4; + // 名称文本 + const text = this.addName(group, { label, x, y }); + + let textWidth = text.getBBox().width; + width = textWidth + itemPadding + nameMarginLeft; + + const keyShapeAttrs = { + x, + y, + width, + height, + radius: borderRadius, + fill: undefined, + stroke: undefined, + }; + + const keyShape = group.addShape('rect', { + attrs: keyShapeAttrs, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'root-key-shape-rect-shape', + }); + + // 底部横线 + // const branchThick = model.depth <= 2 ? 3 : model.depth <= 4 ? 2 : 1; + const bottomLine = this.addBottomLine(group, false, { + stroke: model.branchColor || '#AAB7C4', + lineWidth: 3, // model.lineWidth || branchThick || 1, + x, + width + }); + + const mainX = x - 6; + const mainY = -height - 6; + + // hover背景 + this.addHoverBack(group, { + fill: selected ? MainStyle.background.hover : MainStyle.background.default, + height, + width, + mainX, + mainY, + }); + + let nameColor = NameStyle.color.default; + if (selected) { + nameColor = NameStyle.color.hover; + } + + // 名称 + text.attr({ + y: y - 12, + fill: nameColor + }); + text.toFront(); + textWidth = text.getBBox().width; + + if (bottomLine) bottomLine.toFront(); + + this.addChildCount(group, childCount && !rootNode, { + collapsed, + branchColor: model.branchColor, + count: childCount ? `${childCount}` : undefined, + }); + + const bbox = group.getBBox(); + const backContainer = group.addShape('path', { + attrs: { + path: childCount ? [ + ['M', bbox.minX, bbox.minY], + ['L', bbox.maxX, bbox.minY], + ['L', bbox.maxX, bbox.maxY], + ['L', bbox.minX + 20, bbox.maxY], + ['L', bbox.minX + 20, bbox.maxY + 20], + ['L', bbox.minX, bbox.maxY + 20], + ['Z'] + ] : [ + ['M', bbox.minX, bbox.minY], + ['L', bbox.maxX, bbox.minY], + ['L', bbox.maxX, bbox.maxY], + ['L', bbox.minX, bbox.maxY], + ['Z'] + ], + fill: '#fff', + opacity: 0 + }, + draggable: true + }) + backContainer.toBack(); + return keyShape; + }, + setState(name, value, node) { + if (name === 'closest' || name === 'selected') { + const group = node.getContainer(); + const textShape = group.find(child => child.get('name') === 'not-root-text-shape'); + const mainShape = group.find(child => child.get('name') === 'main-shape'); + if (value) { + if (textShape) { + textShape.attr('fill', NameStyle.color.hover); + } + if (mainShape) { + mainShape.attr('fill', MainStyle.background.hover); + } + } else { + const selected = node.hasState('selected'); + if (selected) { + if (textShape) { + textShape.attr('fill', NameStyle.color.selected); + } + if (mainShape) { + mainShape.attr('fill', MainStyle.background.default); + } + } else { + if (textShape) { + textShape.attr('fill', NameStyle.color.default); + } + if (mainShape) { + mainShape.attr('fill', MainStyle.background.default); + } + } + } + } + } +}, 'treeNode'); + +G6.registerEdge('indentedEdge', { + afterDraw: (cfg, group) => { + const sourceNode = cfg.sourceNode?.getModel(); + const targetNode = cfg.targetNode?.getModel(); + const color = sourceNode.branchColor || targetNode.branchColor || cfg.color || '#000'; + // const branchThick = sourceNode.depth <= 1 ? 3 : sourceNode.depth <= 3 ? 2 : 1; + const keyShape = group.get('children')[0]; + keyShape.attr({ + stroke: color, + lineWidth: 3 // branchThick + }); + group.toBack(); + }, + getControlPoints: (cfg) => { + const startPoint = cfg.startPoint; + const endPoint = cfg.endPoint; + return [ + startPoint, + { + x: startPoint.x, + y: endPoint.y, + }, + endPoint, + ]; + }, + update: undefined +}, 'polyline'); + + +G6.registerBehavior('wheel-scroll', { + getDefaultCfg() { + return { + direction: 'y', + zoomKey: 'ctrl', + sensitivity: 3, + // wheel-scroll 可滚动的扩展范围,默认为 0,即最多可以滚动一屏的位置 + // 当设置的值大于 0 时,即滚动可以超过一屏 + // 当设置的值小于 0 时,相当于缩小了可滚动范围 + // 具体实例可参考:https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IFfoS67_HssAAAAAAAAAAAAAARQnAQ + scalableRange: -64, + }; + }, + + getEvents() { + if (!this.zoomKey || ['shift', 'ctrl', 'alt', 'control'].indexOf(this.zoomKey) === -1) this.zoomKey = 'ctrl'; + return { + wheel: 'onWheel', + }; + }, + + onWheel(ev) { + const graph = this.graph; + let keyDown = ev[`${this.zoomKey}Key`]; + if (this.zoomKey === 'control') keyDown = ev.ctrlKey; + if (keyDown) { + const sensitivity = this.get('sensitivity'); + const canvas = graph.get('canvas'); + const point = canvas.getPointByClient(ev.clientX, ev.clientY); + let ratio = graph.getZoom(); + if (ev.wheelDelta > 0) { + ratio *= (1 + 0.01 * sensitivity); + } else { + ratio *= (1 - 0.01 * sensitivity); + } + graph.zoomTo(ratio, { + x: point.x, + y: point.y, + }); + graph.emit('wheelzoom', ev); + } else { + let dx = ev.deltaX || ev.movementX; + let dy = ev.deltaY || ev.movementY; + if (!dy && navigator.userAgent.indexOf('Firefox') > -1) dy = (-ev.wheelDelta * 125) / 3 + + const width = this.graph.get('width'); + const height = this.graph.get('height'); + const graphCanvasBBox = this.graph.get('group').getCanvasBBox(); + + let expandWidth = this.scalableRange; + let expandHeight = this.scalableRange; + // 若 scalableRange 是 0~1 的小数,则作为比例考虑 + if (expandWidth < 1 && expandWidth > -1) { + expandWidth = width * expandWidth; + expandHeight = height * expandHeight; + } + + const { minX, maxX, minY, maxY } = graphCanvasBBox; + + if (dx > 0) { + if (maxX < -expandWidth) { + dx = 0 + } else if (maxX - dx < -expandWidth) { + dx = maxX + expandWidth + } + } else if (dx < 0) { + if (minX > width + expandWidth) { + dx = 0 + } else if (minX - dx > width + expandWidth) { + dx = minX - (width + expandWidth) + } + } + + if (dy > 0) { + if (maxY < -expandHeight) { + dy = 0 + } else if (maxY - dy < -expandHeight) { + dy = maxY + expandHeight + } + } else if (dy < 0) { + if (minY > height + expandHeight) { + dy = 0 + } else if (minY - dy > height + expandHeight) { + dy = minY - (height + expandHeight) + } + } + + if (this.get('direction') === 'x') { + dy = 0; + } else if (this.get('direction') === 'y') { + dx = 0; + } + + graph.translate(-dx, -dy); + } + ev.preventDefault(); + }, +}); +G6.registerBehavior('drag-branch', { + + getEvents: function getEvents() { + return { + 'node:dragstart': 'dragstart', + 'node:drag': 'drag', + 'node:dragend': 'dragend', + 'node:dragenter': 'dragenter', + 'node:dragleave': 'dragleave', + }; + }, + dragstart: function dragstart(e) { + this.set('foundNode', undefined) + this.origin = { + x: e.x, + y: e.y, + }; + this.target = e.item; + const graph = this.get('graph'); + // 未配置 shouldBegin 时 默认为 true + if (this.shouldBegin && !this.shouldBegin(graph.findDataById(this.target?.getID()))) { + this.began = false; + return; + } + this.began = true; + }, + dragenter: function dragenter(e) { + if (!this.began) { + return; + } + const graph = this.get('graph'); + const foundNode = e.item; + if (foundNode) graph.setItemState(foundNode, 'closest', true); + this.set('foundNode', foundNode); + }, + dragleave: function dragleave(e) { + if (!this.began) { + return; + } + const graph = this.get('graph'); + const foundNode = this.get('foundNode'); + if (foundNode) graph.setItemState(foundNode, 'closest', false); + this.set('foundNode', foundNode); + }, + drag: function drag(e) { + if (!this.began) { + return; + } + // move the delegate + this.updateDelegate(e); + }, + dragend: function dragend(e) { + const graph = this.get('graph'); + const foundNode = this.get('foundNode'); + if (foundNode) { + graph.setItemState(foundNode, 'closest', false); + } + if (!this.began) { + return; + } + this.began = false; + const { item } = e; + const id = item.getID(); + const data = graph.findDataById(id); + + // remove delegate + if (this.delegateRect) { + this.delegateRect.remove(); + this.delegateRect = null; + } + + if (!foundNode) { + graph.emit('afterdragbranch', { success: false, message: 'Failed. No node close to the dragged node.', branch: data }) + return; + } + + const foundNodeId = foundNode.getID(); + + let oriParentData; + Util.traverseTree(graph.get('data'), (d) => { + if (oriParentData) return false; + if (d.children?.filter(child => child.id === id)?.length) { + oriParentData = d; + } + return true; + }); + + // 未配置 shouldEnd,则默认为 true + if (this.shouldEnd && !this.shouldEnd(data, graph.findDataById(foundNodeId), oriParentData)) { + return; + } + + // if the foundNode is a descent of the dragged node, return + let isDescent = false; + + Util.traverseTree(data, (d) => { + if (d.id === foundNodeId) isDescent = true; + }); + if (isDescent) { + const newParentData = graph.findDataById(foundNodeId); + graph.emit('afterdragbranch', { success: false, message: 'Failed. The target node is a descendant of the dragged node.', newParentData, branch: data }) + return; + } + + const newParentData = graph.findDataById(foundNodeId); + // 触发外部对数据的改变 + graph.emit('afterdragbranch', { success: true, message: 'Success.', newParentData, oriParentData, branch: data }) + graph.removeChild(data.id); + setTimeout(() => { + let newChildren = newParentData.children; + if (newChildren) newChildren.push(data); + else newChildren = [data]; + // 更新正在被操作的子树颜色 + Util.traverseTree(data, d => { + d.branchColor = newParentData.branchColor + }) + graph.updateChildren(newChildren, newParentData.id); + }, 600); + }, + updateDelegate(e) { + const { graph } = this; + if (!this.delegateRect) { + // 拖动多个 + const parent = graph.get('group'); + const attrs = { + fill: '#F3F9FF', + fillOpacity: 0.5, + stroke: '#1890FF', + strokeOpacity: 0.9, + lineDash: [5, 5], + }; + + const { x: cx, y: cy, width, height, minX, minY } = this.calculationGroupPosition(e); + this.originPoint = { x: cx, y: cy, width, height, minX, minY }; + // model上的x, y是相对于图形中心的,delegateShape是g实例,x,y是绝对坐标 + this.delegateRect = parent.addShape('rect', { + attrs: { + width, + height, + x: cx, + y: cy, + ...attrs, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-delegate-shape', + }); + this.delegateRect.set('capture', false); + } else { + const clientX = e.x - this.origin.x + this.originPoint.minX; + const clientY = e.y - this.origin.y + this.originPoint.minY; + this.delegateRect.attr({ + x: clientX, + y: clientY, + }); + } + }, + calculationGroupPosition(evt) { + let node = this.target; + if (!node) { + node = evt.item; + } + + const bbox = node.getBBox(); + const { minX, minY, maxX, maxY } = bbox; + + return { + x: Math.floor(minX), + y: Math.floor(minY), + width: Math.ceil(maxX) - Math.floor(minX), + height: Math.ceil(maxY) - Math.floor(minY), + minX, + minY, + }; + }, +}); +G6.registerBehavior('hover-node', { + getEvents() { + return { + 'node:mouseover': 'onNodeMouseOver', + 'node:mouseleave': 'onNodeMouseLeave', + 'node:mouseenter': 'onNodeMouseEnter' + }; + }, + onNodeMouseEnter(ev) { + const { item } = ev; + if (!item || item.get('destroyed')) return; + item.toFront(); + const model = item.getModel(); + const { collapsed, depth } = model; + const rootNode = depth === 0 || model.isRoot; + const group = item.getContainer(); + + if (rootNode) return; + + // 控制子节点个数标记 + if (!collapsed) { + const childCountGroup = group.find(e => e.get('name') === 'child-count-group'); + if (childCountGroup) { + childCountGroup.show(); + } + } + this.graph.setItemState(item, 'hover', true); + }, + onNodeMouseOver(ev) { + const shape = ev.target; + + // tooltip显示、隐藏 + this.graph.emit('tooltip: show', ev); + + // expand 状态下,若 hover 到子节点个数标记,填充背景+显示收起 icon + const { item } = ev; + const group = item.getContainer(); + const model = item.getModel(); + if (!model.collapsed) { + const childCountGroup = group.find(e => e.get('name') === 'child-count-group'); + if (childCountGroup) { + childCountGroup.show(); + const back = childCountGroup.find(e => e.get('name') === 'child-count-rect-shape'); + const expandIcon = childCountGroup.find(e => e.get('name') === 'child-count-expand-icon'); + const rootNode = model.depth === 0 || model.isRoot; + const branchColor = rootNode ? '#576286' : model.branchColor; + if (shape.get('parent').get('name') === 'child-count-group') { + if (back) { + back.attr('fill', branchColor || '#fff'); + } + if (expandIcon) { + expandIcon.show(); + } + } else { + if (back) { + back.attr('fill', '#fff'); + } + if (expandIcon) { + expandIcon.hide(); + } + } + } + } + + const addChildIcon = group.find(e => e.get('name') === 'add-child-icon'); + if (addChildIcon) { + addChildIcon.show(); + } + const addChildLink = group.find(e => e.get('name') === 'add-child-link'); + if (addChildLink) { + addChildLink.show(); + } + }, + onNodeMouseLeave(ev) { + const { item } = ev; + const model = item.getModel(); + const group = item.getContainer(); + const { collapsed } = model; + + if (!collapsed) { + const childCountGroup = group.find(e => e.get('name') === 'child-count-group'); + if (childCountGroup) { + childCountGroup.hide(); + } + + const iconsLinkPath = group.find(e => e.get('name') === 'icons-link-path'); + if (iconsLinkPath) { + iconsLinkPath.hide(); + } + } + + const addChildIcon = group.find(e => e.get('name') === 'add-child-icon'); + if (addChildIcon) { + addChildIcon.hide(); + } + + const addChildLink = group.find(e => e.get('name') === 'add-child-link'); + if (addChildLink) { + addChildLink.hide(); + } + + this.graph.emit('tooltip: hide', ev); + }, +}); +G6.registerBehavior('click-node', { + getEvents() { + return { + 'node:click': 'onNodeClick', + 'canvas:click': 'onCanvasClick' + }; + }, + onNodeClick(e) { + const { item, target } = e; + const shape = target; + const shapeName = shape.cfg.name; + + // 点击增加节点 + if (shapeName === 'add-child-icon') { + const data = this.graph.findDataById(item.getID()); + if (!data.children) data.children = []; + data.children.push({ + id: `node-${Math.random()}`, + label: 'new node', + branchColor: data.branchColor, + type: 'indentedNode' + }); + this.graph.updateChildren(data.children, data.id) + return; + } + // 点击收起/展开 icon + if (shapeName === 'child-count-rect-shape' || shapeName === 'child-count-text-shape') { + const model = item.getModel(); + const updatedCollapsed = !model.collapsed; + this.graph.updateItem(item, { collapsed: updatedCollapsed }); + this.graph.layout(); + return; + } + + // 选中节点 + this.graph.getNodes().forEach(node => { + this.graph.setItemState(node, 'selected', false); + }); + this.graph.setItemState(item, 'selected', true); + + return; + }, + onCanvasClick(e) { + this.graph.getNodes().forEach(node => { + this.graph.setItemState(node, 'selected', false); + }) + } +}) + +const dataTransform = (data) => { + const changeData = (d, level = 0, color) => { + const data = { + ...d, + }; + data.type = level === 0 ? 'indentedRoot' : 'indentedNode'; + data.hover = false; + + if (color) { + data.color = color; + } + + if (level === 1 && !d.direction) { + if (!d.direction) { + data.direction = d.id.charCodeAt(d.id.length - 1) % 2 === 0 ? 'right' : 'left'; + } + } + + if (d.children) { + data.children = d.children.map((child) => changeData(child, level + 1, data.color)); + } + // 给定 branchColor 和 0-2 层节点 depth + if (data.children?.length) { + data.depth = 0; + data.children.forEach((subtree, i) => { + subtree.branchColor = COLORS[i % COLORS.length]; + // dfs + let currentDepth = 1; + subtree.depth = currentDepth; + Util.traverseTree(subtree, child => { + child.branchColor = COLORS[i % COLORS.length]; + + if (!child.depth) { + child.depth = currentDepth + 1; + } + else currentDepth = subtree.depth; + if (child.children) { + child.children.forEach(subChild => { + subChild.depth = child.depth + 1; + }) + } + // 把没有 children 但有 schemaType.subTypeCount 的节点设置为 collapsed + // 说明展开需要增量请求 children,未请求前展示 collapsed 状态 + if (!child.children?.length && child.schemaType?.subTypeCount) { + child.collapsed = true; + } + return true; + }) + }); + } + + return data; + }; + return changeData(data); +}; + +const tree = new G6.TreeGraph({ + container: 'container', + width: 800, + height: 800, + layout: { + type: 'indented', + direction: 'LR', + isHorizontal: true, + indent: 40, + getHeight: (d) => { + if (d.isRoot) { + return 30; + } + if (d.collapsed && d.children?.length) { + return 36; + } + return 22; + }, + getVGap: () => { + return 10; + }, + }, + defaultEdge: { + type: 'indentedEdge', + style: { + lineWidth: 2, + radius: 16, + }, + }, + nodeStateStyles: { + closest: { + fill: '#f00', + 'node-label': { + fill: '#f00', + } + }, + }, + minZoom: 0.5, + modes: { + default: [ + 'drag-canvas', + 'wheel-scroll', + 'hover-node', + 'click-node', + 'drag-branch', { + type: 'collapse-expand', + trigger: 'dblclick' + } + ], + }, +}); + +tree.on('afterrender', e => { + tree.getEdges().forEach(edge => { + const targetNode = edge.getTarget().getModel(); + const color = targetNode.branchColor; + tree.updateItem(edge, { color }); + }); + setTimeout(() => { + tree.moveTo(32, 32); + tree.zoomTo(0.7) + }, 16); +}); + +tree.data(dataTransform(rawData)); + +tree.render(); + diff --git a/packages/site/examples/case/treeDemos/demo/knowledgeTreeGraph.js b/packages/site/examples/case/treeDemos/demo/knowledgeTreeGraph.js new file mode 100644 index 0000000000..3eb05373cc --- /dev/null +++ b/packages/site/examples/case/treeDemos/demo/knowledgeTreeGraph.js @@ -0,0 +1,458 @@ +import G6 from '@antv/g6'; + +const minWidth = 60; + +const BaseConfig = { + nameFontSize: 12, + childCountWidth: 22, + countMarginLeft: 0, + itemPadding: 16, + nameMarginLeft: 4, + rootPadding: 18, +}; + +G6.registerNode('treeNode', { + draw: (cfg, group) => { + const { id, label, collapsed, selected, children, depth } = cfg; + const rootNode = depth === 0; + const hasChildren = children && children.length !== 0; + + const { + childCountWidth, + countMarginLeft, + itemPadding, + selectedIconWidth, + nameMarginLeft, + rootPadding, + } = BaseConfig; + + let width = 0; + const height = 28; + const x = 0; + const y = -height / 2; + + // 名称文本 + const text = group.addShape('text', { + attrs: { + text: label, + x: x * 2, + y, + textAlign: 'left', + textBaseline: 'top', + fontFamily: 'PingFangSC-Regular', + }, + cursor: 'pointer', + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'name-text-shape', + }); + const textWidth = text.getBBox().width; + width = textWidth + itemPadding + nameMarginLeft; + + width = width < minWidth ? minWidth : width; + + if (!rootNode && hasChildren) { + width += countMarginLeft; + width += childCountWidth; + } + + const keyShapeAttrs = { + x, + y, + width, + height, + radius: 4, + }; + + // keyShape根节点选中样式 + if (rootNode && selected) { + keyShapeAttrs.fill = '#e8f7ff'; + keyShapeAttrs.stroke = '#e8f7ff'; + } + const keyShape = group.addShape('rect', { + attrs: keyShapeAttrs, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'root-key-shape-rect-shape', + }); + + if (!rootNode) { + // 底部横线 + group.addShape('path', { + attrs: { + path: [ + ['M', x - 1, 0], + ['L', width, 0], + ], + stroke: '#AAB7C4', + lineWidth: 1, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'node-path-shape', + }); + } + + const mainX = x - 10; + const mainY = -height + 15; + + if (rootNode) { + group.addShape('rect', { + attrs: { + x: mainX, + y: mainY, + width: width + 12, + height, + radius: 14, + fill: '#e8f7ff', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'main-shape', + }); + } + + let nameColor = 'rgba(0, 0, 0, .65)'; + if (selected) { + nameColor = '#40A8FF'; + } + + // 名称 + if (rootNode) { + group.addShape('text', { + attrs: { + text: label, + x: mainX + rootPadding, + y: 1, + textAlign: 'left', + textBaseline: 'middle', + fill: nameColor, + fontSize: 12, + fontFamily: 'PingFangSC-Regular', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'root-text-shape', + }); + } else { + group.addShape('text', { + attrs: { + text: label, + x: selected ? mainX + 6 + nameMarginLeft : mainX + 6, + y: y - 5, + textAlign: 'start', + textBaseline: 'top', + fill: nameColor, + fontSize: 12, + fontFamily: 'PingFangSC-Regular', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'not-root-text-shape', + }); + } + + // 子类数量 + if (hasChildren && !rootNode) { + const childCountHeight = 12; + const childCountX = width - childCountWidth; + const childCountY = -childCountHeight / 2; + + group.addShape('rect', { + attrs: { + width: childCountWidth, + height: 12, + stroke: collapsed ? '#1890ff' : '#5CDBD3', + fill: collapsed ? '#fff' : '#E6FFFB', + x: childCountX, + y: childCountY, + radius: 6, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-rect-shape', + }); + group.addShape('text', { + attrs: { + text: `${children?.length}`, + fill: 'rgba(0, 0, 0, .65)', + x: childCountX + childCountWidth / 2, + y: childCountY + 12, + fontSize: 10, + width: childCountWidth, + textAlign: 'center', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'child-count-text-shape', + }); + } + + return keyShape; + }, +}); + +G6.registerEdge('smooth', { + draw(cfg, group) { + const { startPoint, endPoint } = cfg; + const hgap = Math.abs(endPoint.x - startPoint.x); + + const path = [ + ['M', startPoint.x, startPoint.y], + [ + 'C', + startPoint.x + hgap / 4, + startPoint.y, + endPoint.x - hgap / 2, + endPoint.y, + endPoint.x, + endPoint.y, + ], + ]; + + const shape = group.addShape('path', { + attrs: { + stroke: '#AAB7C4', + path, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'smooth-path-shape', + }); + return shape; + }, +}); + +const data = { + id: '200000004', + tooltip: 'Thing', + label: '事物', + description: null, + descriptionZh: null, + depth: 3, + subTypeCount: 9, + status: 0, + children: [ + { + id: '500000061', + tooltip: 'Person', + label: '自然人', + description: null, + descriptionZh: null, + depth: 1, + subTypeCount: 1, + status: 0, + children: [ + { + id: '707000085', + tooltip: 'FilmPerson', + label: '电影人', + description: null, + descriptionZh: null, + depth: 5, + subTypeCount: 3, + status: 1, + children: [ + { + id: '707000090', + tooltip: 'FilmDirector', + label: '电影导演', + description: null, + descriptionZh: null, + depth: 0, + subTypeCount: 0, + status: 1, + children: [], + }, + { + id: '707000091', + tooltip: 'FilmWriter', + label: '电影编剧', + description: null, + descriptionZh: null, + depth: 4, + subTypeCount: 0, + status: 1, + children: [], + }, + { + id: '707000092', + tooltip: 'FilmStar', + label: '电影主演', + description: null, + descriptionZh: null, + depth: 4, + subTypeCount: 0, + status: 1, + children: [], + }, + ], + }, + { + id: '707000086', + tooltip: 'MusicPerson', + label: '音乐人', + description: null, + descriptionZh: null, + depth: 17, + subTypeCount: 2, + status: 1, + children: [], + }, + ], + }, + { + id: '200000005', + tooltip: 'Music', + label: '音乐', + description: null, + descriptionZh: null, + depth: 3, + subTypeCount: 2, + status: 1, + children: [], + }, + { + id: '707000128', + tooltip: 'Film', + label: '电影', + description: null, + descriptionZh: null, + depth: 4, + subTypeCount: 0, + status: 1, + children: [ + { + id: '7070032328', + tooltip: 'Comedy', + label: '喜剧', + description: null, + descriptionZh: null, + depth: 4, + operation: 'C', + subTypeCount: 0, + status: 1, + children: [], + }, + ], + }, + { + id: '707000095', + tooltip: 'FilmGenre', + label: '电影类别', + description: null, + descriptionZh: null, + depth: 4, + subTypeCount: 0, + status: 1, + children: [], + }, + { + id: '702000021', + tooltip: 'Organization', + label: '机构', + description: null, + descriptionZh: null, + depth: 47, + subTypeCount: 1, + status: 0, + children: [ + { + id: '500000063', + tooltip: 'Company', + label: '公司', + description: null, + descriptionZh: null, + depth: 4, + subTypeCount: 2, + status: 1, + children: [ + { + id: '707000093', + tooltip: 'FilmCompany', + label: '电影公司', + description: null, + descriptionZh: null, + depth: 4, + subTypeCount: 0, + status: 1, + children: [], + }, + { + id: '707000094', + tooltip: 'MusicCompany', + label: '音乐公司', + description: null, + descriptionZh: null, + depth: 2, + subTypeCount: 0, + status: 1, + children: [], + }, + ], + }, + ], + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: [ + { + type: 'collapse-expand', + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + type: 'treeNode', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'smooth', + }, + layout: { + type: 'compactBox', + direction: 'LR', + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth(d) { + const labelWidth = G6.Util.getTextSize(d.label, BaseConfig.nameFontSize)[0]; + const width = + BaseConfig.itemPadding + + BaseConfig.nameMarginLeft + + labelWidth + + BaseConfig.rootPadding + + BaseConfig.childCountWidth; + return width; + }, + getVGap: function getVGap() { + return 15; + }, + getHGap: function getHGap() { + return 30; + }, + }, +}); + +graph.data(data); +graph.render(); +graph.fitView(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/case/treeDemos/demo/meta.json b/packages/site/examples/case/treeDemos/demo/meta.json new file mode 100644 index 0000000000..b267ee9e8d --- /dev/null +++ b/packages/site/examples/case/treeDemos/demo/meta.json @@ -0,0 +1,56 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "australiaFire.js", + "title": { + "zh": "澳大利亚大火", + "en": "Australia Fire" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*WLzKQpZl5QEAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "mindmap.js", + "title": { + "zh": "思维导图", + "en": "Mindmap" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/N5tGvsqQR9/xiazai%252520%2812%29.png" + }, + { + "filename": "customFlow.js", + "title": { + "zh": "自定义流向图", + "en": "Custom Flow" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*1AZTQ4oSPowAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "indentedTree.js", + "title": { + "zh": "缩进文件树", + "en": "Indented File Tree" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*9EHDSZVmLVgAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "knowledgeTreeGraph.js", + "title": { + "zh": "知识图谱树", + "en": "Knowledge Tree Graph" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*2J5YTbvU5FoAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "decisionTree.js", + "title": { + "zh": "决策树", + "en": "Decision Tree" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*HS5gQ6yCiL4AAAAAAAAAAAAAARQnAQ" + } + ] +} \ No newline at end of file diff --git a/packages/site/examples/case/treeDemos/demo/mindmap.js b/packages/site/examples/case/treeDemos/demo/mindmap.js new file mode 100644 index 0000000000..f28286692e --- /dev/null +++ b/packages/site/examples/case/treeDemos/demo/mindmap.js @@ -0,0 +1,502 @@ +import G6 from '@antv/g6'; + +const { + Util +} = G6; + +const colorArr = [ + '#5B8FF9', + '#5AD8A6', + '#5D7092', + '#F6BD16', + '#6F5EF9', + '#6DC8EC', + '#D3EEF9', + '#DECFEA', + '#FFE0C7', + '#1E9493', + '#BBDEDE', + '#FF99C3', + '#FFE0ED', + '#CDDDFD', + '#CDF3E4', + '#CED4DE', + '#FCEBB9', + '#D3CEFD', + '#945FB9', + '#FF9845', +]; + +const rawData = { + label: 'Modeling Methods', + id: '0', + children: [{ + label: 'Classification', + id: '0-1', + color: '#5AD8A6', + children: [{ + label: 'Logistic regression', + id: '0-1-1', + }, + { + label: 'Linear discriminant analysis', + id: '0-1-2', + }, + { + label: 'Rules', + id: '0-1-3', + }, + { + label: 'Decision trees', + id: '0-1-4', + }, + { + label: 'Naive Bayes', + id: '0-1-5', + }, + { + label: 'K nearest neighbor', + id: '0-1-6', + }, + { + label: 'Probabilistic neural network', + id: '0-1-7', + }, + { + label: 'Support vector machine', + id: '0-1-8', + }, + ], + }, + { + label: 'Consensus', + id: '0-2', + color: '#F6BD16', + children: [{ + label: 'Models diversity', + id: '0-2-1', + children: [{ + label: 'Different initializations', + id: '0-2-1-1', + }, + { + label: 'Different parameter choices', + id: '0-2-1-2', + }, + { + label: 'Different architectures', + id: '0-2-1-3', + }, + { + label: 'Different modeling methods', + id: '0-2-1-4', + }, + { + label: 'Different training sets', + id: '0-2-1-5', + }, + { + label: 'Different feature sets', + id: '0-2-1-6', + }, + ], + }, + { + label: 'Methods', + id: '0-2-2', + children: [{ + label: 'Classifier selection', + id: '0-2-2-1', + }, + { + label: 'Classifier fusion', + id: '0-2-2-2', + }, + ], + }, + { + label: 'Common', + id: '0-2-3', + children: [{ + label: 'Bagging', + id: '0-2-3-1', + }, + { + label: 'Boosting', + id: '0-2-3-2', + }, + { + label: 'AdaBoost', + id: '0-2-3-3', + }, + ], + }, + ], + }, + { + label: 'Regression', + id: '0-3', + color: '#269A99', + children: [{ + label: 'Multiple linear regression', + id: '0-3-1', + }, + { + label: 'Partial least squares', + id: '0-3-2', + }, + { + label: 'Multi-layer feedforward neural network', + id: '0-3-3', + }, + { + label: 'General regression neural network', + id: '0-3-4', + }, + { + label: 'Support vector regression', + id: '0-3-5', + }, + ], + }, + ], +}; + +G6.registerNode( + 'dice-mind-map-root', { + jsx: (cfg) => { + const width = Util.getTextSize(cfg.label, 16)[0] + 24; + const stroke = cfg.style.stroke || '#096dd9'; + const fill = cfg.style.fill; + + return ` + + + ${cfg.label} + + + + + `; + }, + getAnchorPoints() { + return [ + [0, 0.5], + [1, 0.5], + ]; + }, + }, + 'single-node', +); +G6.registerNode( + 'dice-mind-map-sub', { + jsx: (cfg) => { + const width = Util.getTextSize(cfg.label, 14)[0] + 24; + const color = cfg.color || cfg.style.stroke; + + return ` + + + ${ + cfg.label + } + + + - + + + + + `; + }, + getAnchorPoints() { + return [ + [0, 0.965], + [1, 0.965], + ]; + }, + }, + 'single-node', +); +G6.registerNode( + 'dice-mind-map-leaf', { + jsx: (cfg) => { + const width = Util.getTextSize(cfg.label, 12)[0] + 24; + const color = cfg.color || cfg.style.stroke; + + return ` + + + ${cfg.label} + + + - + + + + + `; + }, + getAnchorPoints() { + return [ + [0, 0.965], + [1, 0.965], + ]; + }, + }, + 'single-node', +); +G6.registerBehavior('dice-mindmap', { + getEvents() { + return { + 'node:click': 'clickNode', + 'node:dblclick': 'editNode', + 'node:mouseenter': 'hoverNode', + 'node:mouseleave': 'hoverNodeOut', + }; + }, + clickNode(evt) { + const model = evt.item.get('model'); + const name = evt.target.get('action'); + switch (name) { + case 'add': + const newId = + model.id + + '-' + + (((model.children || []).reduce((a, b) => { + const num = Number(b.id.split('-').pop()); + return a < num ? num : a; + }, 0) || 0) + + 1); + evt.currentTarget.updateItem(evt.item, { + children: (model.children || []).concat([{ + id: newId, + direction: newId.charCodeAt(newId.length - 1) % 2 === 0 ? 'right' : 'left', + label: 'New', + type: 'dice-mind-map-leaf', + color: model.color || colorArr[Math.floor(Math.random() * colorArr.length)], + }, ]), + }); + evt.currentTarget.layout(false); + break; + case 'delete': + const parent = evt.item.get('parent'); + evt.currentTarget.updateItem(parent, { + children: (parent.get('model').children || []).filter((e) => e.id !== model.id), + }); + evt.currentTarget.layout(false); + break; + case 'edit': + break; + default: + return; + } + }, + editNode(evt) { + const item = evt.item; + const model = item.get('model'); + const { + x, + y + } = item.calculateBBox(); + const graph = evt.currentTarget; + const realPosition = evt.currentTarget.getClientByPoint(x, y); + const el = document.createElement('div'); + const fontSizeMap = { + 'dice-mind-map-root': 24, + 'dice-mind-map-sub': 18, + 'dice-mind-map-leaf': 16, + }; + el.style.fontSize = fontSizeMap[model.type] + 'px'; + el.style.position = 'fixed'; + el.style.top = realPosition.y + 'px'; + el.style.left = realPosition.x + 'px'; + el.style.paddingLeft = '12px'; + el.style.transformOrigin = 'top left'; + el.style.transform = `scale(${evt.currentTarget.getZoom()})`; + const input = document.createElement('input'); + input.style.border = 'none'; + input.value = model.label; + input.style.width = Util.getTextSize(model.label, fontSizeMap[model.type])[0] + 'px'; + input.className = 'dice-input'; + el.className = 'dice-input'; + el.appendChild(input); + document.body.appendChild(el); + const destroyEl = () => { + document.body.removeChild(el); + }; + const clickEvt = (event) => { + if (!(event.target && event.target.className && event.target.className.includes('dice-input'))) { + window.removeEventListener('mousedown', clickEvt); + window.removeEventListener('scroll', clickEvt); + graph.updateItem(item, { + label: input.value, + }); + graph.layout(false); + graph.off('wheelZoom', clickEvt); + destroyEl(); + } + }; + graph.on('wheelZoom', clickEvt); + window.addEventListener('mousedown', clickEvt); + window.addEventListener('scroll', clickEvt); + input.addEventListener('keyup', (event) => { + if (event.key === 'Enter') { + clickEvt({ + target: {}, + }); + } + }); + }, + hoverNode(evt) { + evt.currentTarget.updateItem(evt.item, { + hover: true, + }); + }, + hoverNodeOut(evt) { + evt.currentTarget.updateItem(evt.item, { + hover: false, + }); + }, +}); +G6.registerBehavior('scroll-canvas', { + getEvents: function getEvents() { + return { + wheel: 'onWheel', + }; + }, + + onWheel: function onWheel(ev) { + const { + graph + } = this; + if (!graph) { + return; + } + if (ev.ctrlKey) { + const canvas = graph.get('canvas'); + const point = canvas.getPointByClient(ev.clientX, ev.clientY); + let ratio = graph.getZoom(); + if (ev.wheelDelta > 0) { + ratio += ratio * 0.05; + } else { + ratio *= ratio * 0.05; + } + graph.zoomTo(ratio, { + x: point.x, + y: point.y, + }); + } else { + const x = ev.deltaX || ev.movementX; + const y = ev.deltaY || ev.movementY || (-ev.wheelDelta * 125) / 3; + graph.translate(-x, -y); + } + ev.preventDefault(); + }, +}); + +const dataTransform = (data) => { + const changeData = (d, level = 0, color) => { + const data = { + ...d, + }; + switch (level) { + case 0: + data.type = 'dice-mind-map-root'; + break; + case 1: + data.type = 'dice-mind-map-sub'; + break; + default: + data.type = 'dice-mind-map-leaf'; + break; + } + + data.hover = false; + + if (color) { + data.color = color; + } + + if (level === 1 && !d.direction) { + if (!d.direction) { + data.direction = d.id.charCodeAt(d.id.length - 1) % 2 === 0 ? 'right' : 'left'; + } + } + + if (d.children) { + data.children = d.children.map((child) => changeData(child, level + 1, data.color)); + } + return data; + }; + return changeData(data); +}; + +const container = document.getElementById('container'); +const el = document.createElement('pre'); +el.innerHTML = + 'Double click on node to change title, hover node to display ops buttons\n双击修改节点标题, hover节点显示操作按钮'; + +container.appendChild(el); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const tree = new G6.TreeGraph({ + container: 'container', + width, + height, + fitView: true, + fitViewPadding: [10, 20], + layout: { + type: 'mindmap', + direction: 'H', + getHeight: () => { + return 16; + }, + getWidth: (node) => { + return node.level === 0 ? + Util.getTextSize(node.label, 16)[0] + 12 : + Util.getTextSize(node.label, 12)[0]; + }, + getVGap: () => { + return 10; + }, + getHGap: () => { + return 60; + }, + getSide: (node) => { + return node.data.direction; + }, + }, + defaultEdge: { + type: 'cubic-horizontal', + style: { + lineWidth: 2, + }, + }, + minZoom: 0.5, + modes: { + default: ['drag-canvas', 'zoom-canvas', 'dice-mindmap'], + }, +}); + +tree.data(dataTransform(rawData)); + +tree.render(); diff --git a/packages/site/examples/case/treeDemos/index.en.md b/packages/site/examples/case/treeDemos/index.en.md new file mode 100644 index 0000000000..f235e7355e --- /dev/null +++ b/packages/site/examples/case/treeDemos/index.en.md @@ -0,0 +1,61 @@ +--- +title: Tree Graph Demos +order: 1 +--- + +## Tree Graph Demos + +The data of tree graph is a hierarchical data, which has no cycles. Each node expect root node has only one parent node, the root node has no parent node. The differences between general graph and tree graph can be refered to [Introduction to General Graph and Tree Graph](/en/docs/manual/middle/layout/graph-layout#introduction) + +### Decision Tree + +#### Definition + +Decision tree, supports default levels, supports expand, collapse, hover, click, etc. + +This demo shows a decision tree that organize event items. It is useful for project risk assessment, feasibility decision analysis, and other decision cases. It supports collapse, expand, hover the title to show tooltip, etc. And with the zoom ratio changing, the node will automatically switch between detail mode and brief mode. + +#### When to Usage + +It is generally used in decision analysis scenarios. In the financial field, it is used to display capital flow, month-on-month, year-on-year, etc., to help executives make decisions and explore opportunities. Each branch displays the current node status through different forms, and can provide early warning and early warning of key indicators. Monitoring and other operations. It is also often used in cost-reduction and revenue-increasing scenarios. The indicators show various cost-reduction channels and operating channels. Channel decisions are made by analyzing the relationship between the channels and the actual cost-reduction amount. + + +### Indented File Graph + +#### Definition + +This demo combines file system view and node-link graph visualization. It demonstrates the hierarchy and and affiliation well. It supports branch draging, child node adding, branch collapsing or expanding, etc. + +### Knowledge Tree Graph + +#### Definition + +Tree Graph is popular graph visualization method to represent hierarchical realtional data. Because of its efficient space utilization and good interactivity when presenting data, it has received a lot of attention and in-depth research, and has been widely used in science, sociology, engineering, business and other fields. Tree diagrams can decompose things or phenomena into branches, also known as tree diagrams or system diagrams. The tree diagram is to systematically expand the purpose to be achieved and the measures or means that need to be taken, and draw it into a diagram to clarify the focus of the problem and find the best means or measures. + +#### When to Usage + +- When the subject is known and given in general, it needs to be transformed into specific details; +- When seeking reasonable steps to achieve a goal; +- When planning to implement specific actions of a plan or other plan; +- When performing a detailed analysis of the process; +- When exploring the root cause of the problem; +- When evaluating several possible solutions to the problem; +- When the affinity diagram or correlation diagram cannot reveal key issues; +- When used as a communication tool to explain specific details to others. + +#### Custom flow graph + +As the demo below, to satisfy such a highly customized requirments, users can custom nodes and edges by themself. + +#### Mindmap + +A mind map is a diagram used to visually organize information. A mind map is hierarchical and shows relationships among pieces of the whole. + +This case contains a series of interaction of mind map, including adding, removing, changing title. + +#### Australia Fire + +This graph demonstrates the fire affectness and population on different cities of Australia. The data comes from NASA, which includes the fire points of Australia detected by the satellites. [Link of the data](https://firms.modaps.eosdis.nasa.gov/active_fire/#firms-shapefile). + +The root node represents Australia. The nodes on the second level are the states of Australia. The leaves of the graph represent the main cities of Australia. The sizes of the leaves indicate the population of the corresponding cities. The bars on the leaves show the number of the fire points in the corresponding city everyday, which is detected by the satellites. The color of a leaf is mapped to the degree of the fire effectiveness in the region, which is the average brightness of the fire points in the region detected by the satellites. + diff --git a/packages/site/examples/case/treeDemos/index.zh.md b/packages/site/examples/case/treeDemos/index.zh.md new file mode 100644 index 0000000000..892997fe08 --- /dev/null +++ b/packages/site/examples/case/treeDemos/index.zh.md @@ -0,0 +1,60 @@ +--- +title: 树图场景案例 +order: 1 +--- + +## 一般图场景案例 + +树图是指数据未嵌套结构的、不存在环路的数据结构展示的图。树图中,根节点没有父节点,其它节点有且仅有一个父节点。一般图与树图的区别详见 [一般图与树图介绍](/zh/docs/manual/middle/layout/graph-layout#简介) + +### 决策树 + +#### 定义 + +决策树是在已知各种情况的基础上,通过事项构建树状图,根据自己的上下级进行关联,挂接到对应的分支,常用于项目风险评估,可行性的决策分析等场景,是直观运用概率分析的一种图解法。支持展开、收起、悬停、点击等交互。支持根据当前缩放等级,节点切换详情模式与缩略模式。 + +#### 何时使用 + +一般用于决策分析场景,在金融领域,用于展示资金流向、月环比、同比等,助力高管进行决策,挖掘机会,各个分支通过不同形态展示当前节点的状态,可对关键指标进行预警、监控等操作。也常用于降本增收场景,指标展示各类降本渠道、以及运营渠道,通过分析各渠道之间的关系以及实际降本额进行渠道决策。 + + +### 缩进文件树 + +#### 定义 + +结合文件树与点线图,能够很好地展示不同类型节点之间的嵌套、从属关系。支持拖拽子树改变结构、收起/展开子树、增加自节点等编辑功能。 + +### 知识图谱树图 + +#### 定义 + +树图是一种流行的利用包含关系表达层次化数据的可视化方法。由于其呈现数据时高效的空间利用率和良好的交互性,受到众多的关注,得到深入的研究,并在科学、社会学、工程、商业等领域都得到了广泛的应用。树图能将事物或现象分解成树枝状,又称树型图或系统图。树图就是把要实现的目的与需要采取的措施或手段,系统地展开,并绘制成图,以明确问题的重点,寻找最佳手段或措施。 + +#### 何时使用 + +- 当主题已知并且泛泛地给出,需要将其转化为具体细节时; +- 当寻求达成一个目标的合理步骤时; +- 当策划实行一个方案或其他计划的具体行动时; +- 当对过程进行详细的分析时; +- 当探究问题的根本原因时; +- 当评估解决问题的几个可能方案时; +- 当亲和图或关联图不能揭示关键问题时; +- 当作为向其他人说明具体细节的交流工具时。 + + +### 自定义流向图 + +对于一些定制化比较强的需求,使用 G6 内置的节点和边很难实现。下面的资金流向图是通过自定义节点和边的方式实现的。 + +### 思维导图 + +思维导图是一种将思维形象化的方法。我们知道放射性思考是人类大脑的自然思考方式,每一种进入大脑的资料,不论是感觉、记忆或是想法——包括文字、数字、符码、香气、食物、线条、颜色、意象、节奏、音符等,都可以成为一个思考中心,并由此中心向外发散出成千上万的关节点,每一个关节点代表与中心主题的一个连结,而每一个连结又可以成为另一个中心主题,再向外发散出成千上万的关节点,呈现出放射性立体结构。 + +本案例展示了一个完整的思维导图交互案例,包括增删改相关操作。 + +### 澳大利亚大火 + +本图展示了 2020 年 1 月 11 日到 18 日的火灾情况,数据来源于 NASA 公开数据,由卫星拍摄到的起火点。[数据链接](https://firms.modaps.eosdis.nasa.gov/active_fire/#firms-shapefile)。 + +图上的根节点代表了澳大利亚;第二级节点代表了澳大利亚的各个州;最外圈节点(叶子节点)代表了澳大利亚各个州的主要城市。叶子节点的大小代表了该城市人口总数的多少;叶子节点上的柱子高低代表了该城市每天起火点的数量(卫星检测到该区域的起火点数量);节点颜色代表了该区域每天的受灾情况(由卫星检测到的该区域的起火点的明亮度(brightness)的平均值)。 + diff --git a/packages/site/examples/interaction/combo/API.en.md b/packages/site/examples/interaction/combo/API.en.md new file mode 100644 index 0000000000..23f7815d22 --- /dev/null +++ b/packages/site/examples/interaction/combo/API.en.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# Item Operations + + + +# Combo Operations + + diff --git a/packages/site/examples/interaction/combo/API.zh.md b/packages/site/examples/interaction/combo/API.zh.md new file mode 100644 index 0000000000..6993479c14 --- /dev/null +++ b/packages/site/examples/interaction/combo/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# 元素操作 + + + +# Combo 操作 + + diff --git a/packages/site/examples/interaction/combo/demo/cCircle.js b/packages/site/examples/interaction/combo/demo/cCircle.js new file mode 100644 index 0000000000..1cafb2e57f --- /dev/null +++ b/packages/site/examples/interaction/combo/demo/cCircle.js @@ -0,0 +1,180 @@ +import G6 from '@antv/g6'; + +/** + * The demo shows customing a combo type by extending the built-in circle combo + * by Shiwu + * + */ + +// The symbols for the marker inside the combo +const collapseIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ]; +}; +const expandIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ['M', x - r + r, y - r + 4], + ['L', x, y + r - 4], + ]; +}; + +G6.registerCombo( + 'cCircle', + { + drawShape: function draw(cfg, group) { + const self = this; + // Get the shape style, where the style.r corresponds to the R in the Illustration of Built-in Rect Combo + const style = self.getShapeStyle(cfg); + // Add a circle shape as keyShape which is the same as the extended 'circle' type Combo + const circle = group.addShape('circle', { + attrs: { + ...style, + x: 0, + y: 0, + r: style.r, + }, + draggable: true, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'combo-keyShape', + }); + // Add the marker on the bottom + const marker = group.addShape('marker', { + attrs: { + ...style, + fill: '#fff', + opacity: 1, + x: 0, + y: style.r, + r: 10, + symbol: collapseIcon, + }, + draggable: true, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'combo-marker-shape', + }); + + return circle; + }, + // Define the updating logic for the marker + afterUpdate: function afterUpdate(cfg, combo) { + const self = this; + // Get the shape style, where the style.r corresponds to the R in the Illustration of Built-in Rect Combo + const style = self.getShapeStyle(cfg); + const group = combo.get('group'); + // Find the marker shape in the graphics group of the Combo + const marker = group.find((ele) => ele.get('name') === 'combo-marker-shape'); + // Update the marker shape + marker.attr({ + x: 0, + y: style.r, + // The property 'collapsed' in the combo data represents the collapsing state of the Combo + // Update the symbol according to 'collapsed' + symbol: cfg.collapsed ? expandIcon : collapseIcon, + }); + }, + }, + 'circle', +); + +const data = { + nodes: [ + { id: 'node1', x: 250, y: 200, comboId: 'combo1' }, + { id: 'node2', x: 300, y: 200, comboId: 'combo1' }, + { id: 'node3', x: 100, y: 200, comboId: 'combo3' }, + ], + combos: [ + { id: 'combo1', label: 'Combo 1', parentId: 'combo2' }, + { id: 'combo2', label: 'Combo 2' }, + { id: 'combo3', label: 'Combo 3', collapsed: true }, + ], +}; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Click the bottom marker to collapse/expand the combo.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + // Configure the combos globally + defaultCombo: { + // The type of the combos. You can also assign type in the data of combos + type: 'cCircle', + labelCfg: { + refY: 2, + }, + // ... Other global configurations for combos + }, + comboStateStyles: { + dragenter: { + lineWidth: 4, + stroke: '#FE9797', + }, + }, + modes: { + default: ['drag-combo', 'drag-node', 'drag-canvas', 'click-select'], + }, +}); +graph.data(data); +graph.render(); + +// collapse/expand when click the marker +graph.on('combo:click', (e) => { + if (e.target.get('name') === 'combo-marker-shape') { + // graph.collapseExpandCombo(e.item.getModel().id); + graph.collapseExpandCombo(e.item); + if (graph.get('layout')) graph.layout(); + else graph.refreshPositions(); + } +}); + +graph.on('combo:dragend', (e) => { + graph.getCombos().forEach((combo) => { + graph.setItemState(combo, 'dragenter', false); + }); +}); +graph.on('node:dragend', (e) => { + graph.getCombos().forEach((combo) => { + graph.setItemState(combo, 'dragenter', false); + }); +}); + +graph.on('combo:dragenter', (e) => { + graph.setItemState(e.item, 'dragenter', true); +}); +graph.on('combo:dragleave', (e) => { + graph.setItemState(e.item, 'dragenter', false); +}); + +graph.on('combo:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('combo:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/interaction/combo/demo/cRect.js b/packages/site/examples/interaction/combo/demo/cRect.js new file mode 100644 index 0000000000..4ebcc1bebb --- /dev/null +++ b/packages/site/examples/interaction/combo/demo/cRect.js @@ -0,0 +1,178 @@ +import G6 from '@antv/g6'; + +/** + * The demo shows customing a combo type by extending the built-in circle combo + * by Shiwu + * + */ + +// The symbols for the marker inside the combo +const collapseIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ]; +}; +const expandIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ['M', x - r + r, y - r + 4], + ['L', x, y + r - 4], + ]; +}; + +G6.registerCombo( + 'cRect', + { + drawShape: function drawShape(cfg, group) { + const self = this; + // Get the padding from the configuration + cfg.padding = cfg.padding || [50, 20, 20, 20]; + // Get the shape's style, where the style.width and style.height correspond to the width and height in the figure of Illustration of Built-in Rect Combo + const style = self.getShapeStyle(cfg); + // Add a rect shape as the keyShape which is the same as the extended rect Combo + const rect = group.addShape('rect', { + attrs: { + ...style, + x: -style.width / 2 - (cfg.padding[3] - cfg.padding[1]) / 2, + y: -style.height / 2 - (cfg.padding[0] - cfg.padding[2]) / 2, + width: style.width, + height: style.height, + }, + draggable: true, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'combo-keyShape', + }); + // Add the circle on the right + group.addShape('marker', { + attrs: { + ...style, + fill: '#fff', + opacity: 1, + // cfg.style.width and cfg.style.heigth correspond to the innerWidth and innerHeight in the figure of Illustration of Built-in Rect Combo + x: cfg.style.width / 2 + cfg.padding[1], + y: (cfg.padding[2] - cfg.padding[0]) / 2, + r: 10, + symbol: collapseIcon, + }, + draggable: true, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'combo-marker-shape', + }); + return rect; + }, + // Define the updating logic of the right circle + afterUpdate: function afterUpdate(cfg, combo) { + const group = combo.get('group'); + // Find the circle shape in the graphics group of the Combo by name + const marker = group.find((ele) => ele.get('name') === 'combo-marker-shape'); + // Update the position of the right circle + marker.attr({ + // cfg.style.width and cfg.style.heigth correspond to the innerWidth and innerHeight in the figure of Illustration of Built-in Rect Combo + x: cfg.style.width / 2 + cfg.padding[1], + y: (cfg.padding[2] - cfg.padding[0]) / 2, + // The property 'collapsed' in the combo data represents the collapsing state of the Combo + // Update the symbol according to 'collapsed' + symbol: cfg.collapsed ? expandIcon : collapseIcon, + }); + }, + }, + 'rect', +); + +const data = { + nodes: [ + { id: 'node1', x: 250, y: 200, comboId: 'combo1' }, + { id: 'node2', x: 300, y: 200, comboId: 'combo1' }, + { id: 'node3', x: 100, y: 200, comboId: 'combo3' }, + ], + combos: [ + { id: 'combo1', label: 'Combo 1', parentId: 'combo2' }, + { id: 'combo2', label: 'Combo 2' }, + { id: 'combo3', label: 'Combo 3', collapsed: true }, + ], +}; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Click the right marker to collapse/expand the combo.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + // Configure the combos globally + defaultCombo: { + // The type of the combos. You can also assign type in the data of combos + type: 'cRect', + // ... Other global configurations for combos + }, + comboStateStyles: { + dragenter: { + lineWidth: 4, + stroke: '#FE9797', + }, + }, + modes: { + default: ['drag-combo', 'drag-node', 'drag-canvas', 'click-select'], + }, +}); +graph.data(data); +graph.render(); + +// collapse/expand when click the marker +graph.on('combo:click', (e) => { + if (e.target.get('name') === 'combo-marker-shape') { + // graph.collapseExpandCombo(e.item.getModel().id); + graph.collapseExpandCombo(e.item); + if (graph.get('layout')) graph.layout(); + else graph.refreshPositions(); + } +}); + +graph.on('combo:dragend', (e) => { + graph.getCombos().forEach((combo) => { + graph.setItemState(combo, 'dragenter', false); + }); +}); +graph.on('node:dragend', (e) => { + graph.getCombos().forEach((combo) => { + graph.setItemState(combo, 'dragenter', false); + }); +}); + +graph.on('combo:dragenter', (e) => { + graph.setItemState(e.item, 'dragenter', true); +}); +graph.on('combo:dragleave', (e) => { + graph.setItemState(e.item, 'dragenter', false); +}); + +graph.on('combo:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('combo:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/interaction/combo/demo/circle.js b/packages/site/examples/interaction/combo/demo/circle.js new file mode 100644 index 0000000000..853cab0de4 --- /dev/null +++ b/packages/site/examples/interaction/combo/demo/circle.js @@ -0,0 +1,91 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node1', x: 350, y: 200, comboId: 'combo1' }, + { id: 'node2', x: 350, y: 250, comboId: 'combo1' }, + { id: 'node3', x: 100, y: 200, comboId: 'combo3' }, + ], + edges: [ + { source: 'node1', target: 'node2' }, + { source: 'node1', target: 'node3' }, + { source: 'combo1', target: 'node3' }, + ], + combos: [ + { id: 'combo1', label: 'Combo 1', parentId: 'combo2' }, + { id: 'combo2', label: 'Combo 2' }, + { id: 'combo3', label: 'Combo 3', collapsed: true }, + ], +}; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Double click the combo to collapse/expand it. Drag the node or combo to change the hierarchy.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + type: 'circle', + style: { + lineWidth: 1, + }, + labelCfg: { + refY: 15, + position: 'bottom', + }, + }, + comboStateStyles: { + dragenter: { + lineWidth: 4, + stroke: '#FE9797', + }, + }, + modes: { + default: ['drag-canvas', 'drag-node', 'drag-combo', 'collapse-expand-combo', 'click-select'], + }, +}); + +graph.data(data); +graph.render(); + +graph.on('combo:dragend', (e) => { + graph.getCombos().forEach((combo) => { + graph.setItemState(combo, 'dragenter', false); + }); +}); +graph.on('node:dragend', (e) => { + graph.getCombos().forEach((combo) => { + graph.setItemState(combo, 'dragenter', false); + }); +}); +graph.on('combo:dragenter', (e) => { + graph.setItemState(e.item, 'dragenter', true); +}); +graph.on('combo:dragleave', (e) => { + graph.setItemState(e.item, 'dragenter', false); +}); + +graph.on('combo:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('combo:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/interaction/combo/demo/meta.json b/packages/site/examples/interaction/combo/demo/meta.json new file mode 100644 index 0000000000..94b0a40172 --- /dev/null +++ b/packages/site/examples/interaction/combo/demo/meta.json @@ -0,0 +1,28 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "circle.js", + "title": "圆形 Combo", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Rvx9SYSHGsIAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "rect.js", + "title": "矩形 Combo", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*qvmVTZc-iNcAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "cCircle.js", + "title": "扩展圆形 Combo", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*1LelSq5TP9EAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "cRect.js", + "title": "扩展矩形 Combo", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*rQGaT4kiaYoAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/combo/demo/rect.js b/packages/site/examples/interaction/combo/demo/rect.js new file mode 100644 index 0000000000..e39aa722a1 --- /dev/null +++ b/packages/site/examples/interaction/combo/demo/rect.js @@ -0,0 +1,94 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node1', x: 350, y: 200, comboId: 'combo1' }, + { id: 'node2', x: 350, y: 250, comboId: 'combo1' }, + { id: 'node3', x: 100, y: 200, comboId: 'combo3' }, + ], + edges: [ + { source: 'node1', target: 'node2' }, + { source: 'node1', target: 'node3' }, + { source: 'combo1', target: 'node3' }, + ], + combos: [ + { id: 'combo1', label: 'Combo 1', parentId: 'combo2' }, + { id: 'combo2', label: 'Combo 2' }, + { id: 'combo3', label: 'Combo 3' }, + ], +}; +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Double click the combo to collapse/expand it. Drag the node or combo to change the hierarchy.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + defaultCombo: { + type: 'rect', + size: [40, 10], // The minimum size of the Combo + padding: [30, 20, 10, 20], + style: { + lineWidth: 1, + }, + labelCfg: { + refY: 10, + refX: 20, + position: 'top', + }, + }, + comboStateStyles: { + dragenter: { + lineWidth: 4, + stroke: '#FE9797', + }, + }, + modes: { + default: ['drag-canvas', 'drag-node', 'drag-combo', 'collapse-expand-combo', 'click-select'], + }, +}); + +graph.data(data); +graph.render(); + +graph.on('combo:dragend', (e) => { + graph.getCombos().forEach((combo) => { + graph.setItemState(combo, 'dragenter', false); + }); +}); +graph.on('node:dragend', (e) => { + graph.getCombos().forEach((combo) => { + graph.setItemState(combo, 'dragenter', false); + }); +}); + +graph.on('combo:dragenter', (e) => { + graph.setItemState(e.item, 'dragenter', true); +}); +graph.on('combo:dragleave', (e) => { + graph.setItemState(e.item, 'dragenter', false); +}); + +graph.on('combo:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('combo:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/interaction/combo/index.en.md b/packages/site/examples/interaction/combo/index.en.md new file mode 100644 index 0000000000..75d9edb4a6 --- /dev/null +++ b/packages/site/examples/interaction/combo/index.en.md @@ -0,0 +1,12 @@ +--- +title: Node Combo +order: 4 +--- + +New feature of V3.5. There are 2 kinds of built-in combos in G6, which can be extended by configurations([docs](/en/docs/manual/middle/elements/combos/built-in/circle)) and custom mechanism ([docs](/en/docs/manual/middle/elements/combos/custom-combo), [demo](/en/examples/item/customCombo)). The following two examples allow users to drag the combo or node to change the hierarchy, and double click to collapse/expand a combo. + +## Usage + +2 built-in combos and their extensions allow users to select appropriate ones for their scenario. + +Please refer to [Built-in Combos](/en/docs/manual/middle/elements/combos/built-in/circle) for more information. diff --git a/packages/site/examples/interaction/combo/index.zh.md b/packages/site/examples/interaction/combo/index.zh.md new file mode 100644 index 0000000000..d3c0c3232a --- /dev/null +++ b/packages/site/examples/interaction/combo/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 节点分组 Combo +order: 4 +--- + +自 V3.5 起支全新节点分组机制 Combo。内置了 2 种不同类型的 Combo,支持通过配置([文档](/zh/docs/manual/middle/elements/combos/built-in/circle))进行扩展,支持自定义([文档](/zh/docs/manual/middle/elements/combos/custom-combo),[Demo](/zh/examples/item/customCombo))。下面两个例子允许节点和 Combo 的拖拽改变从属关系,双击收缩/展开 Combo。 + +## 何时使用 + +用户可根据具体的业务场景,选择合适的内置 Combo。更多内容请参考 [内置 Combo](/zh/docs/manual/middle/elements/combos/built-in/circle)。 diff --git a/packages/site/examples/interaction/createEdge/API.en.md b/packages/site/examples/interaction/createEdge/API.en.md new file mode 100644 index 0000000000..05434a9239 --- /dev/null +++ b/packages/site/examples/interaction/createEdge/API.en.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# Built-in Behaviors + +Refer to [Built-in Behavior](/en/docs/manual/middle/states/defaultBehavior). + +# Custom Behavior + +Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). diff --git a/packages/site/examples/interaction/createEdge/API.zh.md b/packages/site/examples/interaction/createEdge/API.zh.md new file mode 100644 index 0000000000..f672ff9f3b --- /dev/null +++ b/packages/site/examples/interaction/createEdge/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# 内置复合交互 + +请参考教程 [内置 Behavior](/zh/docs/manual/middle/states/defaultBehavior)。 + +# 自定义复合交互 + +请参考教程 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)。 diff --git a/packages/site/examples/interaction/createEdge/demo/click-and-key.js b/packages/site/examples/interaction/createEdge/demo/click-and-key.js new file mode 100644 index 0000000000..413587141b --- /dev/null +++ b/packages/site/examples/interaction/createEdge/demo/click-and-key.js @@ -0,0 +1,58 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node1', x: 350, y: 200 }, + { id: 'node2', x: 350, y: 250 }, + { id: 'node3', x: 100, y: 200 }, + ], +}; +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Press the shift key and click the source and target node to create a new edge.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: [ + { + type: 'create-edge', + key: 'shift', // undefined by default, options: 'shift', 'control', 'ctrl', 'meta', 'alt' + }, + ], + }, + defaultEdge: { + style: { + stroke: '#F6BD16', + lineWidth: 2, + }, + }, + linkCenter: true, +}); + +graph.data(data); +graph.render(); + +graph.on('aftercreateedge', (e) => { + const edges = graph.save().edges; + G6.Util.processParallelEdges(edges); + graph.getEdges().forEach((edge, i) => { + graph.updateItem(edge, { + curveOffset: edges[i].curveOffset, + curvePosition: edges[i].curvePosition, + }); + }); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/interaction/createEdge/demo/click-link-point.js b/packages/site/examples/interaction/createEdge/demo/click-link-point.js new file mode 100644 index 0000000000..80cc1247c9 --- /dev/null +++ b/packages/site/examples/interaction/createEdge/demo/click-link-point.js @@ -0,0 +1,259 @@ +import G6 from '@antv/g6'; + +// G6.Util.processParallelEdges processes the edges with same source node and target node, +// on this basis, processParallelEdgesOnAnchorPoint consider the end nodes and anchor points in the same time. +const processParallelEdgesOnAnchorPoint = ( + edges, + offsetDiff = 15, + multiEdgeType = 'quadratic', + singleEdgeType = undefined, + loopEdgeType = undefined +) => { + const len = edges.length; + const cod = offsetDiff * 2; + const loopPosition = [ + 'top', + 'top-right', + 'right', + 'bottom-right', + 'bottom', + 'bottom-left', + 'left', + 'top-left', + ]; + const edgeMap = {}; + const tags = []; + const reverses = {}; + for (let i = 0; i < len; i++) { + const edge = edges[i]; + const { source, target, sourceAnchor, targetAnchor } = edge; + const sourceTarget = `${source}|${sourceAnchor}-${target}|${targetAnchor}`; + + if (tags[i]) continue; + if (!edgeMap[sourceTarget]) { + edgeMap[sourceTarget] = []; + } + tags[i] = true; + edgeMap[sourceTarget].push(edge); + for (let j = 0; j < len; j++) { + if (i === j) continue; + const sedge = edges[j]; + const { source: src, target: dst, sourceAnchor: srcAnchor, targetAnchor: dstAnchor } = sedge; + + // 两个节点之间共同的边 + // 第一条的source = 第二条的target + // 第一条的target = 第二条的source + if (!tags[j]) { + if (source === dst && sourceAnchor === dstAnchor + && target === src && targetAnchor === srcAnchor) { + edgeMap[sourceTarget].push(sedge); + tags[j] = true; + reverses[`${src}|${srcAnchor}|${dst}|${dstAnchor}|${edgeMap[sourceTarget].length - 1}`] = true; + } else if (source === src && sourceAnchor === srcAnchor + && target === dst && targetAnchor === dstAnchor) { + edgeMap[sourceTarget].push(sedge); + tags[j] = true; + } + } + } + } + + for (const key in edgeMap) { + const arcEdges = edgeMap[key]; + const { length } = arcEdges; + for (let k = 0; k < length; k++) { + const current = arcEdges[k]; + if (current.source === current.target) { + if (loopEdgeType) current.type = loopEdgeType; + // 超过8条自环边,则需要重新处理 + current.loopCfg = { + position: loopPosition[k % 8], + dist: Math.floor(k / 8) * 20 + 50, + }; + continue; + } + if (length === 1 && singleEdgeType && (current.source !== current.target || current.sourceAnchor !== current.targetAnchor)) { + current.type = singleEdgeType; + continue; + } + current.type = multiEdgeType; + const sign = + (k % 2 === 0 ? 1 : -1) * (reverses[`${current.source}|${current.sourceAnchor}|${current.target}|${current.targetAnchor}|${k}`] ? -1 : 1); + if (length % 2 === 1) { + current.curveOffset = sign * Math.ceil(k / 2) * cod; + } else { + current.curveOffset = sign * (Math.floor(k / 2) * cod + offsetDiff); + } + } + } + return edges; +}; + + +const data = { + nodes: [ + { id: 'node1', x: 350, y: 100 }, + { id: 'node2', x: 350, y: 250 }, + ], +}; +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Hover the node and the anchor points will show up, click anchor points to create edges.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +// custom a node with anchor-point shapes +G6.registerNode('rect-node', { + // draw anchor-point circles according to the anchorPoints in afterDraw + afterDraw(cfg, group) { + const bbox = group.getBBox(); + const anchorPoints = this.getAnchorPoints(cfg) + anchorPoints.forEach((anchorPos, i) => { + group.addShape('circle', { + attrs: { + r: 5, + x: bbox.x + bbox.width * anchorPos[0], + y: bbox.y + bbox.height * anchorPos[1], + fill: '#fff', + stroke: '#5F95FF' + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') + anchorPointIdx: i, // flag the idx of the anchor-point circle + links: 0, // cache the number of edges connected to this shape + visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state + }) + }) + }, + getAnchorPoints(cfg) { + return cfg.anchorPoints || [[0, 0.5], [0.33, 0], [0.66, 0], [1, 0.5], [0.33, 1], [0.66, 1]]; + }, + // response the state changes and show/hide the link-point circles + setState(name, value, item) { + if (name === 'showAnchors') { + const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point'); + anchorPoints.forEach(point => { + if (value || point.get('links') > 0) point.show() + else point.hide() + }) + } + } +}, 'rect') + +let sourceAnchorIdx, targetAnchorIdx; + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: [ + 'drag-node', + // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles + { + type: 'create-edge', + shouldBegin: e => { + // avoid beginning at other shapes on the node + if (e.target && e.target.get('name') !== 'anchor-point') return false; + sourceAnchorIdx = e.target.get('anchorPointIdx'); + e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle + return true; + }, + shouldEnd: e => { + // avoid ending at other shapes on the node + if (e.target && e.target.get('name') !== 'anchor-point') return false; + if (e.target) { + targetAnchorIdx = e.target.get('anchorPointIdx'); + e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle + return true; + } + targetAnchorIdx = undefined; + return true; + }, + // update the sourceAnchor + // getEdgeConfig: () => { + // return { + // sourceAnchor: sourceAnchorIdx + // } + // } + }], + }, + defaultNode: { + type: 'rect-node', + style: { + fill: '#eee', + stroke: '#ccc', + } + }, + defaultEdge: { + type: 'quadratic', + style: { + stroke: '#F6BD16', + lineWidth: 2, + }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('aftercreateedge', (e) => { + // update the sourceAnchor and targetAnchor for the newly added edge + graph.updateItem(e.edge, { + sourceAnchor: sourceAnchorIdx, + targetAnchor: targetAnchorIdx + }) + + // update the curveOffset for parallel edges + const edges = graph.save().edges; + processParallelEdgesOnAnchorPoint(edges); + graph.getEdges().forEach((edge, i) => { + graph.updateItem(edge, { + curveOffset: edges[i].curveOffset, + curvePosition: edges[i].curvePosition, + }); + }); +}); + +// if create-edge is canceled before ending, update the 'links' on the anchor-point circles +graph.on('afterremoveitem', e => { + if (e.item && e.item.source && e.item.target) { + const sourceNode = graph.findById(e.item.source); + const targetNode = graph.findById(e.item.target); + const { sourceAnchor, targetAnchor } = e.item; + if (sourceNode && !isNaN(sourceAnchor)) { + const sourceAnchorShape = sourceNode.getContainer().find(ele => (ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === sourceAnchor)); + sourceAnchorShape.set('links', sourceAnchorShape.get('links') - 1); + } + if (targetNode && !isNaN(targetAnchor)) { + const targetAnchorShape = targetNode.getContainer().find(ele => (ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === targetAnchor)); + targetAnchorShape.set('links', targetAnchorShape.get('links') - 1); + } + } +}) + +// after clicking on the first node, the edge is created, update the sourceAnchor +graph.on('afteradditem', e => { + if (e.item && e.item.getType() === 'edge') { + graph.updateItem(e.item, { + sourceAnchor: sourceAnchorIdx + }); + } +}) + + +// some listeners to control the state of nodes to show and hide anchor-point circles +graph.on('node:mouseenter', e => { + graph.setItemState(e.item, 'showAnchors', true); +}) +graph.on('node:mouseleave', e => { + graph.setItemState(e.item, 'showAnchors', false); +}) + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/interaction/createEdge/demo/click.js b/packages/site/examples/interaction/createEdge/demo/click.js new file mode 100644 index 0000000000..6d333bb835 --- /dev/null +++ b/packages/site/examples/interaction/createEdge/demo/click.js @@ -0,0 +1,53 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node1', x: 350, y: 200 }, + { id: 'node2', x: 350, y: 250 }, + { id: 'node3', x: 100, y: 200 }, + ], +}; +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Click the source and target node to create a new edge.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: ['create-edge'], + }, + defaultEdge: { + type: 'quadratic', + style: { + stroke: '#F6BD16', + lineWidth: 2, + }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('aftercreateedge', (e) => { + const edges = graph.save().edges; + G6.Util.processParallelEdges(edges); + graph.getEdges().forEach((edge, i) => { + graph.updateItem(edge, { + curveOffset: edges[i].curveOffset, + curvePosition: edges[i].curvePosition, + }); + }); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/interaction/createEdge/demo/drag-link-point.js b/packages/site/examples/interaction/createEdge/demo/drag-link-point.js new file mode 100644 index 0000000000..262ec86193 --- /dev/null +++ b/packages/site/examples/interaction/createEdge/demo/drag-link-point.js @@ -0,0 +1,273 @@ +import G6 from '@antv/g6'; + +// G6.Util.processParallelEdges processes the edges with same source node and target node, +// on this basis, processParallelEdgesOnAnchorPoint consider the end nodes and anchor points in the same time. +const processParallelEdgesOnAnchorPoint = ( + edges, + offsetDiff = 15, + multiEdgeType = 'quadratic', + singleEdgeType = undefined, + loopEdgeType = undefined +) => { + const len = edges.length; + const cod = offsetDiff * 2; + const loopPosition = [ + 'top', + 'top-right', + 'right', + 'bottom-right', + 'bottom', + 'bottom-left', + 'left', + 'top-left', + ]; + const edgeMap = {}; + const tags = []; + const reverses = {}; + for (let i = 0; i < len; i++) { + const edge = edges[i]; + const { source, target, sourceAnchor, targetAnchor } = edge; + const sourceTarget = `${source}|${sourceAnchor}-${target}|${targetAnchor}`; + + if (tags[i]) continue; + if (!edgeMap[sourceTarget]) { + edgeMap[sourceTarget] = []; + } + tags[i] = true; + edgeMap[sourceTarget].push(edge); + for (let j = 0; j < len; j++) { + if (i === j) continue; + const sedge = edges[j]; + const { source: src, target: dst, sourceAnchor: srcAnchor, targetAnchor: dstAnchor } = sedge; + + // 两个节点之间共同的边 + // 第一条的source = 第二条的target + // 第一条的target = 第二条的source + if (!tags[j]) { + if (source === dst && sourceAnchor === dstAnchor + && target === src && targetAnchor === srcAnchor) { + edgeMap[sourceTarget].push(sedge); + tags[j] = true; + reverses[`${src}|${srcAnchor}|${dst}|${dstAnchor}|${edgeMap[sourceTarget].length - 1}`] = true; + } else if (source === src && sourceAnchor === srcAnchor + && target === dst && targetAnchor === dstAnchor) { + edgeMap[sourceTarget].push(sedge); + tags[j] = true; + } + } + } + } + + for (const key in edgeMap) { + const arcEdges = edgeMap[key]; + const { length } = arcEdges; + for (let k = 0; k < length; k++) { + const current = arcEdges[k]; + if (current.source === current.target) { + if (loopEdgeType) current.type = loopEdgeType; + // 超过8条自环边,则需要重新处理 + current.loopCfg = { + position: loopPosition[k % 8], + dist: Math.floor(k / 8) * 20 + 50, + }; + continue; + } + if (length === 1 && singleEdgeType && (current.source !== current.target || current.sourceAnchor !== current.targetAnchor)) { + current.type = singleEdgeType; + continue; + } + current.type = multiEdgeType; + const sign = + (k % 2 === 0 ? 1 : -1) * (reverses[`${current.source}|${current.sourceAnchor}|${current.target}|${current.targetAnchor}|${k}`] ? -1 : 1); + if (length % 2 === 1) { + current.curveOffset = sign * Math.ceil(k / 2) * cod; + } else { + current.curveOffset = sign * (Math.floor(k / 2) * cod + offsetDiff); + } + } + } + return edges; +}; + + +const data = { + nodes: [ + { id: 'node1', x: 350, y: 100 }, + { id: 'node2', x: 350, y: 250 }, + ], +}; +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Hover the node and the anchor points will show up, drag from and drop on the anchor points to create edges.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +// custom a node with anchor-point shapes +G6.registerNode('rect-node', { + // draw anchor-point circles according to the anchorPoints in afterDraw + afterDraw(cfg, group) { + const bbox = group.getBBox(); + const anchorPoints = this.getAnchorPoints(cfg) + anchorPoints.forEach((anchorPos, i) => { + group.addShape('circle', { + attrs: { + r: 5, + x: bbox.x + bbox.width * anchorPos[0], + y: bbox.y + bbox.height * anchorPos[1], + fill: '#fff', + stroke: '#5F95FF' + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') + anchorPointIdx: i, // flag the idx of the anchor-point circle + links: 0, // cache the number of edges connected to this shape + visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state + draggable: true // allow to catch the drag events on this shape + }) + }) + }, + getAnchorPoints(cfg) { + return cfg.anchorPoints || [[0, 0.5], [0.33, 0], [0.66, 0], [1, 0.5], [0.33, 1], [0.66, 1]]; + }, + // response the state changes and show/hide the link-point circles + setState(name, value, item) { + if (name === 'showAnchors') { + const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point'); + anchorPoints.forEach(point => { + if (value || point.get('links') > 0) point.show() + else point.hide() + }) + } + } +}, 'rect') + +let sourceAnchorIdx, targetAnchorIdx; + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: [ + // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles + { + type: 'drag-node', + shouldBegin: e => { + if (e.target.get('name') === 'anchor-point') return false; + return true; + } + }, + // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles + { + type: 'create-edge', + trigger: 'drag', // set the trigger to be drag to make the create-edge triggered by drag + shouldBegin: e => { + // avoid beginning at other shapes on the node + if (e.target && e.target.get('name') !== 'anchor-point') return false; + sourceAnchorIdx = e.target.get('anchorPointIdx'); + e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle + return true; + }, + shouldEnd: e => { + // avoid ending at other shapes on the node + if (e.target && e.target.get('name') !== 'anchor-point') return false; + if (e.target) { + targetAnchorIdx = e.target.get('anchorPointIdx'); + e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle + return true; + } + targetAnchorIdx = undefined; + return true; + }, + }], + }, + defaultNode: { + type: 'rect-node', + style: { + fill: '#eee', + stroke: '#ccc', + } + }, + defaultEdge: { + type: 'quadratic', + style: { + stroke: '#F6BD16', + lineWidth: 2, + }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('aftercreateedge', (e) => { + // update the sourceAnchor and targetAnchor for the newly added edge + graph.updateItem(e.edge, { + sourceAnchor: sourceAnchorIdx, + targetAnchor: targetAnchorIdx + }) + + // update the curveOffset for parallel edges + const edges = graph.save().edges; + processParallelEdgesOnAnchorPoint(edges); + graph.getEdges().forEach((edge, i) => { + graph.updateItem(edge, { + curveOffset: edges[i].curveOffset, + curvePosition: edges[i].curvePosition, + }); + }); +}); + +// after drag from the first node, the edge is created, update the sourceAnchor +graph.on('afteradditem', e => { + if (e.item && e.item.getType() === 'edge') { + graph.updateItem(e.item, { + sourceAnchor: sourceAnchorIdx + }); + } +}) + +// if create-edge is canceled before ending, update the 'links' on the anchor-point circles +graph.on('afterremoveitem', e => { + if (e.item && e.item.source && e.item.target) { + const sourceNode = graph.findById(e.item.source); + const targetNode = graph.findById(e.item.target); + const { sourceAnchor, targetAnchor } = e.item; + if (sourceNode && !isNaN(sourceAnchor)) { + const sourceAnchorShape = sourceNode.getContainer().find(ele => (ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === sourceAnchor)); + sourceAnchorShape.set('links', sourceAnchorShape.get('links') - 1); + } + if (targetNode && !isNaN(targetAnchor)) { + const targetAnchorShape = targetNode.getContainer().find(ele => (ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === targetAnchor)); + targetAnchorShape.set('links', targetAnchorShape.get('links') - 1); + } + } +}) + +// some listeners to control the state of nodes to show and hide anchor-point circles +graph.on('node:mouseenter', e => { + graph.setItemState(e.item, 'showAnchors', true); +}) +graph.on('node:mouseleave', e => { + graph.setItemState(e.item, 'showAnchors', false); +}) +graph.on('node:dragenter', e => { + graph.setItemState(e.item, 'showAnchors', true); +}) +graph.on('node:dragleave', e => { + graph.setItemState(e.item, 'showAnchors', false); +}) +graph.on('node:dragstart', e => { + graph.setItemState(e.item, 'showAnchors', true); +}) +graph.on('node:dragout', e => { + graph.setItemState(e.item, 'showAnchors', false); +}) + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/interaction/createEdge/demo/drag.js b/packages/site/examples/interaction/createEdge/demo/drag.js new file mode 100644 index 0000000000..7f1db84594 --- /dev/null +++ b/packages/site/examples/interaction/createEdge/demo/drag.js @@ -0,0 +1,58 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node1', x: 350, y: 200 }, + { id: 'node2', x: 350, y: 250 }, + { id: 'node3', x: 100, y: 200 }, + ], +}; +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + "Drag from a source node to a target node to create a new edge. Note that in trigger: 'drag' mode cannot create a self-loop edge"; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: [ + { + type: 'create-edge', + trigger: 'drag', // 'click' by default. options: 'drag', 'click' + }, + ], + }, + defaultEdge: { + style: { + stroke: '#F6BD16', + lineWidth: 2, + }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('aftercreateedge', (e) => { + const edges = graph.save().edges; + G6.Util.processParallelEdges(edges); + graph.getEdges().forEach((edge, i) => { + graph.updateItem(edge, { + curveOffset: edges[i].curveOffset, + curvePosition: edges[i].curvePosition, + }); + }); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/interaction/createEdge/demo/meta.json b/packages/site/examples/interaction/createEdge/demo/meta.json new file mode 100644 index 0000000000..1501188894 --- /dev/null +++ b/packages/site/examples/interaction/createEdge/demo/meta.json @@ -0,0 +1,33 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "click.js", + "title": "点击端点", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*B9dxR4x8UmUAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "drag.js", + "title": "从一个节点拖拽到另一个节点", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*B9dxR4x8UmUAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "click-and-key.js", + "title": "按住 shift 并点击端点", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*B9dxR4x8UmUAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "click-link-point.js", + "title": "点击端点上的子图形", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*WAQ6T4pqCd8AAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "drag-link-point.js", + "title": "从端点上的子图形拖拽", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*WAQ6T4pqCd8AAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/createEdge/index.en.md b/packages/site/examples/interaction/createEdge/index.en.md new file mode 100644 index 0000000000..3a5fd620ab --- /dev/null +++ b/packages/site/examples/interaction/createEdge/index.en.md @@ -0,0 +1,10 @@ +--- +title: Create Edge +order: 13 +--- + +Buit-in behavior `'create-edge'` is a new feature of V3.6.2, which allows the users to create a new edge by some operations. + +## Usage + +`'create-edge'` allows the end users to create a new edge by clicking the source and target node, or dragging from a source node to a target node. For more detail, please refer to [create-edge](/en/docs/manual/middle/states/defaultBehavior#create-edge). diff --git a/packages/site/examples/interaction/createEdge/index.zh.md b/packages/site/examples/interaction/createEdge/index.zh.md new file mode 100644 index 0000000000..cf3898a91f --- /dev/null +++ b/packages/site/examples/interaction/createEdge/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 创建边 +order: 13 +--- + +自 V3.6.2 起支持使用内置交互 `'create-edge'`,允许终端用户通过一些操作创建新到边。 + +## 何时使用 + +`'create-edge'` 允许配置交互方式为点击两个节点、从一个节点拖拽到另一个节点。更多内容请参考 [create-edge](/zh/docs/manual/middle/states/defaultBehavior#create-edge)。 diff --git a/packages/site/examples/interaction/customBehavior/API.en.md b/packages/site/examples/interaction/customBehavior/API.en.md new file mode 100644 index 0000000000..46d455c169 --- /dev/null +++ b/packages/site/examples/interaction/customBehavior/API.en.md @@ -0,0 +1,7 @@ +--- +title: API +--- + +# Custom Behavior + +Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). diff --git a/packages/site/examples/interaction/customBehavior/API.zh.md b/packages/site/examples/interaction/customBehavior/API.zh.md new file mode 100644 index 0000000000..d83a9f5deb --- /dev/null +++ b/packages/site/examples/interaction/customBehavior/API.zh.md @@ -0,0 +1,7 @@ +--- +title: API +--- + +# 自定义复合交互 + +请参考教程 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)。 diff --git a/packages/site/examples/interaction/customBehavior/demo/dragCanvasTwoFingers.js b/packages/site/examples/interaction/customBehavior/demo/dragCanvasTwoFingers.js new file mode 100644 index 0000000000..0db0707830 --- /dev/null +++ b/packages/site/examples/interaction/customBehavior/demo/dragCanvasTwoFingers.js @@ -0,0 +1,67 @@ +import G6 from '@antv/g6'; + +/** + * This demo shows how to custom a behavior to allow drag and zoom canvas with two fingers on touchpad and wheel + * By Shiwu + */ +G6.registerBehavior('double-finger-drag-canvas', { + getEvents: function getEvents() { + return { + wheel: 'onWheel', + }; + }, + + onWheel: function onWheel(ev) { + if (ev.ctrlKey) { + const canvas = graph.get('canvas'); + const point = canvas.getPointByClient(ev.clientX, ev.clientY); + let ratio = graph.getZoom(); + if (ev.wheelDelta > 0) { + ratio = ratio + ratio * 0.05; + } else { + ratio = ratio - ratio * 0.05; + } + graph.zoomTo(ratio, { + x: point.x, + y: point.y, + }); + } else { + const x = ev.deltaX || ev.movementX; + let y = ev.deltaY || ev.movementY; + if (!y && navigator.userAgent.indexOf('Firefox') > -1) y = (-ev.wheelDelta * 125) / 3 + graph.translate(-x, -y); + } + ev.preventDefault(); + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['double-finger-drag-canvas'], + }, + layout: { + type: 'force', + }, +}); + +graph.get('canvas').set('localRefresh', false); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/customBehavior/demo/meta.json b/packages/site/examples/interaction/customBehavior/demo/meta.json new file mode 100644 index 0000000000..5d19e79dab --- /dev/null +++ b/packages/site/examples/interaction/customBehavior/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "dragCanvasTwoFingers.js", + "title": { + "zh": "两指平移画布", + "en": "Drag Canvas by Two fingers" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*eePLR7boe38AAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/customBehavior/index.en.md b/packages/site/examples/interaction/customBehavior/index.en.md new file mode 100644 index 0000000000..a7b9bbbac3 --- /dev/null +++ b/packages/site/examples/interaction/customBehavior/index.en.md @@ -0,0 +1,10 @@ +--- +title: Custom Behavior +order: 10 +--- + +Custom a behavior when the [built-in behaviors](/en/docs/manual/middle/states/defaultBehavior) do not meet your requirements. + +## Usage + +Custom a behavior with `graph.registerBehavior`, refer to [Custom Behavior Doc](/en/docs/manual/middle/states/custom-behavior). The following demo shows how to custom a behavior to allow drag and zoom canvas with two fingers on touchpad and wheel. diff --git a/packages/site/examples/interaction/customBehavior/index.zh.md b/packages/site/examples/interaction/customBehavior/index.zh.md new file mode 100644 index 0000000000..359ff4cc0c --- /dev/null +++ b/packages/site/examples/interaction/customBehavior/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 自定义交互 +order: 10 +--- + +当[内置 behavior](/zh/docs/manual/middle/states/defaultBehavior)不能满足需求时,可以自定义交互。 + +## 使用指南 + +使用 `graph.registerBehavior` 自定义交互,教程参见 [自定义交互](/zh/docs/manual/middle/states/custom-behavior)。下面示例演示了如何使用自定义交互机制实现使用鼠标滚轮或触摸板双指拖动和缩放画布。 diff --git a/packages/site/examples/interaction/dragCanvasHideItem/API.en.md b/packages/site/examples/interaction/dragCanvasHideItem/API.en.md new file mode 100644 index 0000000000..05434a9239 --- /dev/null +++ b/packages/site/examples/interaction/dragCanvasHideItem/API.en.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# Built-in Behaviors + +Refer to [Built-in Behavior](/en/docs/manual/middle/states/defaultBehavior). + +# Custom Behavior + +Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). diff --git a/packages/site/examples/interaction/dragCanvasHideItem/API.zh.md b/packages/site/examples/interaction/dragCanvasHideItem/API.zh.md new file mode 100644 index 0000000000..f672ff9f3b --- /dev/null +++ b/packages/site/examples/interaction/dragCanvasHideItem/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# 内置复合交互 + +请参考教程 [内置 Behavior](/zh/docs/manual/middle/states/defaultBehavior)。 + +# 自定义复合交互 + +请参考教程 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)。 diff --git a/packages/site/examples/interaction/dragCanvasHideItem/demo/hideItem.js b/packages/site/examples/interaction/dragCanvasHideItem/demo/hideItem.js new file mode 100644 index 0000000000..1629b81308 --- /dev/null +++ b/packages/site/examples/interaction/dragCanvasHideItem/demo/hideItem.js @@ -0,0 +1,101 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node0', size: 50, label: '0', x: 326, y: 268 }, + { id: 'node1', size: 30, label: '1', x: 280, y: 384 }, + { id: 'node2', size: 30, label: '2', x: 234, y: 167 }, + { id: 'node3', size: 30, label: '3', x: 391, y: 368 }, + { id: 'node4', size: 30, label: '4', x: 444, y: 209 }, + { id: 'node5', size: 30, label: '5', x: 378, y: 157 }, + { id: 'node6', size: 15, label: '6', x: 229, y: 400 }, + { id: 'node7', size: 15, label: '7', x: 281, y: 440 }, + { id: 'node8', size: 15, label: '8', x: 188, y: 119 }, + { id: 'node9', size: 15, label: '9', x: 287, y: 157 }, + { id: 'node10', size: 15, label: '10', x: 185, y: 200 }, + { id: 'node11', size: 15, label: '11', x: 238, y: 110 }, + { id: 'node12', size: 15, label: '12', x: 239, y: 221 }, + { id: 'node13', size: 15, label: '13', x: 176, y: 160 }, + { id: 'node14', size: 15, label: '14', x: 389, y: 423 }, + { id: 'node15', size: 15, label: '15', x: 441, y: 341 }, + { id: 'node16', size: 15, label: '16', x: 442, y: 398 }, + ], + edges: [ + { source: 'node0', target: 'node1', label: '0-1' }, + { source: 'node0', target: 'node2', label: '0-2' }, + { source: 'node0', target: 'node3', label: '0-3' }, + { source: 'node0', target: 'node4', label: '0-4' }, + { source: 'node0', target: 'node5', label: '0-5' }, + { source: 'node1', target: 'node6', label: '1-6' }, + { source: 'node1', target: 'node7', label: '1-7' }, + { source: 'node2', target: 'node8', label: '2-8' }, + { source: 'node2', target: 'node9', label: '2-9' }, + { source: 'node2', target: 'node10', label: '2-10' }, + { source: 'node2', target: 'node11', label: '2-11' }, + { source: 'node2', target: 'node12', label: '2-12' }, + { source: 'node2', target: 'node13', label: '2-13' }, + { source: 'node3', target: 'node14', label: '3-14' }, + { source: 'node3', target: 'node15', label: '3-15' }, + { source: 'node3', target: 'node16', label: '3-16' }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: [ + 'drag-node', + { + type: 'drag-canvas', + enableOptimize: true, // enable the optimize to hide the shapes beside nodes' keyShape + }, + { + type: 'zoom-canvas', + enableOptimize: true, // enable the optimize to hide the shapes beside nodes' keyShape + }, + ], + }, + defaultNode: { + size: [10, 10], + style: { + lineWidth: 2, + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + size: 1, + style: { + stroke: '#e2e2e2', + lineAppendWidth: 2, + }, + }, + nodeStateStyles: { + yourStateName: { + stroke: '#f00', + lineWidth: 3, + }, + }, + edgeStateStyles: { + yourStateName: { + stroke: '#f00', + lineWidth: 3, + }, + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/dragCanvasHideItem/demo/meta.json b/packages/site/examples/interaction/dragCanvasHideItem/demo/meta.json new file mode 100644 index 0000000000..99f4143b88 --- /dev/null +++ b/packages/site/examples/interaction/dragCanvasHideItem/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "hideItem.js", + "title": { + "zh": "拖拽画布时隐藏节点 keyShape 外所有图形", + "en": "Hide the Shapes beside keyShape of Nodes while Dragging" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*hld6SrA4qacAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/dragCanvasHideItem/index.en.md b/packages/site/examples/interaction/dragCanvasHideItem/index.en.md new file mode 100644 index 0000000000..55c6a924a5 --- /dev/null +++ b/packages/site/examples/interaction/dragCanvasHideItem/index.en.md @@ -0,0 +1,10 @@ +--- +title: Hide Items while Dragging and Zooming +order: 13 +--- + +The global rendering will be triggered frequently when dragging and zooming the canvas, which costs a lot. To improve the performance of drag-canvas and zoom-canvas, v3.5.11 supports a boolean type configuration `enableOptimize` for built-in behavior 'drag-canvas' and 'zoom-canvas' to hide the shapes besides keyShape of nodes. Besides, when the zoom is smaller than a threshold `optimizeZoom`, 'zoom-canvas' will hide the texts to improve the readability and performance when `enableOptimize` is true. + +## Usage + +This demo hide the shapes beside the keyShape of nodes by configuring the built-in behavesides `'drag-canvas'`. and `'zoom-canvas'`. See the configuration `enableOptimize` in [drag-canvas](/en/docs/manual/middle/states/defaultBehavior#drag-canvas) and [zoom-canvas](/en/docs/manual/middle/states/defaultBehavior#zoom-canvas) for detail. diff --git a/packages/site/examples/interaction/dragCanvasHideItem/index.zh.md b/packages/site/examples/interaction/dragCanvasHideItem/index.zh.md new file mode 100644 index 0000000000..8e4fa4e49b --- /dev/null +++ b/packages/site/examples/interaction/dragCanvasHideItem/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 拖拽缩放画布时隐藏元素 +order: 13 +--- + +拖拽画布和缩放画布频繁地全局渲染。为了提升性能,v3.5.11 推出了内置交互 'drag-canvas' 和 'zoom-canvas' 的布尔型配置项 `enableOptimize`。若 `enableOptimize` 为 `true`,则拖拽画布时,将隐藏除节点 keyShape 以外的所有图形。此外,当 `enableOptimize` 为 true 时,画布缩小到一定程度后(缩放值小于 `optimizeZoom`),'zoom-canvas' 将隐藏文字以提升可读性与性能。 + +## 使用指南 + +配置内置交互 'drag-canvas' 和 'zoom-canvas' 的布尔型配置项 `enableOptimize`。详见 [drag-canvas](/zh/docs/manual/middle/states/defaultBehavior#drag-canvas) 和 [zoom-canvas](/zh/docs/manual/middle/states/defaultBehavior#zoom-canvas)。 diff --git a/packages/site/examples/interaction/fitView/API.en.md b/packages/site/examples/interaction/fitView/API.en.md new file mode 100644 index 0000000000..05434a9239 --- /dev/null +++ b/packages/site/examples/interaction/fitView/API.en.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# Built-in Behaviors + +Refer to [Built-in Behavior](/en/docs/manual/middle/states/defaultBehavior). + +# Custom Behavior + +Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). diff --git a/packages/site/examples/interaction/fitView/API.zh.md b/packages/site/examples/interaction/fitView/API.zh.md new file mode 100644 index 0000000000..791a2040f8 --- /dev/null +++ b/packages/site/examples/interaction/fitView/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# 复合交互 + +请参考教程 [内置 Behavior](/en/docs/manual/middle/states/defaultBehavior)。 + +# 自定义复合交互 + +请参考教程 [自定义 Behavior](/en/docs/manual/middle/states/custom-behavior)。 diff --git a/packages/site/examples/interaction/fitView/demo/fitView.js b/packages/site/examples/interaction/fitView/demo/fitView.js new file mode 100644 index 0000000000..cfe135530e --- /dev/null +++ b/packages/site/examples/interaction/fitView/demo/fitView.js @@ -0,0 +1,34 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); + +const tipDiv = document.createElement('div'); +tipDiv.innerHTML = `Press both the keys 'control' and '1' to call graph.fitView. The keys and the called function can be configured.【ATTENTION】: make sure the focus is on the canvas when you pressing keys +
按住 'control' 并按下 '1' 键,将会调用 graph.fitView。组合按键及被调用的函数及其参数均可被配置。【注意】:使用组合件调用函数时,请保证当前焦点在画布上`; +container.appendChild(tipDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 100; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitCenter: true, + modes: { + default: ['shortcuts-call'], + }, +}); +fetch('https://gw.alipayobjects.com/os/bmw-prod/b0ca4b15-bd0c-43ec-ae41-c810374a1d55.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + graph.zoom(2); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 100); + }; diff --git a/packages/site/examples/interaction/fitView/demo/meta.json b/packages/site/examples/interaction/fitView/demo/meta.json new file mode 100644 index 0000000000..73b74bbc00 --- /dev/null +++ b/packages/site/examples/interaction/fitView/demo/meta.json @@ -0,0 +1,18 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "fitView.js", + "title": "使用快捷键适配画布", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*FALhS5MhAzAAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "moveTo.js", + "title": "使用快捷键移动到左上角", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*7RPkSJpwzrQAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/fitView/demo/moveTo.js b/packages/site/examples/interaction/fitView/demo/moveTo.js new file mode 100644 index 0000000000..af3d73212c --- /dev/null +++ b/packages/site/examples/interaction/fitView/demo/moveTo.js @@ -0,0 +1,41 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); + +const tipDiv = document.createElement('div'); +tipDiv.innerHTML = `Press both the keys 'control' and 'm' to call graph.fitView. The keys and the called function can be configured.【ATTENTION】: make sure the focus is on the canvas when you pressing keys +
按住 'control' 并按下 'm' 键,将会调用 graph.moveTo。组合按键及被调用的函数及其参数均可被配置。【注意】:使用组合件调用函数时,请保证当前焦点在画布上`; +container.appendChild(tipDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 100; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitCenter: true, + modes: { + default: [ + { + type: 'shortcuts-call', + functionName: 'moveTo', + functionParams: [0, 0], + combinedKey: 'm', + }, + ], + }, +}); +fetch('https://gw.alipayobjects.com/os/bmw-prod/b0ca4b15-bd0c-43ec-ae41-c810374a1d55.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + graph.zoom(2); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 100); + }; diff --git a/packages/site/examples/interaction/fitView/index.en.md b/packages/site/examples/interaction/fitView/index.en.md new file mode 100644 index 0000000000..cd1fd8f8ea --- /dev/null +++ b/packages/site/examples/interaction/fitView/index.en.md @@ -0,0 +1,12 @@ +--- +title: Fit View with Shortcuts +order: 15 +--- + +Allow the end-user to call a function of Graph with shortcuts keys. + +允许终端用户使用键盘组合键调用 Graph 的函数。 + +## Usage + +E.g. press down 'control' and '1' on keyboard to make the graph fit the canvas. Attention: make sure the focus is on the canvas when the end-user is pressing keys to call the function. For more information, please refer to [shortcuts-call](/en/docs/manual/middle/states/defaultBehavior/shortcuts-call). diff --git a/packages/site/examples/interaction/fitView/index.zh.md b/packages/site/examples/interaction/fitView/index.zh.md new file mode 100644 index 0000000000..e3ca62339b --- /dev/null +++ b/packages/site/examples/interaction/fitView/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 快捷键适应画布 +order: 15 +--- + +允许终端用户使用键盘组合键调用 Graph 的函数。 + +## 如何使用 + +例如按下键盘上的 control 与 1,对图进行适应画布。注意:终端用户使用该功能时焦点必须在画布上才能够正确触发。详细内容参考文档 [shortcuts-call](/zh/docs/manual/middle/states/defaultBehavior/shortcuts-call)。 diff --git a/packages/site/examples/interaction/highlight/API.en.md b/packages/site/examples/interaction/highlight/API.en.md new file mode 100644 index 0000000000..05434a9239 --- /dev/null +++ b/packages/site/examples/interaction/highlight/API.en.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# Built-in Behaviors + +Refer to [Built-in Behavior](/en/docs/manual/middle/states/defaultBehavior). + +# Custom Behavior + +Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). diff --git a/packages/site/examples/interaction/highlight/API.zh.md b/packages/site/examples/interaction/highlight/API.zh.md new file mode 100644 index 0000000000..f672ff9f3b --- /dev/null +++ b/packages/site/examples/interaction/highlight/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# 内置复合交互 + +请参考教程 [内置 Behavior](/zh/docs/manual/middle/states/defaultBehavior)。 + +# 自定义复合交互 + +请参考教程 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)。 diff --git a/packages/site/examples/interaction/highlight/demo/activateRelations.js b/packages/site/examples/interaction/highlight/demo/activateRelations.js new file mode 100644 index 0000000000..409e954deb --- /dev/null +++ b/packages/site/examples/interaction/highlight/demo/activateRelations.js @@ -0,0 +1,101 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-component-tooltip { + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #000; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +`); + +const tooltip = new G6.Tooltip({ + offsetX: 10, + offsetY: 10, + fixToNode: [1, 0.5], + // the types of items that allow the tooltip show up + // 允许出现 tooltip 的 item 类型 + itemTypes: ['node', 'edge'], + // custom the tooltip's content + // 自定义 tooltip 内容 + getContent: (e) => { + const outDiv = document.createElement('div'); + outDiv.style.width = 'fit-content'; + outDiv.style.height = 'fit-content'; + const model = e.item.getModel(); + if (e.item.getType() === 'node') { + outDiv.innerHTML = `${model.name}`; + } else { + const source = e.item.getSource(); + const target = e.item.getTarget(); + outDiv.innerHTML = `来源:${source.getModel().name}
去向:${target.getModel().name}`; + } + return outDiv; + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'force', + edgeStrength: 0.7, + }, + plugins: [tooltip], + modes: { + default: ['drag-canvas', 'activate-relations'], + }, + defaultNode: { + size: [10, 10], + /* style for the keyShape */ + // style: { + // lineWidth: 2, + // fill: '#DEE9FF', + // stroke: '#5B8FF9', + // }, + }, + defaultEdge: { + /* style for the keyShape */ + style: { + stroke: '#aaa', + lineAppendWidth: 2, + opacity: 0.3, + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // nodeStateStyles: { + // active: { + // opacity: 1, + // }, + // inactive: { + // opacity: 0.2, + // }, + // }, + // edgeStateStyles: { + // active: { + // stroke: '#999', + // }, + // }, +}); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/xiaomi.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/highlight/demo/highlightDark.js b/packages/site/examples/interaction/highlight/demo/highlightDark.js new file mode 100644 index 0000000000..2e32c31b7b --- /dev/null +++ b/packages/site/examples/interaction/highlight/demo/highlightDark.js @@ -0,0 +1,147 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-component-tooltip { + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #000; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +`); + +const tooltip = new G6.Tooltip({ + offsetX: 10, + offsetY: 10, + fixToNode: [1, 0.5], + // the types of items that allow the tooltip show up + // 允许出现 tooltip 的 item 类型 + itemTypes: ['node', 'edge'], + // custom the tooltip's content + // 自定义 tooltip 内容 + getContent: (e) => { + const outDiv = document.createElement('div'); + outDiv.style.width = 'fit-content'; + outDiv.style.height = 'fit-content'; + const model = e.item.getModel(); + if (e.item.getType() === 'node') { + outDiv.innerHTML = `${model.name}`; + } else { + const source = e.item.getSource(); + const target = e.item.getTarget(); + outDiv.innerHTML = `来源:${source.getModel().name}
去向:${target.getModel().name}`; + } + return outDiv; + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [tooltip], + layout: { + type: 'force', + edgeStrength: 0.7, + }, + modes: { + default: ['drag-canvas'], + }, + defaultNode: { + size: [10, 10], + style: { + lineWidth: 2, + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + size: 1, + style: { + stroke: '#e2e2e2', + lineAppendWidth: 2, + }, + }, + nodeStateStyles: { + highlight: { + opacity: 1, + }, + dark: { + opacity: 0.2, + }, + }, + edgeStateStyles: { + highlight: { + stroke: '#999', + }, + }, +}); + +function clearAllStats() { + graph.setAutoPaint(false); + graph.getNodes().forEach(function (node) { + graph.clearItemStates(node); + }); + graph.getEdges().forEach(function (edge) { + graph.clearItemStates(edge); + }); + graph.paint(); + graph.setAutoPaint(true); +} + +graph.on('node:mouseenter', function (e) { + const item = e.item; + graph.setAutoPaint(false); + graph.getNodes().forEach(function (node) { + graph.clearItemStates(node); + graph.setItemState(node, 'dark', true); + }); + graph.setItemState(item, 'dark', false); + graph.setItemState(item, 'highlight', true); + graph.getEdges().forEach(function (edge) { + if (edge.getSource() === item) { + graph.setItemState(edge.getTarget(), 'dark', false); + graph.setItemState(edge.getTarget(), 'highlight', true); + graph.setItemState(edge, 'highlight', true); + edge.toFront(); + } else if (edge.getTarget() === item) { + graph.setItemState(edge.getSource(), 'dark', false); + graph.setItemState(edge.getSource(), 'highlight', true); + graph.setItemState(edge, 'highlight', true); + edge.toFront(); + } else { + graph.setItemState(edge, 'highlight', false); + } + }); + graph.paint(); + graph.setAutoPaint(true); +}); +graph.on('node:mouseleave', clearAllStats); +graph.on('canvas:click', clearAllStats); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/xiaomi.json') + .then((res) => res.json()) + .then((data) => { + graph.data({ + nodes: data.nodes, + edges: data.edges.map(function (edge, i) { + edge.id = 'edge' + i; + return Object.assign({}, edge); + }), + }); + + graph.render(); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/highlight/demo/meta.json b/packages/site/examples/interaction/highlight/demo/meta.json new file mode 100644 index 0000000000..7626b14f13 --- /dev/null +++ b/packages/site/examples/interaction/highlight/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "activateRelations.js", + "title": { + "zh": "内置的高亮节点", + "en": "Activate Relations" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*A3iMQo8L_McAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "highlightDark.js", + "title": { + "zh": "自定义高亮", + "en": "Custom Highlight" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*A3iMQo8L_McAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/highlight/index.en.md b/packages/site/examples/interaction/highlight/index.en.md new file mode 100644 index 0000000000..819b31c78e --- /dev/null +++ b/packages/site/examples/interaction/highlight/index.en.md @@ -0,0 +1,13 @@ +--- +title: Highlight Nodes +order: 0 +--- + +Highlighting a node and its neighbors when user hover it is a common requirement in graph analysis applications. + +## Usage + +The demos below show two ways to satisfy the highlight requirement: + +- Built-in behavior: activate-relations; +- `graph.on` or [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). diff --git a/packages/site/examples/interaction/highlight/index.zh.md b/packages/site/examples/interaction/highlight/index.zh.md new file mode 100644 index 0000000000..4a2aaa9235 --- /dev/null +++ b/packages/site/examples/interaction/highlight/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 高亮相邻节点 +order: 0 +--- + +在图分析应用中,鼠标 hover 到某个节点后,高亮其相邻的节点及边是一种非常常见的需求。 + +## 使用指南 + +该示例演示了 G6 内置的 activate-relations 和自定义实现高亮相邻节点的方式。另外,如果内置高亮关注点及其邻居的行为不能满足需求,用户也可以通过使用 `graph.on` 或[自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior) 实现。 diff --git a/packages/site/examples/interaction/hull/API.en.md b/packages/site/examples/interaction/hull/API.en.md new file mode 100644 index 0000000000..5750762068 --- /dev/null +++ b/packages/site/examples/interaction/hull/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/hull/API.zh.md b/packages/site/examples/interaction/hull/API.zh.md new file mode 100644 index 0000000000..b74435a14d --- /dev/null +++ b/packages/site/examples/interaction/hull/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/hull/demo/changeMembers.js b/packages/site/examples/interaction/hull/demo/changeMembers.js new file mode 100644 index 0000000000..f2ca92d069 --- /dev/null +++ b/packages/site/examples/interaction/hull/demo/changeMembers.js @@ -0,0 +1,212 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '1', + label: '公司1', + group: 1, + }, + { + id: '2', + label: '公司2', + group: 1, + }, + { + id: '3', + label: '公司3', + group: 1, + }, + { + id: '4', + label: '公司4', + group: 1, + }, + { + id: '5', + label: '公司5', + group: 2, + }, + { + id: '6', + label: '公司6', + group: 2, + }, + { + id: '7', + label: '公司7', + group: 2, + }, + { + id: '8', + label: '公司8', + group: 2, + }, + { + id: '9', + label: '公司9', + group: 2, + }, + ], + edges: [ + { + source: '1', + target: '1', + type: 'loop', + }, + { + source: '2', + target: '2', + type: 'loop', + }, + { + source: '1', + target: '2', + data: { + type: 'A', + amount: '100,000 元', + date: '2019-08-03', + }, + }, + { + source: '1', + target: '3', + data: { + type: 'B', + amount: '100,000 元', + date: '2019-08-03', + }, + }, + { + source: '2', + target: '5', + data: { + type: 'C', + amount: '100,000 元', + date: '2019-08-03', + }, + }, + { + source: '5', + target: '6', + data: { + type: 'B', + amount: '100,000 元', + date: '2019-08-03', + }, + }, + { + source: '3', + target: '4', + data: { + type: 'C', + amount: '100,000 元', + date: '2019-08-03', + }, + }, + { + source: '4', + target: '7', + data: { + type: 'B', + amount: '100,000 元', + date: '2019-08-03', + }, + }, + { + source: '1', + target: '8', + data: { + type: 'B', + amount: '100,000 元', + date: '2019-08-03', + }, + }, + { + source: '1', + target: '9', + data: { + type: 'C', + amount: '100,000 元', + date: '2019-08-03', + }, + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'zoom-canvas', 'drag-node'], + }, + fitView: true, + layout: { + type: 'grid', + }, +}); + +graph.data(data); +graph.render(); + +const hull1 = graph.createHull({ + id: 'hull1', + type: 'smooth-convex', + padding: 15, + members: graph.getNodes().filter((node) => node.getModel().group === 1), +}); +const hull2 = graph.createHull({ + id: 'hull2', + members: graph.getNodes().filter((node) => node.getModel().group === 2), + padding: 15, + type: 'bubble', + style: { + fill: 'pink', + stroke: 'red', + }, + update: 'drag', +}); + +graph.on('canvas:contextmenu', (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + const item = graph.addItem('node', { + x: ev.x, + y: ev.y, + id: Math.random(), + group: 2, + }); + hull2.addMember(item); +}); + +graph.on('afterupdateitem', (e) => { + if (hull1.members.indexOf(e.item) > -1 || hull1.nonMembers.indexOf(e.item) > -1) { + hull1.updateData(hull1.members); + } +}); + +graph.on('node:dragend', (e) => { + const item = e.item; + const memberIdx = hull2.members.indexOf(item); + if (memberIdx > -1) { + // 如果移出原hull范围,则去掉 + if (!hull2.contain(item)) { + hull2.removeMember(item); + } else { + hull2.updateData(hull2.members); + } + } else { + if (hull2.contain(item)) hull2.addMember(item); + } +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/hull/demo/hull.js b/packages/site/examples/interaction/hull/demo/hull.js new file mode 100644 index 0000000000..3fc9e88a5b --- /dev/null +++ b/packages/site/examples/interaction/hull/demo/hull.js @@ -0,0 +1,134 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node0', size: 50 }, + { id: 'node1', size: 30 }, + { id: 'node2', size: 30 }, + { id: 'node3', size: 30 }, + { id: 'node4', size: 30, isLeaf: true }, + { id: 'node5', size: 30, isLeaf: true }, + { id: 'node6', size: 15, isLeaf: true }, + { id: 'node7', size: 15, isLeaf: true }, + { id: 'node8', size: 15, isLeaf: true }, + { id: 'node9', size: 15, isLeaf: true }, + { id: 'node10', size: 15, isLeaf: true }, + { id: 'node11', size: 15, isLeaf: true }, + { id: 'node12', size: 15, isLeaf: true }, + { id: 'node13', size: 15, isLeaf: true }, + { id: 'node14', size: 15, isLeaf: true }, + { id: 'node15', size: 15, isLeaf: true }, + { id: 'node16', size: 15, isLeaf: true }, + ], + edges: [ + { source: 'node0', target: 'node1' }, + { source: 'node0', target: 'node2' }, + { source: 'node0', target: 'node3' }, + { source: 'node0', target: 'node4' }, + { source: 'node0', target: 'node5' }, + { source: 'node1', target: 'node6' }, + { source: 'node1', target: 'node7' }, + { source: 'node2', target: 'node8' }, + { source: 'node2', target: 'node9' }, + { source: 'node2', target: 'node10' }, + { source: 'node2', target: 'node11' }, + { source: 'node2', target: 'node12' }, + { source: 'node2', target: 'node13' }, + { source: 'node3', target: 'node14' }, + { source: 'node3', target: 'node15' }, + { source: 'node3', target: 'node16' }, + ], +}; +const nodes = data.nodes; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Wait for the layout to complete...'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'zoom-canvas', 'drag-node', 'lasso-select'], + }, + layout: { + type: 'force', + preventOverlap: true, + linkDistance: (d) => { + if (d.source.id === 'node0') { + return 300; + } + return 60; + }, + nodeStrength: (d) => { + if (d.isLeaf) { + return -50; + } + return -10; + }, + edgeStrength: (d) => { + if (d.source.id === 'node1' || d.source.id === 'node2' || d.source.id === 'node3') { + return 0.7; + } + return 0.1; + }, + }, +}); +graph.data({ + nodes, + edges: data.edges.map(function (edge, i) { + edge['id'] = 'edge' + i; + return Object.assign({}, edge); + }), +}); +graph.render(); + +let centerNodes = graph.getNodes().filter((node) => !node.getModel().isLeaf); + +graph.on('afterlayout', () => { + descriptionDiv.innerHTML = ''; + const hull1 = graph.createHull({ + id: 'centerNode-hull', + type: 'bubble', + members: centerNodes, + padding: 10, + }); + + const hull2 = graph.createHull({ + id: 'leafNode-hull1', + members: ['node6', 'node7'], + padding: 10, + style: { + fill: 'lightgreen', + stroke: 'green', + }, + }); + + const hull3 = graph.createHull({ + id: 'leafNode-hull2', + members: ['node8', 'node9', 'node10', 'node11', 'node12', 'node13'], + padding: 10, + style: { + fill: 'lightgreen', + stroke: 'green', + }, + }); + + graph.on('afterupdateitem', (e) => { + hull1.updateData(hull1.members); + hull2.updateData(hull2.members); + hull3.updateData(hull3.members); + }); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/interaction/hull/demo/meta.json b/packages/site/examples/interaction/hull/demo/meta.json new file mode 100644 index 0000000000..56d2807796 --- /dev/null +++ b/packages/site/examples/interaction/hull/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "hull.js", + "title": { + "zh": "用轮廓包裹节点集合", + "en": "Use hulls to wrap the node sets." + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*krsJQb6tH-oAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "changeMembers.js", + "title": { + "zh": "修改包裹内部成员", + "en": "Interactively change the members in the hull." + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*TNxDRL0t0GsAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/hull/index.en.md b/packages/site/examples/interaction/hull/index.en.md new file mode 100644 index 0000000000..a83d7ceb10 --- /dev/null +++ b/packages/site/examples/interaction/hull/index.en.md @@ -0,0 +1,10 @@ +--- +title: Hull +order: 5 +--- + +Use a smooth contour to wrap a specified set of nodes. + +## Usage + +Hull is used for wrapping a set of nodes, to emphasize the sets or groups in the graph and do not affect the original node positions and the layout. There are three types of hull: `round-convex`, `smooth-convex` and `bubble`. The `round-convex` type generates a rounded polygon, the `smooth-convex` configuration generates a closed spline contour and the `bubble` type generates a smooth concave hull that could avoids `nonMembers`. The hulls could be updated interactively. In the first example below, the blue one is of bubble type and green is a rounded convex hull. The two hulls listen for node changes and then be updated, so that they always wrap the nodes. In the second example, the red bubble allows its members to be dragged in and out, and its members could be added by right clicking on the canvas. The blue bubble is updated according to node changes. For the complete configuration, please refer to the API documentation: [createHull](/en/docs/api/graphFunc/hull#createhullcfg-hullcfg). diff --git a/packages/site/examples/interaction/hull/index.zh.md b/packages/site/examples/interaction/hull/index.zh.md new file mode 100644 index 0000000000..df1afbdf8d --- /dev/null +++ b/packages/site/examples/interaction/hull/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 节点集轮廓包裹 +order: 5 +--- + +使用平滑的轮廓来包裹指定的一组节点集合。 + +## 何时使用 + +节点集轮廓包裹常用语交互过程中,对团伙标记与分析,尤其是在不希望影响原图布局的情况下。轮廓包裹的形状支持 `round-convex` / `smooth-convex` / `bubble` 三种类型,默认为 `round-convex` 类型。`round-convex` 为圆角凸包轮廓,`smooth-convex` 为平滑曲线凸包轮廓,这两种凸包轮廓不可排除配置项中的 nonMembers;`bubble` 为自由凹包轮廓,可以排除 nonMembers。可配合事件监听实现轮廓的动态更新。下面的第一个示例中,蓝色为 bubble 类型轮廓, 绿色为 round-convex 类型轮廓,轮廓监听节点变化进行更新,始终包裹节点。第二个示例中,红色气泡配合节点 drag 事件,实现节点拖进拖出,右击可以增加内部成员;蓝色凸包随节点位置更新,始终包裹节点。完整的配置项请参考 API 文档: [createHull](/zh/docs/api/graphFunc/hull#createhullcfg-hullcfg)。 diff --git a/packages/site/examples/interaction/label/API.en.md b/packages/site/examples/interaction/label/API.en.md new file mode 100644 index 0000000000..04b89816ac --- /dev/null +++ b/packages/site/examples/interaction/label/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/label/API.zh.md b/packages/site/examples/interaction/label/API.zh.md new file mode 100644 index 0000000000..351d2b713d --- /dev/null +++ b/packages/site/examples/interaction/label/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/label/demo/changeImg.js b/packages/site/examples/interaction/label/demo/changeImg.js new file mode 100644 index 0000000000..e8585c2361 --- /dev/null +++ b/packages/site/examples/interaction/label/demo/changeImg.js @@ -0,0 +1,114 @@ +import G6 from '@antv/g6'; + +/** + * 本示例演示以下功能: + * 1、如何使用图片作为节点背景; + * 2、点击切换节点背景图片。 + * + */ + +const img = new Image(); +img.src = 'https://gw.alipayobjects.com/os/s/prod/antv/assets/image/logo-with-text-73b8a.svg'; + +// 点击图片节点,切换背景图片 +const img2 = new Image(); +img2.src = 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wAmHQJbNVdwAAAAAAAAAAABkARQnAQ'; +const data = { + nodes: [ + { + x: 150, + y: 100, + type: 'circleNode', + label: 'circle', + id: 'node1', + labelCfg: { + position: 'center', + }, + }, + { + x: 350, + y: 100, + type: 'image', + id: 'node2', + img: img.src, + size: [120, 60], + label: 'avatar', + style: { + cursor: 'pointer', + }, + labelCfg: { + position: 'bottom', + }, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + label: 'line', + labelCfg: { + refY: 10, + }, + }, + ], +}; +// 避免拖动过程中闪烁:使用加载已经LOAD好的图片 +img.onload = function () { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultNode: { + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + color: '#e2e2e2', + }, + modes: { + default: [ + 'drag-node', + { + type: 'drag-node', + }, + ], + }, + }); + graph.data(data); + graph.render(); + + graph.on('node:click', function (evt) { + const target = evt.target; + + const type = target.get('type'); + const hasChangeBg = target.get('hasChangeBg'); + if (type === 'image') { + if (!hasChangeBg) { + // 点击图片节点时,切换背景图片 + target.attr('img', img2); + target.attr('imgSrc', 'http://seopic.699pic.com/photo/50055/5642.jpg_wh1200.jpg'); + target.set('hasChangeBg', true); + } else { + target.attr('img', img); + target.attr( + 'imgSrc', + 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1566553535233&di=b0b17eeea7bd7356a6f42ebfd48e9441&imgtype=0&src=http%3A%2F%2Fa2.att.hudong.com%2F64%2F29%2F01300543361379145388299988437_s.jpg', + ); + target.set('hasChangeBg', false); + } + graph.paint(); + } + }); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; +}; diff --git a/packages/site/examples/interaction/label/demo/meta.json b/packages/site/examples/interaction/label/demo/meta.json new file mode 100644 index 0000000000..3a3152b918 --- /dev/null +++ b/packages/site/examples/interaction/label/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "update.js", + "title": { + "zh": "更新节点或边上的标签", + "en": "Update the Label" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*NJm8S4sfgp4AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "changeImg.js", + "title": { + "zh": "切换节点背景图片", + "en": "Change the Image" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*kwp1S6PJIVUAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/label/demo/update.js b/packages/site/examples/interaction/label/demo/update.js new file mode 100644 index 0000000000..b9d033f31f --- /dev/null +++ b/packages/site/examples/interaction/label/demo/update.js @@ -0,0 +1,110 @@ +import G6 from '@antv/g6'; +/** + * 本示例演示以下功能: + * 鼠标 hover 节点更新节点样式及其标签文本 + * 鼠标 hover 边更新边样式及其标签文本 + * by 十吾 + */ + +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 100, + label: 'label before\nbeen hovered', + }, + { + id: 'node2', + x: 400, + y: 100, + label: 'label before\nbeen hovered', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + label: 'label before\nbeen hovered', + labelCfg: { + refY: 10, + }, + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultEdge: { + color: '#e2e2e2', + lineAppendWidth: 3, + }, +}); +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', function (evt) { + const node = evt.item; + const model = node.getModel(); + model.oriLabel = model.label; + graph.updateItem(node, { + label: `after been hovered ${model.id}`, + labelCfg: { + style: { + fill: '#003a8c', + }, + }, + }); +}); + +graph.on('node:mouseleave', function (evt) { + const node = evt.item; + const model = node.getModel(); + graph.updateItem(node, { + label: model.oriLabel, + labelCfg: { + style: { + fill: '#555', + }, + }, + }); +}); + +graph.on('edge:mouseenter', function (evt) { + const edge = evt.item; + const model = edge.getModel(); + model.oriLabel = model.label; + graph.updateItem(edge, { + label: 'after been hovered', + labelCfg: { + style: { + fill: '#003a8c', + }, + }, + }); +}); + +graph.on('edge:mouseleave', function (evt) { + const edge = evt.item; + graph.setItemState(edge, 'hover', false); + graph.updateItem(edge, { + label: 'label before \n been hovered', + labelCfg: { + style: { + fill: '#555', + }, + }, + }); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/label/index.en.md b/packages/site/examples/interaction/label/index.en.md new file mode 100644 index 0000000000..26c683635b --- /dev/null +++ b/packages/site/examples/interaction/label/index.en.md @@ -0,0 +1,12 @@ +--- +title: Label and Background Updating +order: 3 +--- + +Update the label or background of items with interaction. + +## Usage + +In some scenario, labels and background need to be updated by interaction. The following demos show the implementation. + +For more information about properties of label, please refre to [Label on the Node](/en/docs/manual/middle/elements/nodes/defaultNode/#label-and-labelcfg) and [Label on the Edge](/en/docs/manual/middle/elements/edges/defaultEdge/#label-and-labelcfg). diff --git a/packages/site/examples/interaction/label/index.zh.md b/packages/site/examples/interaction/label/index.zh.md new file mode 100644 index 0000000000..78ffb4d756 --- /dev/null +++ b/packages/site/examples/interaction/label/index.zh.md @@ -0,0 +1,12 @@ +--- +title: 改变标签或背景 +order: 3 +--- + +交互过程中动态改变节点或边上的文本。 + +## 使用指南 + +在有些业务场景中,需要通过交互来修改节点或边上的文本,也有可能会通过点击来切换节点的背景图片,对于这类需求,可以使用演示案例中的方式来实现。 + +G6 中支持的所有文本属性可以参考 [节点上的标签文本](/zh/docs/manual/middle/elements/nodes/defaultNode/#标签文本-label-及其配置-labelcfg) 和 [边上的标签文本](/zh/docs/manual/middle/elements/edges/defaultEdge/#标签文本-label-及其配置-labelcfg)。 diff --git a/packages/site/examples/interaction/pagination/demo/dagrePagination.js b/packages/site/examples/interaction/pagination/demo/dagrePagination.js new file mode 100644 index 0000000000..0fa0a4ea8d --- /dev/null +++ b/packages/site/examples/interaction/pagination/demo/dagrePagination.js @@ -0,0 +1,515 @@ +import G6 from '@antv/g6'; + +// the max number of nodes for each level +const MAX_NUM_EACH_LEVEL = 3; +let nodeMap = {}; +let curNewNodeIds = []; + +const data = { + nodes: [ + { + id: '1', + label: 'level 1', + }, + { + id: '2-1', + label: 'level 2-1', + }, + { + id: '2-2', + label: 'level 2-2', + }, + { + id: '2-3', + label: 'level 2-3', + }, + { + id: '2-4', + label: 'level 2-4', + }, + { + id: '2-5', + label: 'level 2-5', + }, + { + id: '2-6', + label: 'level 2-6', + }, + { + id: '3', + label: 'level 3', + }, + { + id: '4', + label: 'level 4', + }, + { + id: '5-1', + label: 'level 5-1', + }, + { + id: '5-2', + label: 'level 5-2', + }, + { + id: '5-3', + label: 'level 5-3', + }, + { + id: '5-4', + label: 'level 5-4', + }, + { + id: '5-5', + label: 'level 5-5', + }, + { + id: '5-6', + label: 'level 5-6', + }, + { + id: '5-7', + label: 'level 5-7', + }, + { + id: '5-8', + label: 'level 5-8', + }, + ], + edges: [ + { + source: '1', + target: '2-1', + }, + { + source: '1', + target: '2-2', + }, + { + source: '1', + target: '2-3', + }, + { + source: '1', + target: '2-4', + }, + { + source: '1', + target: '2-5', + }, + { + source: '1', + target: '2-6', + }, + { + source: '2-1', + target: '3', + }, + { + source: '2-2', + target: '3', + }, + { + source: '2-3', + target: '3', + }, + { + source: '2-4', + target: '3', + }, + { + source: '2-5', + target: '3', + }, + { + source: '2-6', + target: '3', + }, + { + source: '3', + target: '4', + }, + { + source: '4', + target: '5-1', + }, + { + source: '4', + target: '5-2', + }, + { + source: '4', + target: '5-3', + }, + { + source: '4', + target: '5-4', + }, + { + source: '4', + target: '5-5', + }, + { + source: '4', + target: '5-6', + }, + { + source: '4', + target: '5-7', + }, + { + source: '4', + target: '5-8', + }, + ], +}; + +const compare = (property) => { + return (obj1, obj2) => { + return obj1[property] - obj2[property]; + }; +}; + +const curNodesToNewData = (levels, initPos) => { + const resData = { nodes: [], edges: [] }; + curNewNodeIds = []; + let newNodeMap = {}; + levels.forEach((level) => { + if (level.curNodes) { + for (let i = 0; i < level.nodes.length; i++) { + const node = level.nodes[i]; + if (i >= level.curBeginIdx && i < level.curEndIdx) { + if (!node.x || !node.y) { + node.x = initPos[0]; + node.y = initPos[1]; + } + } else { + node.x = undefined; + node.y = undefined; + } + } + resData.nodes = resData.nodes.concat(level.curNodes); + } else { + resData.nodes = resData.nodes.concat(level.nodes); + } + }); + resData.nodes.forEach((node) => { + newNodeMap[node.id] = node; + if (Object.keys(nodeMap).length !== 0 && !nodeMap[node.id]) { + curNewNodeIds.push(node.id); + } + }); + + data.edges.forEach((edge) => { + if (newNodeMap[edge.source] && newNodeMap[edge.target]) { + resData.edges.push(edge); + } + }); + nodeMap = newNodeMap; + return resData; +}; + +const container = document.getElementById('container'); +const tipDiv = document.createElement('div'); +tipDiv.id = 'tip'; +tipDiv.innerHTML = `
Hover the nodes of level 2 and leve 5, and click the triangle icons to switch the nodes patination.
+
将鼠标移动到 level 2 和 level 5 的节点上后,点击左右小三角按钮以切换该层级的节点,达到分页效果
`; +container.appendChild(tipDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 50; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + layout: { + type: 'dagre', + nodesepFunc: (d) => { + return 20; + }, + ranksep: 30, + controlPoints: true, + }, + defaultNode: { + type: 'rect', + size: [70, 30], + style: { + radius: 10, + }, + }, + defaultEdge: { + type: 'cubic-vertical', + style: { + radius: 20, + offset: 45, + endArrow: true, + lineWidth: 2, + stroke: '#C2C8D5', + }, + }, + nodeStateStyles: { + selected: { + stroke: '#d9d9d9', + fill: '#5394ef', + }, + }, + modes: { + default: ['drag-canvas', 'zoom-canvas', 'click-select'], + }, +}); +graph.data(data); +graph.render(); + +// stash origin data +const levelsMap = {}; +// group the nodes according to their y, which indicates the level +data.nodes.forEach((node) => { + if (!levelsMap[node.y]) { + levelsMap[node.y] = { + y: node.y, + nodes: [], + }; + } + levelsMap[node.y].nodes.push(node); +}); +const unsortedlevels = []; +Object.keys(levelsMap).forEach((key) => { + unsortedlevels.push(levelsMap[key]); +}); +// sort the levels according to the y. +// and slice the nodes for each level if the num of nodes is over MAX_NUM_EACH_LEVEL +const levels = unsortedlevels.sort(compare('y')); +levels.forEach((level, k) => { + if (level.nodes.length > MAX_NUM_EACH_LEVEL) { + level.curBeginIdx = 0; + level.curEndIdx = MAX_NUM_EACH_LEVEL; + level.curNodes = []; + level.nodes.forEach((levelNode, i) => { + levelNode.overflow = true; + levelNode.levelIdx = k; + if (i < MAX_NUM_EACH_LEVEL && i >= 0) { + level.curNodes.push(levelNode); + } + }); + } else { + level.nodes.forEach((levelNode) => { + levelNode.levelIdx = k; + }); + } +}); + +graph.changeData(curNodesToNewData(levels)); +graph.fitView(); +graph.set('animate', true); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 50); + }; + +const iconMap = {}; + +// draw the icons on the root graphics group of the graph +const drawIcons = (nodeId) => { + const node = graph.findById(nodeId); + const model = node.getModel(); + delayDestroyIcons(nodeId, 2000); + if (model.overflow) { + const levelIdx = model.levelIdx; + const y = model.y; + if (iconMap[levelIdx] && !iconMap[levelIdx].destroyed) return; + const level = levels[levelIdx]; + const graphicsGroup = graph.getGroup(); + const prePos = [level.curNodes[0].x - (level.curNodes[0].size[0] / 2 || 20) - 10, y]; + const nextPos = [level.curNodes[2].x + (level.curNodes[0].size[0] / 2 || 20) + 10, y]; + const preIcon = graphicsGroup.addShape('marker', { + attrs: { + symbol: 'triangle', + x: prePos[0], + y: prePos[1], + r: 5, + fill: '#333', + opacity: 0, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `pre-icon`, + levelIdx, + }); + const nextIcon = graphicsGroup.addShape('marker', { + attrs: { + symbol: 'triangle-down', + x: nextPos[0], + y: nextPos[1], + r: 5, + fill: '#333', + opacity: 0, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `next-icon`, + levelIdx, + }); + + if (level.curBeginIdx > 0) { + preIcon.animate( + { + opacity: 1, + }, + { + duration: 150, + repeat: false, + }, + ); + } + if (level.curEndIdx < level.nodes.length) { + nextIcon.animate( + { + opacity: 1, + }, + { + duration: 150, + repeat: false, + }, + ); + } + + iconMap[levelIdx] = { + preIcon, + nextIcon, + destroyed: false, + }; + return true; + } + return false; +}; + +// update the icons +const updateIcons = (levelIdx) => { + if (!levelIdx || !iconMap[levelIdx]) return; + const preIcon = iconMap[levelIdx].preIcon; + const nextIcon = iconMap[levelIdx].nextIcon; + const level = levels[levelIdx]; + + const curNodes = level.curNodes; + if (!curNodes) return; + const prePos = [curNodes[0].x - (curNodes[0].size[0] / 2 || 20) - 10, curNodes[0].y]; + const nextPos = [curNodes[2].x + (curNodes[0].size[0] / 2 || 20) + 10, curNodes[2].y]; + preIcon.attr({ + opacity: level.curBeginIdx <= 0 ? 0 : 1, + x: prePos[0], + y: prePos[1], + }); + nextIcon.attr({ + opacity: level.curEndIdx >= level.nodes.length ? 0 : 1, + x: nextPos[0], + y: nextPos[1], + }); +}; + +// destroy the icons +const destroyIcons = (levelIdx) => { + if (!iconMap[levelIdx]) return; + const preIcon = iconMap[levelIdx].preIcon; + const nextIcon = iconMap[levelIdx].nextIcon; + if (preIcon && !preIcon.destroyed) { + preIcon.animate( + { + opacity: 0, + }, + { + duration: 150, + repeat: false, + }, + ); + setTimeout(() => { + preIcon.remove(); + preIcon.destroy(); + }, 150); + } + if (nextIcon && !nextIcon.destroyed) { + nextIcon.animate( + { + opacity: 0, + }, + { + duration: 150, + repeat: false, + }, + ); + setTimeout(() => { + nextIcon.remove(); + nextIcon.destroy(); + }, 150); + } + iconMap[levelIdx].destroyed = true; +}; + +// destroy the icons with delay +const delayDestroyIcons = (levelIdx, delay = 2000) => { + if (!iconMap[levelIdx] || typeof window === 'undefined') return; + if (iconMap[levelIdx].timeouter) { + window.clearTimeout(iconMap[levelIdx].timeouter); + } + iconMap[levelIdx].timeouter = window.setTimeout(() => { + destroyIcons(levelIdx); + }, delay); +}; + +// mouseenter the node to show the previous/next icons +graph.on('node:mouseenter', (e) => { + drawIcons(e.item.getID()); +}); + +// mouseleave the node to destroy the icons +graph.on('node:mouseleave', (e) => { + const levelIdx = e.item.getModel().levelIdx; + delayDestroyIcons(levelIdx, 2000); +}); + +// click the icon to changeData +graph.on('click', (e) => { + if (e.name === 'click') return; + const marker = e.target; + const targetName = marker.get('name'); + if (targetName !== 'pre-icon' && targetName !== 'next-icon') return; + const levelIdx = marker.get('levelIdx'); + const level = levels[levelIdx]; + const oriNodes = level.nodes; + if (targetName === 'pre-icon') { + if (level.curBeginIdx <= 0) return; // touch the top + level.curBeginIdx--; + level.curEndIdx--; + } else { + if (level.curEndIdx >= oriNodes.length) return; // touch the bottom + level.curBeginIdx++; + level.curEndIdx++; + } + level.curNodes = oriNodes.slice(level.curBeginIdx, level.curEndIdx); + + const initPos = [marker.attr('x'), level.y]; + + graph.changeData(curNodesToNewData(levels, initPos)); + curNewNodeIds.forEach((newId) => { + const newNode = graph.findById(newId); + const nodeGroup = newNode.getContainer(); + nodeGroup.attr('opacity', 0); + nodeGroup.animate( + { + opacity: 1, + }, + 150, + ); + }); +}); + +// update the icon after changeData +graph.on('afterlayout', (e) => { + Object.keys(iconMap).forEach((levelIdx) => { + if (!iconMap[levelIdx] || iconMap[levelIdx].destroyed) return; + updateIcons(levelIdx); + }); +}); diff --git a/packages/site/examples/interaction/pagination/demo/meta.json b/packages/site/examples/interaction/pagination/demo/meta.json new file mode 100644 index 0000000000..e3408bbca1 --- /dev/null +++ b/packages/site/examples/interaction/pagination/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "treePagination.js", + "title": { + "zh": "子树节点分页", + "en": "Pagination for Subtree" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*v7yASaVZDisAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "dagrePagination.js", + "title": { + "zh": "dagre 节点分页", + "en": "Pagination for Dagre Graph" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ztMMS5WPancAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/pagination/demo/treePagination.js b/packages/site/examples/interaction/pagination/demo/treePagination.js new file mode 100644 index 0000000000..a9de5ba820 --- /dev/null +++ b/packages/site/examples/interaction/pagination/demo/treePagination.js @@ -0,0 +1,320 @@ +import G6 from '@antv/g6'; + +// the max number of nodes for each subtree +const MAX_NUM_EACH_SUBTREE = 3; + +const container = document.getElementById('container'); +const tipDiv = document.createElement('div'); +tipDiv.id = 'tip'; +tipDiv.innerHTML = `
Hover the nodes of a subtree, and click the triangle icons to switch the nodes patination.
+
将鼠标移动到有多个子节点的子树上,点击左右小三角按钮以切换该层级的节点,达到分页效果
`; +container.appendChild(tipDiv); + +fetch('https://gw.alipayobjects.com/os/bmw-prod/a3ae9b40-ff40-434a-894f-b10c535f8b9f.json') + .then((res) => res.json()) + .then((data) => { + const stashSubtrees = {}; + G6.Util.traverseTreeUp(data, (subtree) => { + // process the label + (subtree.label = subtree.id), + (subtree.labelCfg = { + offset: 10, + position: subtree.children && subtree.children.length > 0 ? 'left' : 'right', + }); + + // stash the origin children for the subtree to be pruned + if (subtree.children && subtree.children.length > MAX_NUM_EACH_SUBTREE) { + subtree.overflow = true; + const stashChildren = []; + subtree.children.forEach((child) => { + stashChildren.push(Object.assign({}, child)); + }); + stashSubtrees[subtree.id] = { + oriChildren: stashChildren, + curBeginIdx: 0, + curEndIdx: MAX_NUM_EACH_SUBTREE, + }; + } + }); + + // pruning the tree + G6.Util.traverseTree(data, (subtree) => { + if (subtree.overflow) subtree.children = subtree.children.slice(0, MAX_NUM_EACH_SUBTREE); + }); + + const width = container.scrollWidth; + const height = (container.scrollHeight || 500) - 50; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + item.getModel().collapsed = collapsed; + Object.keys(iconMap).forEach((parentId) => { + if (!iconMap[parentId] || iconMap[parentId].destroyed) return; + destroyIcons(parentId); + }); + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + style: { + fill: '#C6E5FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + type: 'cubic-horizontal', + style: { + stroke: '#A3B1BF', + }, + }, + layout: { + type: 'compactBox', + direction: 'LR', + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + getVGap: function getVGap() { + return 10; + }, + getHGap: function getHGap() { + return 100; + }, + }, + fitView: true, + }); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 50); + }; + + const iconMap = {}; + + // draw the icons on the root graphics group of the graph + const drawIcons = (parentId) => { + const parentNode = graph.findById(parentId); + const model = parentNode.getModel(); + delayDestroyIcons(parentId, 2000); + if (model.overflow) { + if (iconMap[parentId] && !iconMap[parentId].destroyed) return; + const graphicsGroup = graph.getGroup(); + const children = model.children; + const stashSubtree = stashSubtrees[parentId]; + const prePos = [children[0].x, children[0].y - (children[0].size || 26)]; + const nextPos = [children[2].x, children[2].y + (children[0].size || 26)]; + const preIcon = graphicsGroup.addShape('marker', { + attrs: { + symbol: 'triangle', + x: prePos[0], + y: prePos[1], + r: 6, + fill: '#333', + opacity: 0, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `pre-icon`, + subtreeID: model.id, + }); + const nextIcon = graphicsGroup.addShape('marker', { + attrs: { + symbol: 'triangle-down', + x: nextPos[0], + y: nextPos[1], + r: 6, + fill: '#333', + opacity: 0, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `next-icon`, + subtreeID: model.id, + }); + + if (stashSubtree.curBeginIdx > 0) { + preIcon.animate( + { + opacity: 0.5, + }, + { + duration: 150, + repeat: false, + }, + ); + } + if (stashSubtree.curEndIdx < stashSubtree.oriChildren.length) { + nextIcon.animate( + { + opacity: 0.5, + }, + { + duration: 150, + repeat: false, + }, + ); + } + + iconMap[parentId] = { + preIcon, + nextIcon, + destroyed: false, + }; + return true; + } + return false; + }; + + // update the icons + const updateIcons = (parentId) => { + if (!parentId || !iconMap[parentId]) return; + const preIcon = iconMap[parentId].preIcon; + const nextIcon = iconMap[parentId].nextIcon; + const stashSubtree = stashSubtrees[parentId]; + + const parentModel = graph.findById(parentId).getModel(); + const children = parentModel.children; + if (!children) return; + const prePos = [children[0].x, children[0].y - (children[0].size || 26)]; + const nextPos = [children[2].x, children[2].y + (children[0].size || 26)]; + preIcon.attr({ + opacity: stashSubtree.curBeginIdx <= 0 || parentModel.collapsed ? 0 : 0.5, + x: prePos[0], + y: prePos[1], + }); + nextIcon.attr({ + opacity: + stashSubtree.curEndIdx >= stashSubtree.oriChildren.length || parentModel.collapsed + ? 0 + : 0.5, + x: nextPos[0], + y: nextPos[1], + }); + }; + + // destroy the icons + const destroyIcons = (parentId) => { + if (!iconMap[parentId]) return; + const preIcon = iconMap[parentId].preIcon; + const nextIcon = iconMap[parentId].nextIcon; + if (preIcon && !preIcon.destroyed) { + preIcon.animate( + { + opacity: 0, + }, + { + duration: 150, + repeat: false, + }, + ); + setTimeout(() => { + preIcon.remove(); + preIcon.destroy(); + }, 150); + } + if (nextIcon && !nextIcon.destroyed) { + nextIcon.animate( + { + opacity: 0, + }, + { + duration: 150, + repeat: false, + }, + ); + setTimeout(() => { + nextIcon.remove(); + nextIcon.destroy(); + }, 150); + } + iconMap[parentId].destroyed = true; + }; + + // destroy the icons with delay + const delayDestroyIcons = (parentId, delay = 2000) => { + if (!iconMap[parentId] || window || typeof window === 'undefined') return; + if (iconMap[parentId].timeouter) { + window.clearTimeout(iconMap[parentId].timeouter); + } + iconMap[parentId].timeouter = window.setTimeout(() => { + destroyIcons(parentId); + }, delay); + }; + + // mouseenter the node to show the previous/next icons + graph.on('node:mouseenter', (e) => { + const parentNode = e.item.get('parent'); + if (!parentNode) return; + const parentId = parentNode.getID(); + drawIcons(parentId); + }); + + // mouseleave the node to destroy the icons + graph.on('node:mouseleave', (e) => { + const parentNode = e.item.get('parent'); + if (!parentNode) return; + const parentId = parentNode.getID(); + delayDestroyIcons(parentId, 2000); + }); + + // click the icon to changeData + graph.on('click', (e) => { + if (e.name === 'click') return; + const target = e.target; + const targetName = target.get('name'); + if (targetName !== 'pre-icon' && targetName !== 'next-icon') return; + const parentId = target.get('subtreeID'); + const stashSubtree = stashSubtrees[parentId]; + const oriChildren = stashSubtree.oriChildren; + if (targetName === 'pre-icon') { + if (stashSubtree.curBeginIdx <= 0) return; // touch the top + stashSubtree.curBeginIdx--; + stashSubtree.curEndIdx--; + } else { + if (stashSubtree.curEndIdx >= oriChildren.length) return; // touch the bottom + stashSubtree.curBeginIdx++; + stashSubtree.curEndIdx++; + } + const newChildren = oriChildren.slice(stashSubtree.curBeginIdx, stashSubtree.curEndIdx); + newChildren.forEach((childTree) => { + G6.Util.traverseTreeUp(childTree, (subChildTree) => { + if (subChildTree.children && subChildTree.children.length > MAX_NUM_EACH_SUBTREE) { + subChildTree.children = subChildTree.children.slice(0, MAX_NUM_EACH_SUBTREE); + } + }); + }); + graph.updateChildren(newChildren, parentId); + }); + + graph.on('afterlayout', (e) => { + Object.keys(iconMap).forEach((parentId) => { + if (!iconMap[parentId] || iconMap[parentId].destroyed) return; + updateIcons(parentId); + }); + }); + + graph.data(data); + graph.render(); + }); diff --git a/packages/site/examples/interaction/pagination/index.en.md b/packages/site/examples/interaction/pagination/index.en.md new file mode 100644 index 0000000000..8646eea242 --- /dev/null +++ b/packages/site/examples/interaction/pagination/index.en.md @@ -0,0 +1,13 @@ +--- +title: Nodes Paginaion +order: 15 +--- + +The demos here shows how to implement a pagination for nodes when there are too many nodes in one level to be displayed. + +## Usage + +Hover the nodes, and there will be two triangle icons show up, click the icons to switch the nodes. + +- 1st demo: node pagination for a tree graph; +- 2nd demo: node pagination for a general graph with dagre layout. diff --git a/packages/site/examples/interaction/pagination/index.zh.md b/packages/site/examples/interaction/pagination/index.zh.md new file mode 100644 index 0000000000..02301c849d --- /dev/null +++ b/packages/site/examples/interaction/pagination/index.zh.md @@ -0,0 +1,13 @@ +--- +title: 节点分页器 +order: 15 +--- + +当树图或 dagre 的某一层节点数量过多时,可以参考本例中的方法,实现单层节点的分页器。 + +## 使用指南 + +将鼠标移入节点较多的一层节点上,该层的上下或左右会出现三角形小按钮,点击该按钮可以切换节点。 + +- 第一个示例:树图的节点分页器; +- 第二个示例:一般图 dagre 布局的节点分页器。 diff --git a/packages/site/examples/interaction/partialResponse/API.en.md b/packages/site/examples/interaction/partialResponse/API.en.md new file mode 100644 index 0000000000..1e101f8f95 --- /dev/null +++ b/packages/site/examples/interaction/partialResponse/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/partialResponse/API.zh.md b/packages/site/examples/interaction/partialResponse/API.zh.md new file mode 100644 index 0000000000..d1e1c917ba --- /dev/null +++ b/packages/site/examples/interaction/partialResponse/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/partialResponse/demo/meta.json b/packages/site/examples/interaction/partialResponse/demo/meta.json new file mode 100644 index 0000000000..7289b9b7b5 --- /dev/null +++ b/packages/site/examples/interaction/partialResponse/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "partialNode.js", + "title": { + "zh": "响应节点中部分区域", + "en": "Region Response" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*rszjRrAopBkAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/partialResponse/demo/partialNode.js b/packages/site/examples/interaction/partialResponse/demo/partialNode.js new file mode 100644 index 0000000000..1bf310ed09 --- /dev/null +++ b/packages/site/examples/interaction/partialResponse/demo/partialNode.js @@ -0,0 +1,136 @@ +import G6 from '@antv/g6'; +/** + * 演示如何响应节点某一区域上的点击事件 + * by 长哲 + */ + +const GRAPH_CONTAINER = 'container'; + +// 注册自定义节点 +G6.registerNode( + 'customNode', + { + // 绘制节点 + drawShape: function drawShape(cfg, group) { + const shapeType = this.shapeType; + const style = Object.assign({}, this.getShapeStyle(cfg), { + x: 0, + y: 0, + r: 50, + }); + const shape = group.addShape(shapeType, { + attrs: style, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'key-shape', + }); + // 绘制节点里面的小圆。点击这个小圆会显示tooltip + group.addShape('circle', { + attrs: { + x: 0, + y: -30, + r: 10, + fill: '#096dd9', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + return shape; + }, + }, + 'circle', +); + +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 150, + label: 'node1', + size: 100, + type: 'customNode', + }, + { + id: 'node2', + x: 300, + y: 150, + label: 'node2', + size: 100, + type: 'customNode', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: GRAPH_CONTAINER, + width, + height, + modes: { + default: [ + { + type: 'drag-node', + delegate: false, + }, + ], + }, + defaultNode: { + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + labelCfg: { + style: { + fontSize: 12, + }, + }, + }, + defaultEdge: { + style: { + stroke: '#e2e2e2', + }, + }, + nodeStateStyles: { + selected: { + stroke: 'red', + }, + }, +}); + +graph.data(data); +graph.render(); + +// 节点上的点击事件 +graph.on('node:click', function (event) { + const { item } = event; + graph.setItemState(item, 'selected', true); +}); + +graph.on('circle-shape:click', (evt) => { + const { item } = evt; + graph.updateItem(item, { + label: '点击了局部', + labelCfg: { + style: { + fill: '#003a8c', + fontSize: 16, + }, + }, + }); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/partialResponse/index.en.md b/packages/site/examples/interaction/partialResponse/index.en.md new file mode 100644 index 0000000000..aedccc829e --- /dev/null +++ b/packages/site/examples/interaction/partialResponse/index.en.md @@ -0,0 +1,10 @@ +--- +title: Region Response +order: 7 +--- + +Region response on a node or edge. + +## Usage + +The following demo shows region(e.g. an small icon of a node) response on a node or edge. diff --git a/packages/site/examples/interaction/partialResponse/index.zh.md b/packages/site/examples/interaction/partialResponse/index.zh.md new file mode 100644 index 0000000000..67731845af --- /dev/null +++ b/packages/site/examples/interaction/partialResponse/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 响应区域事件 +order: 7 +--- + +区域响应。 + +## 使用指南 + +G6 的图元素中,可能只想响应部分元素的事件,如节点中的某个 icon、树图中的展开收起 icon 等。 diff --git a/packages/site/examples/interaction/position/API.en.md b/packages/site/examples/interaction/position/API.en.md new file mode 100644 index 0000000000..486d5e0a9f --- /dev/null +++ b/packages/site/examples/interaction/position/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/position/API.zh.md b/packages/site/examples/interaction/position/API.zh.md new file mode 100644 index 0000000000..e92fbefc35 --- /dev/null +++ b/packages/site/examples/interaction/position/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/position/demo/meta.json b/packages/site/examples/interaction/position/demo/meta.json new file mode 100644 index 0000000000..0567805ef7 --- /dev/null +++ b/packages/site/examples/interaction/position/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "moveAnimate.js", + "title": { + "zh": "点击节点带有动画地移动到画布中心", + "en": "Click to Animately Focus a Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*yPb-SKNZ5YYAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "move.js", + "title": { + "zh": "点击节点移动到画布中心", + "en": "Click to Focus a Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*yPb-SKNZ5YYAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/position/demo/move.js b/packages/site/examples/interaction/position/demo/move.js new file mode 100644 index 0000000000..12a4abffc0 --- /dev/null +++ b/packages/site/examples/interaction/position/demo/move.js @@ -0,0 +1,80 @@ +import G6 from '@antv/g6'; +/** + * Focus a node + * by Changzhe + */ +const data = { + nodes: [ + { + id: 'node1', + x: 150, + y: 50, + label: 'node1', + }, + { + id: 'node2', + x: 200, + y: 150, + label: 'node2', + }, + { + id: 'node3', + x: 100, + y: 150, + label: 'node3', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + { + source: 'node2', + target: 'node3', + }, + { + source: 'node3', + target: 'node1', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: false, + defaultNode: { + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + }, + }, +}); +graph.data(data); +graph.render(); + +function handleNodeClick(event) { + const item = event.item; + // animately move the graph to focus on the item. + graph.focusItem(item); +} + +// listen to the node click event +graph.on('node:click', handleNodeClick); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/position/demo/moveAnimate.js b/packages/site/examples/interaction/position/demo/moveAnimate.js new file mode 100644 index 0000000000..ccf276bbf4 --- /dev/null +++ b/packages/site/examples/interaction/position/demo/moveAnimate.js @@ -0,0 +1,90 @@ +import G6 from '@antv/g6'; +/** + * Focus a node with Animation + * by Changzhe + */ +const data = { + nodes: [ + { + id: 'node1', + x: 150, + y: 50, + label: 'node1', + }, + { + id: 'node2', + x: 200, + y: 150, + label: 'node2', + }, + { + id: 'node3', + x: 100, + y: 150, + label: 'node3', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + { + source: 'node2', + target: 'node3', + }, + { + source: 'node3', + target: 'node1', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: false, + defaultNode: { + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + }, + }, + // The global configuration for graph animation also takes effect on the focusItem + // animate: true, + // animateCfg: { + // easing: 'easeCubic', + // duration: 500 + // } +}); +graph.data(data); +graph.render(); + +function handleNodeClick(event) { + const item = event.item; + // animately move the graph to focus on the item. + // the second parameter controlls whether move with animation, the third parameter is the animate configuration + graph.focusItem(item, true, { + easing: 'easeCubic', + duration: 500, + }); +} + +// listen to the node click event +graph.on('node:click', handleNodeClick); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/position/index.en.md b/packages/site/examples/interaction/position/index.en.md new file mode 100644 index 0000000000..c7ceb22ed7 --- /dev/null +++ b/packages/site/examples/interaction/position/index.en.md @@ -0,0 +1,13 @@ +--- +title: Click to Focus a Node +order: 6 +--- + +Move the graph by interaction. + +## Usage + +Move the graph to focus on the clicked node, which means pan the canvas to center at the clicked node. + +- 1st demo: move the graph with animation; +- 2nd demo: move the graph without animation. diff --git a/packages/site/examples/interaction/position/index.zh.md b/packages/site/examples/interaction/position/index.zh.md new file mode 100644 index 0000000000..e12b42828d --- /dev/null +++ b/packages/site/examples/interaction/position/index.zh.md @@ -0,0 +1,13 @@ +--- +title: 聚焦一个节点 +order: 6 +--- + +通过交互改变节点的位置。 + +## 使用指南 + +点击某个节点,让其移动到画布中心。 + +- 第一个示例:带有动画地移动图; +- 第二个示例:不带动画移动图。 diff --git a/packages/site/examples/interaction/select/API.en.md b/packages/site/examples/interaction/select/API.en.md new file mode 100644 index 0000000000..05434a9239 --- /dev/null +++ b/packages/site/examples/interaction/select/API.en.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# Built-in Behaviors + +Refer to [Built-in Behavior](/en/docs/manual/middle/states/defaultBehavior). + +# Custom Behavior + +Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). diff --git a/packages/site/examples/interaction/select/API.zh.md b/packages/site/examples/interaction/select/API.zh.md new file mode 100644 index 0000000000..f672ff9f3b --- /dev/null +++ b/packages/site/examples/interaction/select/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# 内置复合交互 + +请参考教程 [内置 Behavior](/zh/docs/manual/middle/states/defaultBehavior)。 + +# 自定义复合交互 + +请参考教程 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)。 diff --git a/packages/site/examples/interaction/select/demo/brush.js b/packages/site/examples/interaction/select/demo/brush.js new file mode 100644 index 0000000000..4170dc4ab6 --- /dev/null +++ b/packages/site/examples/interaction/select/demo/brush.js @@ -0,0 +1,91 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node1', x: 150, y: 250 }, + { id: 'node2', x: 350, y: 250 }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +let shift = true; + +const container = document.getElementById('container'); + +const switchDiv = document.createElement('div'); +switchDiv.innerHTML = `Press 'shift' and drag begin on empty space to brush select. Click 「HERE」 to switch trigger to \'drag\', and disable drag-canvas +
按住 'shift' 并从画布空白处开始拖拽即可开始框选。点击「这里」将 trigger 切换为 'drag',同时关闭画布拖拽`; +container.appendChild(switchDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitCenter: true, + modes: { + default: ['brush-select', 'drag-node', 'drag-canvas'], + altSelect: [ + { + type: 'brush-select', + trigger: 'drag', + }, + 'drag-node', + ], + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable. you can extend or custom it by the following lines */ + /* 不同状态下节点和边的样式,G6 提供以下状态名的默认样式:active, inactive, selected, highlight, disable。可以通过如下方式修改或者扩展全局状态样式*/ + // nodeStateStyles: { + // selected: { + // stroke: '#f00', + // lineWidth: 3 + // } + // }, + // edgeStateStyles: { + // selected: { + // lineWidth: 3, + // stroke: '#f00' + // } + // } +}); + +graph.on('node:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); + +graph.on('node:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); + +graph.on('nodeselectchange', (e) => { + console.log(e.selectedItems, e.select); +}); + +switchDiv.addEventListener('click', (e) => { + shift = !shift; + if (shift) { + graph.setMode('default'); + switchDiv.innerHTML = `Press \'shift\' and drag begin on empty space to brush select. Click Here to switch trigger to \'drag\', and disable drag-canvas +
按住 'shift' 并从画布空白处开始拖拽即可开始框选。点击「这里」将 trigger 切换为 'drag',同时关闭画布拖拽`; + } else { + graph.setMode('altSelect'); + switchDiv.innerHTML = `Press \'alt\' and drag begin on empty space to brush select. Click Here to switch trigger to key \'shift\', and enable drag-canvas +
按住 'alt' 并从画布空白处开始拖拽即可开始框选。点击「这里」将 trigger 切换为 'shift',同时开启画布拖拽`; + } +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; diff --git a/packages/site/examples/interaction/select/demo/click.js b/packages/site/examples/interaction/select/demo/click.js new file mode 100644 index 0000000000..21c19e544a --- /dev/null +++ b/packages/site/examples/interaction/select/demo/click.js @@ -0,0 +1,91 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node1', x: 150, y: 250 }, + { id: 'node2', x: 350, y: 250 }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +let shift = true; + +const graphDiv = document.getElementById('container'); + +const switchDiv = document.createElement('div'); +switchDiv.innerHTML = `Press \'shift\' to select multiple nodes. Click Here to switch trigger to key \'alt\' +
按住 'shift' 可多选节点。点击「这里」将 trigger 切换为 'alt'`; +graphDiv.appendChild(switchDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitCenter: true, + modes: { + default: ['click-select', 'drag-node'], + altSelect: [ + { + type: 'click-select', + trigger: 'alt', + }, + 'drag-node', + ], + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable. you can extend or custom it by the following lines */ + /* 不同状态下节点和边的样式,G6 提供以下状态名的默认样式:active, inactive, selected, highlight, disable。可以通过如下方式修改或者扩展全局状态样式*/ + // nodeStateStyles: { + // selected: { + // stroke: '#f00', + // lineWidth: 3 + // } + // }, + // edgeStateStyles: { + // selected: { + // lineWidth: 3, + // stroke: '#f00' + // } + // } +}); + +graph.on('node:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); + +graph.on('node:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); + +graph.on('nodeselectchange', (e) => { + console.log(e.selectedItems, e.select); +}); + +switchDiv.addEventListener('click', (e) => { + shift = !shift; + if (shift) { + graph.setMode('default'); + switchDiv.innerHTML = `Press \'shift\' to select multiple nodes. Click Here to switch trigger to key \'alt\' +
按住 'shift' 可多选节点。点击「这里」将 trigger 切换为 'alt'`; + } else { + graph.setMode('altSelect'); + switchDiv.innerHTML = `Press \'alt\' to select multiple nodes. Click Here to switch trigger to key \'shift\' +
按住 'alt' 可多选节点。点击「这里」将 trigger 切换为 'shift'`; + } +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; diff --git a/packages/site/examples/interaction/select/demo/lasso.js b/packages/site/examples/interaction/select/demo/lasso.js new file mode 100644 index 0000000000..a67ed99a15 --- /dev/null +++ b/packages/site/examples/interaction/select/demo/lasso.js @@ -0,0 +1,89 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node1', x: 150, y: 250 }, + { id: 'node2', x: 350, y: 250 }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +let shift = true; + +const switchDiv = document.createElement('div'); +switchDiv.innerHTML = `Press down the 'shift' on keyboard and drag to begin select. Click「HERE」to switch trigger to \'drag\', and custom lasso style, and disable drag-canvas +
按住 'shift' 可开始拉索选择。点击「这里」切换 trigger 为 'drag',同时修改拉索样式和关闭画布拖拽`; +const container = document.getElementById('container'); +container.appendChild(switchDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitCenter: true, + modes: { + default: ['lasso-select', 'drag-node', 'drag-canvas'], + dragLasso: [ + { + type: 'lasso-select', + delegateStyle: { + fill: '#f00', + fillOpacity: 0.05, + stroke: '#f00', + lineWidth: 1, + }, + onSelect: (nodes, edges) => { + console.log('onSelect', nodes, edges); + }, + trigger: 'drag', + }, + 'drag-node', + ], + }, + nodeStateStyles: { + selected: { + stroke: '#f00', + lineWidth: 3, + }, + }, + edgeStateStyles: { + selected: { + lineWidth: 3, + stroke: '#f00', + }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('nodeselectchange', (e) => { + console.log(e.selectedItems, e.select); +}); + +switchDiv.addEventListener('click', (e) => { + shift = !shift; + if (shift) { + graph.setMode('default'); + switchDiv.innerHTML = `Press down the 'shift' on keyboard and drag to begin select. Click「HERE」to switch trigger to \'drag\', and custom lasso style, and disable drag-canvas +
按住 'shift' 可开始拉索选择。点击「这里」切换 trigger 为 'drag',同时修改拉索样式和关闭画布拖拽`; + } else { + graph.setMode('dragLasso'); + switchDiv.innerHTML = `Drag on the canvas to begin lasso select. Click「HERE」to switch trigger to \'shift\', and enable drag-canvas +
拖拽画布即可进行拉索选择。点击「这里」切换 trigger 为 'drag',同时开启画布拖拽`; + } +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; diff --git a/packages/site/examples/interaction/select/demo/meta.json b/packages/site/examples/interaction/select/demo/meta.json new file mode 100644 index 0000000000..04fc6ca97c --- /dev/null +++ b/packages/site/examples/interaction/select/demo/meta.json @@ -0,0 +1,23 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "click.js", + "title": "点击单选或多选", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*F0RGQ67xbGYAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "brush.js", + "title": "框选", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ZDEiRIQRByEAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "lasso.js", + "title": "拉索选择", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*8WwrRplTPG8AAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/select/index.en.md b/packages/site/examples/interaction/select/index.en.md new file mode 100644 index 0000000000..5aa1fb434b --- /dev/null +++ b/packages/site/examples/interaction/select/index.en.md @@ -0,0 +1,14 @@ +--- +title: Select +order: 14 +--- + +G6 provides some built-in behaviors for selection, including selecting a single item, selecting multiple items, selecting items by brushing, and selecting items by a lasso. Where behavior `'lasso-select'` for selecting by a lasso is a new feature of V3.6.2, which allows the users select items by drawing a irregular polygon as lasso. + +## Usage + +- Selecting a single item or selecting multiple items: a built-in behavior `'click-select'` with default configurations allows the end-user to select a single item by clicking. You can configure `multiple: true` for it to allow multiple selection by combining mouse clicking and keydown on keyboard. By default, the combined key is `shift`, and you can switch the `trigger` to one of `'shift'`, `'ctrl'`, `'alt'`, `'control'` as you want. For more detail, please refer to click-select](/en/docs/manual/middle/states/defaultBehavior#click-select); + +- Selecting multiple items by brush: a built-in behavior `'brush-select'` with default configurations allows the end-user to select multiple items by combining `shift` keydown and draging start from any empty space on the canvas. The key can be switched to one of `'drag'`, `'shift'`, `'ctrl'`, `'alt'`, `'control'` by configuring `trigger`. Notice that, when you are using `'drag'` for `trigger`, the brush selection will start when the end-user drag the canvas without combining key, which might be conflict with `'drag-canvas'` if you configured. So we suggest to use one key for `trigger` to solve the conflict, For more detail, please refer to [brush-select](/en/docs/manual/middle/states/defaultBehavior#brush-select); + +- Selecting multiple items by a lasso: a built-in behavior `'lasso-select` allows end-user to draw a lasso to select items inside it. Similar to `'brush-select'`, there are multiple options for `trigger` for keyboard combination. For more detail, please refer to [lasso-select](/en/docs/manual/middle/states/defaultBehavior#lasso-select). diff --git a/packages/site/examples/interaction/select/index.zh.md b/packages/site/examples/interaction/select/index.zh.md new file mode 100644 index 0000000000..0284bc0c89 --- /dev/null +++ b/packages/site/examples/interaction/select/index.zh.md @@ -0,0 +1,12 @@ +--- +title: 选中 +order: 14 +--- + +G6 提供内置的单选、多选、框选、拉索选择四种选中元素的方式。其中,内置交互 `'lasso-select'`自 V3.6.2 起支持,允许终端用户在画布上绘制一个不规则多边形作为拉索选中几点/边/ combo。 + +## 如何使用 + +- 单选与多选:内置交互 `'click-select'` 的默认配置允许终端用户单击选中节点;配置 `multiple: true` 即可允许组合键盘按键进行多选,默认的键盘按键为 `Shift`,可配置 `trigger` 为 `'shift'`、`'ctrl'`、`'alt'`、`'control'` 中的一个来更换键盘按。文档参见 [click-select](/zh/docs/manual/middle/states/defaultBehavior#click-select); +- 框选:内置交互 `'brush-select'` 为框选交互,在默认配置下,终端用户可以按住 `shift` 并从画布空白处开始拖拽进行框选。`shift` 也可以通过配置 `trigger` 替换为 `'drag'`、`'shift'`、`'ctrl'`、`'alt'`、`'control'`。需要注意的时,当使用 `'drag'`,即无键盘组合按键直接进行拖拽框选,可能会与 `'drag-canavs'` 的交互产生冲突。因此,我们推荐使用一个键盘组合按键进行区分。文档参见 [brush-select](/zh/docs/manual/middle/states/defaultBehavior#brush-select); +- 拉索选择:与 `'brush-select'` 类似,内置交互 `'lasso-select'` 允许配置不同的 `trigger` 来区分拖拽画布。更多内容请参考 [lasso-select](/zh/docs/manual/middle/states/defaultBehavior#lasso-select)。 diff --git a/packages/site/examples/interaction/setMode/API.en.md b/packages/site/examples/interaction/setMode/API.en.md new file mode 100644 index 0000000000..486d5e0a9f --- /dev/null +++ b/packages/site/examples/interaction/setMode/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/setMode/API.zh.md b/packages/site/examples/interaction/setMode/API.zh.md new file mode 100644 index 0000000000..fb5cb948df --- /dev/null +++ b/packages/site/examples/interaction/setMode/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/setMode/demo/meta.json b/packages/site/examples/interaction/setMode/demo/meta.json new file mode 100644 index 0000000000..922e306270 --- /dev/null +++ b/packages/site/examples/interaction/setMode/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "setMode.js", + "title": { + "zh": "设置交互模式", + "en": "Set Mode" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*y1qkTKqhQXkAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/setMode/demo/setMode.js b/packages/site/examples/interaction/setMode/demo/setMode.js new file mode 100644 index 0000000000..bab1255df7 --- /dev/null +++ b/packages/site/examples/interaction/setMode/demo/setMode.js @@ -0,0 +1,173 @@ +import G6 from '@antv/g6'; + +/** + * 该案例演示切换交互模式,在不同模式下实现拖动节点、增加节点、增加边的交互行为。 + */ +let addedCount = 0; +// Register a custom behavior: add a node when user click the blank part of canvas +G6.registerBehavior('click-add-node', { + // Set the events and the corresponding responsing function for this behavior + getEvents() { + // The event is canvas:click, the responsing function is onClick + return { + 'canvas:click': 'onClick', + }; + }, + // Click event + onClick(ev) { + const self = this; + const graph = self.graph; + // Add a new node + graph.addItem('node', { + x: ev.canvasX, + y: ev.canvasY, + id: `node-${addedCount}`, // Generate the unique id + }); + addedCount++; + }, +}); +// Register a custom behavior: click two end nodes to add an edge +G6.registerBehavior('click-add-edge', { + // Set the events and the corresponding responsing function for this behavior + getEvents() { + return { + 'node:click': 'onClick', // The event is canvas:click, the responsing function is onClick + mousemove: 'onMousemove', // The event is mousemove, the responsing function is onMousemove + 'edge:click': 'onEdgeClick', // The event is edge:click, the responsing function is onEdgeClick + }; + }, + // The responsing function for node:click defined in getEvents + onClick(ev) { + const self = this; + const node = ev.item; + const graph = self.graph; + // The position where the mouse clicks + const point = { x: ev.x, y: ev.y }; + const model = node.getModel(); + if (self.addingEdge && self.edge) { + graph.updateItem(self.edge, { + target: model.id, + }); + + self.edge = null; + self.addingEdge = false; + } else { + // Add anew edge, the end node is the current node user clicks + self.edge = graph.addItem('edge', { + source: model.id, + target: model.id, + }); + self.addingEdge = true; + } + }, + // The responsing function for mousemove defined in getEvents + onMousemove(ev) { + const self = this; + // The current position the mouse clicks + const point = { x: ev.x, y: ev.y }; + if (self.addingEdge && self.edge) { + // Update the end node to the current node the mouse clicks + self.graph.updateItem(self.edge, { + target: point, + }); + } + }, + // The responsing function for edge:click defined in getEvents + onEdgeClick(ev) { + const self = this; + const currentEdge = ev.item; + if (self.addingEdge && self.edge === currentEdge) { + self.graph.removeItem(self.edge); + self.edge = null; + self.addingEdge = false; + } + }, +}); +// Initial data +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 200, + }, + { + id: 'node2', + x: 300, + y: 200, + }, + { + id: 'node3', + x: 300, + y: 300, + }, + ], + edges: [ + { + id: 'edge1', + target: 'node2', + source: 'node1', + }, + ], +}; + +const container = document.getElementById('container'); + +// Add a selector to DOM +const selector = document.createElement('select'); +selector.id = 'selector'; +const selection1 = document.createElement('option'); +selection1.value = 'default'; +selection1.innerHTML = 'Default Mode'; +const selection2 = document.createElement('option'); +selection2.value = 'addNode'; +selection2.innerHTML = 'Add Node (By clicking canvas)'; +const selection3 = document.createElement('option'); +selection3.value = 'addEdge'; +selection3.innerHTML = 'Add Edge (By clicking two end nodes)'; +selector.appendChild(selection1); +selector.appendChild(selection2); +selector.appendChild(selection3); +container.appendChild(selector); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // The sets of behavior modes + modes: { + // Defualt mode + default: ['drag-node', 'click-select'], + // Adding node mode + addNode: ['click-add-node', 'click-select'], + // Adding edge mode + addEdge: ['click-add-edge', 'click-select'], + }, + // The node styles in different states + nodeStateStyles: { + // The node styles in selected state + selected: { + stroke: '#666', + lineWidth: 2, + fill: 'steelblue', + }, + }, +}); +graph.data(data); +graph.render(); + +// Listen to the selector, change the mode when the selector is changed +selector.addEventListener('change', (e) => { + const value = e.target.value; + // change the behavior mode + graph.setMode(value); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; diff --git a/packages/site/examples/interaction/setMode/index.en.md b/packages/site/examples/interaction/setMode/index.en.md new file mode 100644 index 0000000000..dd8e01e768 --- /dev/null +++ b/packages/site/examples/interaction/setMode/index.en.md @@ -0,0 +1,14 @@ +--- +title: Change Mode to Add items +order: 8 +--- + +You can change to different modes with the selector on the left top. Dragging node, adding node, adding edge are enabled in different modes. This mechanism help developers to avoid the conflict of same events in different behaviors. + +## Usage + +Change to different modes with the selector on the left top. + +- When the 'Default' is selected, the mode is changed to default: The node will move withe the mouse when user drags it; The node will be selected when user clicks it; +- When 'Add Node' is selected, the mode is changed to addNode: A new node will be added when user clicks the blank part of the canvas; The node will be selected when user clicks it; +- When 'Add Edge' is selected, the mode is changed to addEdge: An new edge will be added when user clicks two different end nodes. diff --git a/packages/site/examples/interaction/setMode/index.zh.md b/packages/site/examples/interaction/setMode/index.zh.md new file mode 100644 index 0000000000..58aefaee24 --- /dev/null +++ b/packages/site/examples/interaction/setMode/index.zh.md @@ -0,0 +1,14 @@ +--- +title: 切换模式增加点和边 +order: 8 +--- + +使用左上角的下拉框切换交互模式,在不同模式下拖动节点、增加节点、增加边,从而避免交互事件的冲突。 + +## 使用指南 + +使用左上角的下拉框切换交互模式: + +- 选择 “Default” 按钮时,切换到 default 交互模式:拖拽节点时节点跟随鼠标移动;点击节点时选中该节点; +- 选择 “Add Node” 按钮时,切换到  addNode 交互模式:点击空白区域在点击处增加一个节点;点击节点时选中该节点; +- 选择 “Add Edge” 按钮时,切换到 addEdge 交互模式:依次点击两个节点将会在这两个节点之间添加一条边。 diff --git a/packages/site/examples/interaction/treeBehavior/API.en.md b/packages/site/examples/interaction/treeBehavior/API.en.md new file mode 100644 index 0000000000..09c26fde1a --- /dev/null +++ b/packages/site/examples/interaction/treeBehavior/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/treeBehavior/API.zh.md b/packages/site/examples/interaction/treeBehavior/API.zh.md new file mode 100644 index 0000000000..da0637f305 --- /dev/null +++ b/packages/site/examples/interaction/treeBehavior/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/interaction/treeBehavior/demo/changeData.js b/packages/site/examples/interaction/treeBehavior/demo/changeData.js new file mode 100644 index 0000000000..61a99a0c7c --- /dev/null +++ b/packages/site/examples/interaction/treeBehavior/demo/changeData.js @@ -0,0 +1,185 @@ +import G6 from '@antv/g6'; + +/** + * 该案例演示,当点击叶子节点时,如何动态向树图中添加多条数据。 + * 主要演示changeData的用法。 + */ + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: ['collapse-expand', 'drag-canvas'], + }, + fitView: true, + layout: { + type: 'compactBox', + direction: 'LR', + defalutPosition: [], + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + getVGap: function getVGap() { + return 50; + }, + getHGap: function getHGap() { + return 100; + }, + }, +}); +graph.node(function (node) { + return { + size: 16, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + label: node.id, + labelCfg: { + position: node.children && node.children.length > 0 ? 'left' : 'right', + }, + }; +}); +let i = 0; +graph.edge(function () { + i++; + return { + type: 'cubic-horizontal', + color: '#A3B1BF', + label: i, + }; +}); + +const data = { + isRoot: true, + id: 'Root', + style: { + fill: 'red', + }, + children: [ + { + id: 'SubTreeNode1', + raw: {}, + children: [ + { + id: 'SubTreeNode1.1', + }, + { + id: 'SubTreeNode1.2', + children: [ + { + id: 'SubTreeNode1.2.1', + }, + { + id: 'SubTreeNode1.2.2', + }, + { + id: 'SubTreeNode1.2.3', + }, + ], + }, + ], + }, + { + id: 'SubTreeNode2', + children: [ + { + id: 'SubTreeNode2.1', + }, + ], + }, + { + id: 'SubTreeNode3', + children: [ + { + id: 'SubTreeNode3.1', + }, + { + id: 'SubTreeNode3.2', + }, + { + id: 'SubTreeNode3.3', + }, + ], + }, + { + id: 'SubTreeNode4', + }, + { + id: 'SubTreeNode5', + }, + { + id: 'SubTreeNode6', + }, + ], +}; +graph.data(data); +graph.render(); + +let count = 0; + +graph.on('node:click', function (evt) { + const item = evt.item; + + const nodeId = item.get('id'); + const model = item.getModel(); + const children = model.children; + if (!children || children.length === 0) { + const childData = [ + { + id: 'child-data-' + count, + type: 'rect', + children: [ + { + id: 'x-' + count, + }, + { + id: 'y-' + count, + }, + ], + }, + { + id: 'child-data1-' + count, + children: [ + { + id: 'x1-' + count, + }, + { + id: 'y1-' + count, + }, + ], + }, + ]; + + const parentData = graph.findDataById(nodeId); + if (!parentData.children) { + parentData.children = []; + } + // 如果childData是一个数组,则直接赋值给parentData.children + // 如果是一个对象,则使用parentData.children.push(obj) + parentData.children = childData; + graph.changeData(); + count++; + } +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/treeBehavior/demo/collapseSlibing.js b/packages/site/examples/interaction/treeBehavior/demo/collapseSlibing.js new file mode 100644 index 0000000000..1a0e9b0215 --- /dev/null +++ b/packages/site/examples/interaction/treeBehavior/demo/collapseSlibing.js @@ -0,0 +1,226 @@ +import G6 from '@antv/g6'; + +/** + * 该案例演示,点击一个节点,同层的同类节点合并为一个聚合节点。再次点击则展开。 + * 来示例启发自 GitHub 用户 @c1105590204 + * by 十吾 + * + * Click a node to collapse the siblings with same type, and click again to expand + * This demo is inspired by the GitHub User @c1105590204 + * by Yanyan-Wang + */ + +const container = document.getElementById('container'); +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Click a node to collapse the siblings with same cluster, and click again to expand'; +container.appendChild(descriptionDiv); + +// custom the collapse-sibling behavior +G6.registerBehavior('collapse-slibing', { + getEvents() { + return { + 'node:click': 'onClick', + }; + }, + onClick(evt) { + const { item } = evt; + const model = item.getModel(); + const cluster = model.cluster; + const parentData = item.get('parent').getModel(); + + if (model.collapsedSiblings) { + graph.removeChild(model.id); + setTimeout(() => { + const children = parentData.children; + for (let i = model.collapsedSiblings.length - 1; i >= 0; i --) { + const add = model.collapsedSiblings[i]; + children.splice(add.idx, 0, add); + } + delete model.collapsedSiblings; + this.graph.updateChildren(children, parentData.id) + }, 550); + return; + } + const siblingData = parentData.children; + if (siblingData.length <= 1) return; + let count = 0; + const aggregateNode = { + id: `aggregate-node-${model.id}`, + children: [], + cluster, + collapsedSiblings: [] + } + let modelIdx = siblingData.length - 1; + for (let i = siblingData.length - 1; i >= 0; i --) { + const sibling = siblingData[i] + if (sibling.id === model.id) { + + const remove = siblingData[i] + remove.idx = i; + aggregateNode.collapsedSiblings.push(remove); + modelIdx = Math.min(i, modelIdx); + + aggregateNode.children = (siblingData[i].children || []).concat(aggregateNode.children); + } + else if (sibling.cluster === cluster && sibling.id !== model.id) { + count ++; + aggregateNode.children = (siblingData[i].children || []).concat(aggregateNode.children); + const remove = siblingData.splice(i, 1)[0] + remove.idx = i; + aggregateNode.collapsedSiblings.push(remove); + modelIdx = Math.min(i, modelIdx); + } + } + if (!count) return; + aggregateNode.label = count + 1; + aggregateNode.size = 16 + (count + 1) * 4; + + graph.removeChild(model.id); + siblingData.splice(modelIdx, 0, aggregateNode); + + setTimeout(() => { + this.graph.updateChildren(siblingData, parentData.id) + }, 550); + + return; + }, +}); + +// givin subject colors, getColorSetsBySubjectColors returns colorsets +const colors = [ '#5F95FF', '#61DDAA', '#65789B' ]; +const colorSets = G6.Util.getColorSetsBySubjectColors(colors, '#fff', 'default', '#777'); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 50; +const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: ['collapse-slibing', 'drag-canvas'], + }, + fitView: true, + layout: { + type: 'compactBox', + direction: 'LR', + defalutPosition: [], + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + getVGap: function getVGap(d) { + return 10; + }, + getHGap: function getHGap(d) { + return 50; + }, + }, + defaultEdge: { + type: 'cubic-horizontal', + color: '#A3B1BF', + } +}); +graph.node(function (node) { + const colorSet = colorSets[(+node.cluster.replace('c', ''))] + return { + size: node.size || 16, + style: { + fill: colorSet?.mainFill || '#DEE9FF', + stroke: colorSet?.mainStroke || '#5B8FF9', + }, + }; +}); + +// data with cluster info +const data = { + isRoot: true, + id: 'Root', + cluster: 'c0', + children: [ + { + id: 'SubTreeNode1', + cluster: 'c0', + raw: {}, + children: [ + { + id: 'SubTreeNode1.1', + cluster: 'c1', + }, + { + id: 'SubTreeNode1.2', + cluster: 'c1', + children: [ + { + id: 'SubTreeNode1.2.1', + cluster: 'c0', + }, + { + id: 'SubTreeNode1.2.2', + cluster: 'c0', + }, + { + id: 'SubTreeNode1.2.3', + cluster: 'c1', + }, + ], + }, + ], + }, + { + id: 'SubTreeNode2', + cluster: 'c0', + children: [ + { + id: 'SubTreeNode2.1', + cluster: 'c2', + }, + ], + }, + { + id: 'SubTreeNode3', + cluster: 'c0', + children: [ + { + id: 'SubTreeNode3.1', + cluster: 'c2', + }, + { + id: 'SubTreeNode3.2', + cluster: 'c2', + }, + { + id: 'SubTreeNode3.3', + cluster: 'c2', + }, + ], + }, + { + id: 'SubTreeNode4', + cluster: 'c1', + }, + { + id: 'SubTreeNode5', + cluster: 'c1', + }, + { + id: 'SubTreeNode6', + cluster: 'c1', + }, + ], +}; +graph.data(data); +graph.render(); + + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/treeBehavior/demo/dragSubtree.js b/packages/site/examples/interaction/treeBehavior/demo/dragSubtree.js new file mode 100644 index 0000000000..d45fa6417c --- /dev/null +++ b/packages/site/examples/interaction/treeBehavior/demo/dragSubtree.js @@ -0,0 +1,149 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Move a subtree to a new parent by dragging the root node of the subtree.'; +container.appendChild(descriptionDiv); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = (container.scrollHeight || 500) - 20; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: [ + 'drag-canvas', + 'zoom-canvas', + { + type: 'drag-node', + enableDelegate: true, + }, + ], + }, + defaultNode: { + size: [26, 26], + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + style: { + fill: '#C6E5FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + type: 'cubic-horizontal', + style: { + stroke: '#A3B1BF', + }, + }, + nodeStateStyles: { + closest: { + fill: '#f00', + }, + }, + layout: { + type: 'compactBox', + direction: 'LR', + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + getVGap: function getVGap() { + return 10; + }, + getHGap: function getHGap() { + return 100; + }, + }, + }); + + graph.node(function (node) { + return { + label: node.id, + labelCfg: { + offset: 10, + position: node.children && node.children.length > 0 ? 'left' : 'right', + }, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + let minDisNode; + graph.on('node:dragstart', (e) => { + minDisNode = undefined; + }); + graph.on('node:drag', (e) => { + minDisNode = undefined; + const item = e.item; + const model = item.getModel(); + const nodes = graph.getNodes(); + let minDis = Infinity; + nodes.forEach((inode) => { + graph.setItemState(inode, 'closest', false); + const node = inode.getModel(); + if (node.id === model.id) return; + const dis = (node.x - e.x) * (node.x - e.x) + (node.y - e.y) * (node.y - e.y); + if (dis < minDis) { + minDis = dis; + minDisNode = inode; + } + }); + console.log('minDis', minDis, minDisNode); + if (minDis < 2000) graph.setItemState(minDisNode, 'closest', true); + else minDisNode = undefined; + }); + + graph.on('node:dragend', (e) => { + if (!minDisNode) { + descriptionDiv.innerHTML = 'Failed. No node close to the dragged node.'; + return; + } + const item = e.item; + const id = item.getID(); + const data = graph.findDataById(id); + // if the minDisNode is a descent of the dragged node, return + let isDescent = false; + const minDisNodeId = minDisNode.getID(); + console.log('dragend', minDisNodeId, isDescent, data, id); + + G6.Util.traverseTree(data, (d) => { + if (d.id === minDisNodeId) isDescent = true; + }); + if (isDescent) { + descriptionDiv.innerHTML = 'Failed. The target node is a descendant of the dragged node.'; + return; + } + graph.removeChild(id); + + setTimeout(() => { + const newParentData = graph.findDataById(minDisNodeId); + let newChildren = newParentData.children; + if (newChildren) newChildren.push(data); + else newChildren = [data]; + graph.updateChildren(newChildren, minDisNodeId); + descriptionDiv.innerHTML = 'Success.'; + }, 600); + }); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; + }); diff --git a/packages/site/examples/interaction/treeBehavior/demo/loadTreeData.js b/packages/site/examples/interaction/treeBehavior/demo/loadTreeData.js new file mode 100644 index 0000000000..a00f27a1d1 --- /dev/null +++ b/packages/site/examples/interaction/treeBehavior/demo/loadTreeData.js @@ -0,0 +1,162 @@ +import G6 from '@antv/g6'; + +/** + * 该案例演示,当点击叶子节点时,如何向树图中动态添加数据。 + * 主要演示 addChild 和 layout 的用法。 + */ + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: ['collapse-expand', 'drag-canvas'], + }, + fitView: true, + layout: { + type: 'compactBox', + direction: 'LR', + defalutPosition: [], + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + getVGap: function getVGap() { + return 50; + }, + getHGap: function getHGap() { + return 100; + }, + }, +}); +graph.node(function (node) { + return { + size: 16, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + label: node.id, + labelCfg: { + position: node.children && node.children.length > 0 ? 'left' : 'right', + }, + }; +}); + +graph.edge(function () { + return { + type: 'cubic-horizontal', + color: '#A3B1BF', + }; +}); + +const data = { + isRoot: true, + id: 'Root', + style: { + fill: 'red', + }, + children: [ + { + id: 'SubTreeNode1', + raw: {}, + children: [ + { + id: 'SubTreeNode1.1', + }, + { + id: 'SubTreeNode1.2', + children: [ + { + id: 'SubTreeNode1.2.1', + }, + { + id: 'SubTreeNode1.2.2', + }, + { + id: 'SubTreeNode1.2.3', + }, + ], + }, + ], + }, + { + id: 'SubTreeNode2', + children: [ + { + id: 'SubTreeNode2.1', + }, + ], + }, + { + id: 'SubTreeNode3', + children: [ + { + id: 'SubTreeNode3.1', + }, + { + id: 'SubTreeNode3.2', + }, + { + id: 'SubTreeNode3.3', + }, + ], + }, + { + id: 'SubTreeNode4', + }, + { + id: 'SubTreeNode5', + }, + { + id: 'SubTreeNode6', + }, + ], +}; +graph.data(data); +graph.render(); + +let count = 0; + +graph.on('node:click', function (evt) { + const item = evt.item; + + const nodeId = item.get('id'); + const model = item.getModel(); + const children = model.children; + if (!children || children.length === 0) { + const childData = { + id: 'child-data-' + count, + type: 'rect', + children: [ + { + id: 'x-' + count, + }, + { + id: 'y-' + count, + }, + ], + }; + graph.addChild(childData, nodeId); + count++; + } +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/interaction/treeBehavior/demo/meta.json b/packages/site/examples/interaction/treeBehavior/demo/meta.json new file mode 100644 index 0000000000..7d44ca1ae4 --- /dev/null +++ b/packages/site/examples/interaction/treeBehavior/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "dragSubtree.js", + "title": { + "zh": "拖拽子树改变结构", + "en": "Move Subtree to New Parent" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*EU_yRa8hMJUAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "collapseSlibing.js", + "title": { + "zh": "合并同类兄弟节点", + "en": "Collapse the Sibling Nodes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ugs9Qad-bQMAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "changeData.js", + "title": { + "zh": "使用 changeData", + "en": "Using changeData" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wc1-QaqOPsgAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "loadTreeData.js", + "title": { + "zh": "使用 addChild", + "en": "Using addChild" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wc1-QaqOPsgAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/treeBehavior/index.en.md b/packages/site/examples/interaction/treeBehavior/index.en.md new file mode 100644 index 0000000000..36df03be99 --- /dev/null +++ b/packages/site/examples/interaction/treeBehavior/index.en.md @@ -0,0 +1,20 @@ +--- +title: Custom Tree Graph Behavior +order: 1 +--- + +## Move Subtree + +Move a subtree to a new parent by dragging the root node of the subtree. Drag the root node of the subtree to get closed to a new parent which is not a descendant node of the dragged node, drop the node to finish. + + +## Onload Data to TreeGraph + +Click the nodes on the tree graph to onload multiple data. The following demos show two ways to load data: + +- `graph.changeData`; +- `graph.addChild`; + +## Collapse the Sibling Nodes + +Click a node to collapse the sibling nodes with same data type. \ No newline at end of file diff --git a/packages/site/examples/interaction/treeBehavior/index.zh.md b/packages/site/examples/interaction/treeBehavior/index.zh.md new file mode 100644 index 0000000000..ed2f799663 --- /dev/null +++ b/packages/site/examples/interaction/treeBehavior/index.zh.md @@ -0,0 +1,21 @@ +--- +title: 自定义树图交互 +order: 1 +--- + + +## 拖拽子树改变结构。 + +通过拖拽子树的根节点,靠近新的父节点后放下,可将该子树从原来位置删除,追加到新父节点下,从而改变树的结构。若新父节点是被拖拽子树中的一个节点,则拖拽改变结构将不会生效。 + + +## 动态加载树图数据 + +共演示了两种动态添加数据的方式: + +- 使用 `graph.changeData`; +- 使用 `graph.addChild`。 + +## 合并同类兄弟节点 + +展示合并同类兄弟节点的交互。 \ No newline at end of file diff --git a/packages/site/examples/interaction/zoomCanvasFixItem/API.en.md b/packages/site/examples/interaction/zoomCanvasFixItem/API.en.md new file mode 100644 index 0000000000..05434a9239 --- /dev/null +++ b/packages/site/examples/interaction/zoomCanvasFixItem/API.en.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# Built-in Behaviors + +Refer to [Built-in Behavior](/en/docs/manual/middle/states/defaultBehavior). + +# Custom Behavior + +Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). diff --git a/packages/site/examples/interaction/zoomCanvasFixItem/API.zh.md b/packages/site/examples/interaction/zoomCanvasFixItem/API.zh.md new file mode 100644 index 0000000000..f672ff9f3b --- /dev/null +++ b/packages/site/examples/interaction/zoomCanvasFixItem/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# 内置复合交互 + +请参考教程 [内置 Behavior](/zh/docs/manual/middle/states/defaultBehavior)。 + +# 自定义复合交互 + +请参考教程 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)。 diff --git a/packages/site/examples/interaction/zoomCanvasFixItem/demo/fixItem.js b/packages/site/examples/interaction/zoomCanvasFixItem/demo/fixItem.js new file mode 100644 index 0000000000..0d9c2c4d2d --- /dev/null +++ b/packages/site/examples/interaction/zoomCanvasFixItem/demo/fixItem.js @@ -0,0 +1,145 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node0', size: 50, label: '0', x: 326, y: 268 }, + { id: 'node1', size: 30, label: '1', x: 280, y: 384 }, + { id: 'node2', size: 30, label: '2', x: 234, y: 167 }, + { id: 'node3', size: 30, label: '3', x: 391, y: 368 }, + { id: 'node4', size: 30, label: '4', x: 444, y: 209 }, + { id: 'node5', size: 30, label: '5', x: 378, y: 157 }, + { id: 'node6', size: 15, label: '6', x: 229, y: 400 }, + { id: 'node7', size: 15, label: '7', x: 281, y: 440 }, + { id: 'node8', size: 15, label: '8', x: 188, y: 119 }, + { id: 'node9', size: 15, label: '9', x: 287, y: 157 }, + { id: 'node10', size: 15, label: '10', x: 185, y: 200 }, + { id: 'node11', size: 15, label: '11', x: 238, y: 110 }, + { id: 'node12', size: 15, label: '12', x: 239, y: 221 }, + { id: 'node13', size: 15, label: '13', x: 176, y: 160 }, + { id: 'node14', size: 15, label: '14', x: 389, y: 423 }, + { id: 'node15', size: 15, label: '15', x: 441, y: 341 }, + { id: 'node16', size: 15, label: '16', x: 442, y: 398 }, + ], + edges: [ + { source: 'node0', target: 'node1', label: '0-1' }, + { source: 'node0', target: 'node2', label: '0-2' }, + { source: 'node0', target: 'node3', label: '0-3' }, + { source: 'node0', target: 'node4', label: '0-4' }, + { source: 'node0', target: 'node5', label: '0-5' }, + { source: 'node1', target: 'node6', label: '1-6' }, + { source: 'node1', target: 'node7', label: '1-7' }, + { source: 'node2', target: 'node8', label: '2-8' }, + { source: 'node2', target: 'node9', label: '2-9' }, + { source: 'node2', target: 'node10', label: '2-10' }, + { source: 'node2', target: 'node11', label: '2-11' }, + { source: 'node2', target: 'node12', label: '2-12' }, + { source: 'node2', target: 'node13', label: '2-13' }, + { source: 'node3', target: 'node14', label: '3-14' }, + { source: 'node3', target: 'node15', label: '3-15' }, + { source: 'node3', target: 'node16', label: '3-16' }, + ], +}; + +const container = document.getElementById('container'); + +// Add a selector to DOM +const selector = document.createElement('select'); +selector.id = 'selector'; +const selection1 = document.createElement('option'); +selection1.value = 'fixAll'; +selection1.innerHTML = 'Fix all'; +const selection2 = document.createElement('option'); +selection2.value = 'fixLabel'; +selection2.innerHTML = 'Fix fontSize of label'; +const selection3 = document.createElement('option'); +selection3.value = 'fixLineWidth'; +selection3.innerHTML = 'Fix lineWidth of keyShape'; +selector.appendChild(selection1); +selector.appendChild(selection2); +selector.appendChild(selection3); +container.appendChild(selector); + +let fixSelectedItems = { + fixAll: true, + fixState: 'yourStateName', // 'selected' by default +}; + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: [ + 'drag-canvas', + 'drag-node', + { + type: 'zoom-canvas', + fixSelectedItems, + }, + ], + }, + defaultNode: { + size: [10, 10], + style: { + lineWidth: 2, + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + size: 1, + style: { + stroke: '#e2e2e2', + lineAppendWidth: 2, + }, + }, + nodeStateStyles: { + yourStateName: { + stroke: '#f00', + lineWidth: 3, + }, + }, + edgeStateStyles: { + yourStateName: { + stroke: '#f00', + lineWidth: 3, + }, + }, +}); + +graph.on('node:click', (e) => { + graph.setItemState(e.item, 'yourStateName', true); +}); +graph.on('edge:click', (e) => { + graph.setItemState(e.item, 'yourStateName', true); +}); + +graph.on('canvas:click', (e) => { + graph.findAllByState('node', 'yourStateName').forEach((node) => { + graph.setItemState(node, 'yourStateName', false); + }); + graph.findAllByState('edge', 'yourStateName').forEach((edge) => { + graph.setItemState(edge, 'yourStateName', false); + }); +}); + +graph.data(data); +graph.render(); + +// Listen to the selector, change the mode when the selector is changed +selector.addEventListener('change', (e) => { + const value = e.target.value; + Object.keys(fixSelectedItems).forEach((key) => { + if (key !== 'fixState') fixSelectedItems[key] = false; + }); + fixSelectedItems[value] = true; +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; diff --git a/packages/site/examples/interaction/zoomCanvasFixItem/demo/meta.json b/packages/site/examples/interaction/zoomCanvasFixItem/demo/meta.json new file mode 100644 index 0000000000..53d1b17231 --- /dev/null +++ b/packages/site/examples/interaction/zoomCanvasFixItem/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "fixItem.js", + "title": { + "zh": "缩放画布时固定元素", + "en": "Fix Items while Zooming" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*nmaeRqYi9CMAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/interaction/zoomCanvasFixItem/index.en.md b/packages/site/examples/interaction/zoomCanvasFixItem/index.en.md new file mode 100644 index 0000000000..864c6211c2 --- /dev/null +++ b/packages/site/examples/interaction/zoomCanvasFixItem/index.en.md @@ -0,0 +1,10 @@ +--- +title: Fix Items while Zooming +order: 12 +--- + +To keep the selected items being highlighted, v3.5.11 supports a configuration `fixSelectedItems` for built-in behavior 'zoom-canvas'. + +## Usage + +This demo fix the size, fontSize, lineWidth of selected items by configuring the built-in behavior `'zoom-canvas'`. See the configuration `fixSelectedItems` in [zoom-canvas](/en/docs/manual/middle/states/defaultBehavior#zoom-canvas) for detail. diff --git a/packages/site/examples/interaction/zoomCanvasFixItem/index.zh.md b/packages/site/examples/interaction/zoomCanvasFixItem/index.zh.md new file mode 100644 index 0000000000..f1fc5ece60 --- /dev/null +++ b/packages/site/examples/interaction/zoomCanvasFixItem/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 缩放画布时固定选中元素 +order: 12 +--- + +为了在缩放画布时能够保持关注节点/边的高亮效果,v3.5.11 推出了内置交互 'zoom-canvas' 的配置项 `fixSelectedItems` 以达到缩放画布时固定选中节点的需求。 + +## 使用指南 + +配置内置交互 'zoom-canvas' 的配置项 `fixSelectedItems`,`fixSelectedItems` 是一个对象,包括 `fixState`、`fixAll`、`fixLabel`、`fixLineWidth`,可达到在缩小画布时固定选定的元素的大小、文本大小、描边粗细。详见 [zoom-canvas](/zh/docs/manual/middle/states/defaultBehavior#zoom-canvas)。 diff --git a/packages/site/examples/item/arrows/API.en.md b/packages/site/examples/item/arrows/API.en.md new file mode 100644 index 0000000000..e576259224 --- /dev/null +++ b/packages/site/examples/item/arrows/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/item/arrows/API.zh.md b/packages/site/examples/item/arrows/API.zh.md new file mode 100644 index 0000000000..4ff20a289b --- /dev/null +++ b/packages/site/examples/item/arrows/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/item/arrows/demo/built-in-arrows.js b/packages/site/examples/item/arrows/demo/built-in-arrows.js new file mode 100644 index 0000000000..2e11502311 --- /dev/null +++ b/packages/site/examples/item/arrows/demo/built-in-arrows.js @@ -0,0 +1,220 @@ +import G6 from '@antv/g6'; + +/** + * The usage of arrows + * by Shiwu + */ + +const data = { + nodes: [ + { + id: '0', + x: 150, + y: 50, + }, + { + id: '1', + x: 350, + y: 50, + }, + { + id: '2', + x: 150, + y: 100, + }, + { + id: '3', + x: 350, + y: 100, + }, + { + id: '4', + x: 150, + y: 150, + }, + { + id: '5', + x: 350, + y: 150, + }, + { + id: '6', + x: 150, + y: 200, + }, + { + id: '7', + x: 350, + y: 200, + }, + { + id: '8', + x: 150, + y: 250, + }, + { + id: '9', + x: 350, + y: 250, + }, + { + id: '10', + x: 150, + y: 300, + }, + { + id: '11', + x: 350, + y: 300, + }, + { + id: '12', + x: 150, + y: 350, + }, + { + id: '13', + x: 350, + y: 350, + }, + { + id: '14', + x: 150, + y: 400, + }, + { + id: '15', + x: 350, + y: 400, + }, + ], + edges: [ + { + id: 'edge0', + source: '0', + target: '1', + label: 'default arrow', + style: { + endArrow: true, + }, + }, + { + id: 'edge1', + source: '2', + target: '3', + label: 'triangle arrow', + style: { + endArrow: { + path: G6.Arrow.triangle(), + }, + }, + }, + { + id: 'edge2', + source: '4', + target: '5', + label: 'vee arrow', + style: { + endArrow: { + path: G6.Arrow.vee(), + }, + }, + }, + { + id: 'edge3', + source: '6', + target: '7', + label: 'circle arrow', + style: { + endArrow: { + path: G6.Arrow.circle(5, 3), + d: 3, + }, + }, + }, + { + id: 'edge4', + source: '8', + target: '9', + label: 'diamond arrow', + style: { + endArrow: { + path: G6.Arrow.diamond(10, 10, 3), + d: 3, + }, + }, + }, + { + id: 'edge5', + source: '10', + target: '11', + label: 'rect arrow', + style: { + endArrow: { + path: G6.Arrow.rect(10, 10, 3), + d: 3, + }, + }, + }, + { + id: 'edge6', + source: '12', + target: '13', + label: 'rect arrow 2', + style: { + endArrow: { + path: G6.Arrow.rect(10, 2, 5), + d: 5, + }, + }, + }, + { + id: 'edge7', + source: '14', + target: '15', + label: 'triangleRect arrow', + style: { + endArrow: { + path: G6.Arrow.triangleRect(10, 10, 10, 2, 4), + }, + }, + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +data.edges.forEach((edge) => { + edge.style.stroke = '#F6BD16'; + console.log(edge.style.endArrow); + if (edge.id !== 'edge0') edge.style.endArrow.fill = '#F6BD16'; +}); +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + defaultNode: { + size: 15, + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + modes: { + // behaviors + default: ['drag-node'], + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/arrows/demo/custom-arrows.js b/packages/site/examples/item/arrows/demo/custom-arrows.js new file mode 100644 index 0000000000..308b018efb --- /dev/null +++ b/packages/site/examples/item/arrows/demo/custom-arrows.js @@ -0,0 +1,92 @@ +import G6 from '@antv/g6'; + +/** + * The usage of arrows + * by Shiwu + */ + +const data = { + nodes: [ + { + id: '0', + x: 150, + y: 50, + }, + { + id: '1', + x: 350, + y: 50, + }, + { + id: '2', + x: 150, + y: 100, + }, + { + id: '3', + x: 350, + y: 100, + }, + ], + edges: [ + { + id: 'edge0', + source: '0', + target: '1', + label: 'custom arrow 1', + style: { + endArrow: { + path: 'M 3,-5 L 3,5 L 15,10 L 15,-10 Z', + }, + }, + }, + { + id: 'edge1', + source: '2', + target: '3', + label: 'custom arrow 2', + style: { + endArrow: { + path: 'M0,0 L10,4 L14,14 L18,4 L28,0 L18,-4 L14,-14 L10,-4 Z', + }, + }, + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +data.edges.forEach((edge) => { + edge.style.stroke = '#F6BD16'; + console.log(edge.style.endArrow); + edge.style.endArrow.fill = '#F6BD16'; +}); +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + defaultNode: { + size: 15, + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + modes: { + // behaviors + default: ['drag-node'], + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/arrows/demo/meta.json b/packages/site/examples/item/arrows/demo/meta.json new file mode 100644 index 0000000000..f4bd118d35 --- /dev/null +++ b/packages/site/examples/item/arrows/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "built-in-arrows.js", + "title": { + "zh": "默认和内置箭头", + "en": "Default and Built-in Arrows" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*mGusTo9FcCYAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "custom-arrows.js", + "title": { + "zh": "自定义箭头", + "en": "Custom Arrows" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*C2ODRb7E9mwAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/item/arrows/index.en.md b/packages/site/examples/item/arrows/index.en.md new file mode 100644 index 0000000000..356286bbef --- /dev/null +++ b/packages/site/examples/item/arrows/index.en.md @@ -0,0 +1,10 @@ +--- +title: Arrow of Edge +order: 3 +--- + +G6 provids [default arrow and built-in arrows](/en/docs/manual/middle/elements/edges/arrow). If these arrows do not meet your requirements, you can [custom arrow](/en/docs/manual/middle/elements/edges/built-in/arrow#custom-arrow). + +## Usage + +Configure the start and end arrows for a built-in or custom edge. diff --git a/packages/site/examples/item/arrows/index.zh.md b/packages/site/examples/item/arrows/index.zh.md new file mode 100644 index 0000000000..a8e19d1b64 --- /dev/null +++ b/packages/site/examples/item/arrows/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 边的箭头 +order: 3 +--- + +G6 提供了[默认箭头和内置箭头](/zh/docs/manual/middle/elements/edges/arrow)。当这些箭头不满足条件时,可以[自定义箭头](/zh/docs/manual/middle/elements/edges/arrow#自定义箭头)。 + +## 何时使用 + +为内置边和自定义边配置起始端箭头和结束端箭头。 diff --git a/packages/site/examples/item/customCombo/API.en.md b/packages/site/examples/item/customCombo/API.en.md new file mode 100644 index 0000000000..8f1203f627 --- /dev/null +++ b/packages/site/examples/item/customCombo/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/item/customCombo/API.zh.md b/packages/site/examples/item/customCombo/API.zh.md new file mode 100644 index 0000000000..c655cc24c6 --- /dev/null +++ b/packages/site/examples/item/customCombo/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/item/customCombo/demo/cCircle.js b/packages/site/examples/item/customCombo/demo/cCircle.js new file mode 100644 index 0000000000..a4eea78e31 --- /dev/null +++ b/packages/site/examples/item/customCombo/demo/cCircle.js @@ -0,0 +1,146 @@ +import G6 from '@antv/g6'; + +/** + * The demo shows customing a combo type by extending the built-in circle combo + * by Shiwu + * + */ + +// The symbols for the marker inside the combo +const collapseIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ]; +}; +const expandIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ['M', x - r + r, y - r + 4], + ['L', x, y + r - 4], + ]; +}; + +G6.registerCombo( + 'cCircle', + { + drawShape: function draw(cfg, group) { + const self = this; + // Get the shape style, where the style.r corresponds to the R in the Illustration of Built-in Rect Combo + const style = self.getShapeStyle(cfg); + // Add a circle shape as keyShape which is the same as the extended 'circle' type Combo + const circle = group.addShape('circle', { + attrs: { + ...style, + x: 0, + y: 0, + r: style.r, + }, + draggable: true, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'combo-keyShape', + }); + // Add the marker on the bottom + const marker = group.addShape('marker', { + attrs: { + ...style, + fill: '#fff', + opacity: 1, + x: 0, + y: style.r, + r: 10, + symbol: collapseIcon, + }, + draggable: true, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'combo-marker-shape', + }); + + return circle; + }, + // Define the updating logic for the marker + afterUpdate: function afterUpdate(cfg, combo) { + const self = this; + // Get the shape style, where the style.r corresponds to the R in the Illustration of Built-in Rect Combo + const style = self.getShapeStyle(cfg); + const group = combo.get('group'); + // Find the marker shape in the graphics group of the Combo + const marker = group.find((ele) => ele.get('name') === 'combo-marker-shape'); + // Update the marker shape + marker.attr({ + x: 0, + y: style.r, + // The property 'collapsed' in the combo data represents the collapsing state of the Combo + // Update the symbol according to 'collapsed' + symbol: cfg.collapsed ? expandIcon : collapseIcon, + }); + }, + }, + 'circle', +); + +const data = { + nodes: [ + { id: 'node1', x: 250, y: 200, comboId: 'combo1' }, + { id: 'node2', x: 300, y: 200, comboId: 'combo1' }, + { id: 'node3', x: 100, y: 200, comboId: 'combo3' }, + ], + combos: [ + { id: 'combo1', label: 'Combo 1', parentId: 'combo2' }, + { id: 'combo2', label: 'Combo 2' }, + { id: 'combo3', label: 'Combo 3', collapsed: true }, + ], +}; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Click the bottom marker to collapse/expand the combo.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + // Configure the combos globally + defaultCombo: { + // The type of the combos. You can also assign type in the data of combos + type: 'cCircle', + labelCfg: { + refY: 2, + }, + // ... Other global configurations for combos + }, + modes: { + default: ['drag-combo', 'drag-node', 'drag-canvas'], + }, +}); +graph.data(data); +graph.render(); + +// collapse/expand when click the marker +graph.on('combo:click', (e) => { + if (e.target.get('name') === 'combo-marker-shape') { + // graph.collapseExpandCombo(e.item.getModel().id); + graph.collapseExpandCombo(e.item); + if (graph.get('layout')) graph.layout(); + else graph.refreshPositions(); + } +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/item/customCombo/demo/cRect.js b/packages/site/examples/item/customCombo/demo/cRect.js new file mode 100644 index 0000000000..bf68c5c7ea --- /dev/null +++ b/packages/site/examples/item/customCombo/demo/cRect.js @@ -0,0 +1,144 @@ +import G6 from '@antv/g6'; + +/** + * The demo shows customing a combo type by extending the built-in circle combo + * by Shiwu + * + */ + +// The symbols for the marker inside the combo +const collapseIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ]; +}; +const expandIcon = (x, y, r) => { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ['M', x - r + r, y - r + 4], + ['L', x, y + r - 4], + ]; +}; + +G6.registerCombo( + 'cRect', + { + drawShape: function drawShape(cfg, group) { + const self = this; + // Get the padding from the configuration + cfg.padding = cfg.padding || [50, 20, 20, 20]; + // Get the shape's style, where the style.width and style.height correspond to the width and height in the figure of Illustration of Built-in Rect Combo + const style = self.getShapeStyle(cfg); + // Add a rect shape as the keyShape which is the same as the extended rect Combo + const rect = group.addShape('rect', { + attrs: { + ...style, + x: -style.width / 2 - (cfg.padding[3] - cfg.padding[1]) / 2, + y: -style.height / 2 - (cfg.padding[0] - cfg.padding[2]) / 2, + width: style.width, + height: style.height, + }, + draggable: true, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'combo-keyShape', + }); + // Add the circle on the right + group.addShape('marker', { + attrs: { + ...style, + fill: '#fff', + opacity: 1, + // cfg.style.width and cfg.style.heigth correspond to the innerWidth and innerHeight in the figure of Illustration of Built-in Rect Combo + x: cfg.style.width / 2 + cfg.padding[1], + y: (cfg.padding[2] - cfg.padding[0]) / 2, + r: 10, + symbol: collapseIcon, + }, + draggable: true, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'combo-marker-shape', + }); + return rect; + }, + // Define the updating logic of the right circle + afterUpdate: function afterUpdate(cfg, combo) { + const group = combo.get('group'); + // Find the circle shape in the graphics group of the Combo by name + const marker = group.find((ele) => ele.get('name') === 'combo-marker-shape'); + // Update the position of the right circle + marker.attr({ + // cfg.style.width and cfg.style.heigth correspond to the innerWidth and innerHeight in the figure of Illustration of Built-in Rect Combo + x: cfg.style.width / 2 + cfg.padding[1], + y: (cfg.padding[2] - cfg.padding[0]) / 2, + // The property 'collapsed' in the combo data represents the collapsing state of the Combo + // Update the symbol according to 'collapsed' + symbol: cfg.collapsed ? expandIcon : collapseIcon, + }); + }, + }, + 'rect', +); + +const data = { + nodes: [ + { id: 'node1', x: 250, y: 200, comboId: 'combo1' }, + { id: 'node2', x: 300, y: 200, comboId: 'combo1' }, + { id: 'node3', x: 100, y: 200, comboId: 'combo3' }, + ], + combos: [ + { id: 'combo1', label: 'Combo 1', parentId: 'combo2' }, + { id: 'combo2', label: 'Combo 2' }, + { id: 'combo3', label: 'Combo 3', collapsed: true }, + ], +}; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Click the right marker to collapse/expand the combo.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + // Configure the combos globally + defaultCombo: { + // The type of the combos. You can also assign type in the data of combos + type: 'cRect', + // ... Other global configurations for combos + }, + modes: { + default: ['drag-combo', 'drag-node', 'drag-canvas'], + }, +}); +graph.data(data); +graph.render(); + +// collapse/expand when click the marker +graph.on('combo:click', (e) => { + if (e.target.get('name') === 'combo-marker-shape') { + // graph.collapseExpandCombo(e.item.getModel().id); + graph.collapseExpandCombo(e.item); + if (graph.get('layout')) graph.layout(); + else graph.refreshPositions(); + } +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/item/customCombo/demo/meta.json b/packages/site/examples/item/customCombo/demo/meta.json new file mode 100644 index 0000000000..8ee40adeac --- /dev/null +++ b/packages/site/examples/item/customCombo/demo/meta.json @@ -0,0 +1,18 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "cCircle.js", + "title": "带有 Marker 的 Circle", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*1LelSq5TP9EAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "cRect.js", + "title": "带有 Marker 的 Rect", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*rQGaT4kiaYoAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/item/customCombo/index.en.md b/packages/site/examples/item/customCombo/index.en.md new file mode 100644 index 0000000000..534fae7def --- /dev/null +++ b/packages/site/examples/item/customCombo/index.en.md @@ -0,0 +1,10 @@ +--- +title: Custom Combo +order: 6 +--- + +The custom combo mechanism in G6 allows users to design their own node by extending the built-in combo types when there are no appropriate built-in nodes for their scenario. + +## Usage + +The following examples show how to custom a combo type by extending the built-in combo types. For more information, please refer to [Custom Comb o Doc](/en/docs/manual/middle/elements/combos/custom-combo). diff --git a/packages/site/examples/item/customCombo/index.zh.md b/packages/site/examples/item/customCombo/index.zh.md new file mode 100644 index 0000000000..9c783bac23 --- /dev/null +++ b/packages/site/examples/item/customCombo/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 自定义 Combo +order: 6 +--- + +当 G6 的内置 Combo 不能满足需求时,G6 的自定义 Combo 机制允许用户通过扩展内置 Combo 来设计和定制新的节点类型。 + +## 使用指南 + +下面示例展示了通过扩展内置的 Circle 和 Rect Combo,进行 Combo 的自定义。更多信息参见[自定义 Combo 文档](/zh/docs/manual/middle/elements/combos/custom-combo)。 diff --git a/packages/site/examples/item/customEdge/API.en.md b/packages/site/examples/item/customEdge/API.en.md new file mode 100644 index 0000000000..8f1203f627 --- /dev/null +++ b/packages/site/examples/item/customEdge/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/item/customEdge/API.zh.md b/packages/site/examples/item/customEdge/API.zh.md new file mode 100644 index 0000000000..c655cc24c6 --- /dev/null +++ b/packages/site/examples/item/customEdge/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/item/customEdge/demo/customPolyline.js b/packages/site/examples/item/customEdge/demo/customPolyline.js new file mode 100644 index 0000000000..145189ebcb --- /dev/null +++ b/packages/site/examples/item/customEdge/demo/customPolyline.js @@ -0,0 +1,136 @@ +import G6 from '@antv/g6'; + +/** + * Extend the built-in line edge and override getPath and getShapeStyle to custom a polyline edge + * by siogo's issue(https://github.com/antvis/g6/issues/814) + * + * If you want to fit the dragging, you need to adjust the controlpoints while dragging + * + */ + +G6.registerEdge( + 'line-arrow', + { + getPath(points) { + const startPoint = points[0]; + const endPoint = points[1]; + return [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y], + ['L', endPoint.x, endPoint.y], + ]; + }, + getShapeStyle(cfg) { + const startPoint = cfg.startPoint; + const endPoint = cfg.endPoint; + const controlPoints = this.getControlPoints(cfg); + let points = [startPoint]; // the start point + // the control points + if (controlPoints) { + points = points.concat(controlPoints); + } + // the end point + points.push(endPoint); + const path = this.getPath(points); + const style = Object.assign( + {}, + G6.Global.defaultEdge.style, + { + stroke: '#BBB', + lineWidth: 1, + path, + }, + cfg.style, + ); + return style; + }, + }, + 'line', +); + +const data = { + nodes: [ + { + id: '7', + x: 150, + y: 100, + size: 40, + anchorPoints: [ + [1, 0.5], + [1, 0], + ], + }, + { + id: '8', + x: 300, + y: 200, + size: 40, + anchorPoints: [ + [0, 0.5], + [0, 1], + ], + }, + ], + edges: [ + { + source: '7', + target: '8', + sourceAnchor: 0, + targetAnchor: 0, + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + // behavior + default: ['drag-node', 'drag-canvas'], + }, + defaultNode: { + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + linkPoints: { + left: true, + right: true, + fill: '#fff', + stroke: '#1890FF', + size: 3, + }, + }, + defaultEdge: { + type: 'line-arrow', + style: { + stroke: '#F6BD16', + startArrow: { + path: 'M 0,0 L 12,6 L 9,0 L 12,-6 Z', + fill: '#F6BD16', + }, + endArrow: { + path: 'M 0,0 L 12,6 L 9,0 L 12,-6 Z', + fill: '#F6BD16', + }, + }, + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customEdge/demo/customPolyline2.js b/packages/site/examples/item/customEdge/demo/customPolyline2.js new file mode 100644 index 0000000000..55e3ce4a43 --- /dev/null +++ b/packages/site/examples/item/customEdge/demo/customPolyline2.js @@ -0,0 +1,128 @@ +import G6 from '@antv/g6'; + +/** + * Custom a new polyline + * by siogo's issue(https://github.com/antvis/g6/issues/814) + * + * If you want to fit the dragging, you need to adjust the controlpoints while dragging + */ +G6.registerEdge('line-arrow', { + options: { + style: { + stroke: '#ccc', + }, + }, + draw: function draw(cfg, group) { + const startPoint = cfg.startPoint; + const endPoint = cfg.endPoint; + + const stroke = (cfg.style && cfg.style.stroke) || this.options.style.stroke; + const startArrow = (cfg.style && cfg.style.startArrow) || undefined; + const endArrow = (cfg.style && cfg.style.endArrow) || undefined; + + const keyShape = group.addShape('path', { + attrs: { + path: [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y], + ['L', endPoint.x, endPoint.y], + ], + stroke, + lineWidth: 1, + startArrow, + endArrow, + }, + className: 'edge-shape', + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'edge-shape', + }); + return keyShape; + }, +}); + +const data = { + nodes: [ + { + id: '7', + x: 150, + y: 100, + size: 40, + anchorPoints: [ + [1, 0.5], + [1, 0], + ], + }, + { + id: '8', + x: 300, + y: 200, + size: 40, + anchorPoints: [ + [0, 0.5], + [0, 1], + ], + }, + ], + edges: [ + { + source: '7', + target: '8', + sourceAnchor: 0, + targetAnchor: 0, + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + // behavior + default: ['drag-node', 'drag-canvas'], + }, + defaultNode: { + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + linkPoints: { + left: true, + right: true, + fill: '#fff', + stroke: '#1890FF', + size: 3, + }, + }, + defaultEdge: { + type: 'line-arrow', + style: { + stroke: '#F6BD16', + startArrow: { + path: 'M 0,0 L 12,6 L 9,0 L 12,-6 Z', + fill: '#F6BD16', + }, + endArrow: { + path: 'M 0,0 L 12,6 L 9,0 L 12,-6 Z', + fill: '#F6BD16', + }, + }, + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customEdge/demo/edgeMulLabel.js b/packages/site/examples/item/customEdge/demo/edgeMulLabel.js new file mode 100644 index 0000000000..3fe59c4682 --- /dev/null +++ b/packages/site/examples/item/customEdge/demo/edgeMulLabel.js @@ -0,0 +1,137 @@ +import G6 from '@antv/g6'; + +/** + * Custom the edge with multiple labels + * by Changzhe + */ + +// custom the edge with multiple labels +G6.registerEdge('multipleLabelsEdge', { + options: { + style: { + stroke: '#000', + }, + }, + labelAutoRotate: true, + draw(cfg, group) { + const startPoint = cfg.startPoint; + const endPoint = cfg.endPoint; + const stroke = (cfg.style && cfg.style.stroke) || this.options.style.stroke; + + const shape = group.addShape('path', { + attrs: { + stroke, + path: [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x, endPoint.y], + ], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + if (cfg.label && cfg.label.length) { + // the left label + group.addShape('text', { + attrs: { + text: cfg.label[0], + fill: '#595959', + textAlign: 'start', + textBaseline: 'middle', + x: startPoint.x, + y: startPoint.y - 10, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'left-text-shape', + }); + if (cfg.label.length > 1) { + // the right label + group.addShape('text', { + attrs: { + text: cfg.label[1], + fill: '#595959', + textAlign: 'end', + textBaseline: 'middle', + x: endPoint.x, + y: endPoint.y - 10, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'right-text-shape', + }); + } + } + // return the keyShape + return shape; + }, +}); + +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 100, + label: 'node1', + }, + { + id: 'node2', + x: 300, + y: 100, + label: 'node2', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + // The left and right labels + label: ['hello', 'world'], + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: [ + { + type: 'drag-node', + delegate: false, + }, + 'drag-canvas', + { + type: 'zoom-canvas', + sensitivity: 0.5, + }, + ], + }, + defaultNode: { + type: 'circle', + size: [50], + linkPoints: { + left: true, + right: true, + }, + }, + defaultEdge: { + type: 'multipleLabelsEdge', + style: { + stroke: '#F6BD16', + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customEdge/demo/extraShape.js b/packages/site/examples/item/customEdge/demo/extraShape.js new file mode 100644 index 0000000000..9a6ad171a6 --- /dev/null +++ b/packages/site/examples/item/customEdge/demo/extraShape.js @@ -0,0 +1,115 @@ +import G6 from '@antv/g6'; + +/** + * Custom the edge with extra shapes + * by Shiwu + */ + +// custom the edge with an extra rect +G6.registerEdge( + 'extra-shape-edge', + { + afterDraw(cfg, group) { + // get the first shape in the graphics group of this edge, it is the path of the edge here + // 获取图形组中的第一个图形,在这里就是边的路径图形 + const shape = group.get('children')[0]; + // get the coordinate of the mid point on the path + // 获取路径图形的中点坐标 + const midPoint = shape.getPoint(0.5); + const rectColor = cfg.midPointColor || '#333'; + // add a rect on the mid point of the path. note that the origin of a rect shape is on its lefttop + // 在中点增加一个矩形,注意矩形的原点在其左上角 + group.addShape('rect', { + attrs: { + width: 10, + height: 10, + fill: rectColor || '#333', + // x and y should be minus width / 2 and height / 2 respectively to translate the center of the rect to the midPoint + // x 和 y 分别减去 width / 2 与 height / 2,使矩形中心在 midPoint 上 + x: midPoint.x - 5, + y: midPoint.y - 5, + }, + }); + + // get the coordinate of the quatile on the path + // 获取路径上的四分位点坐标 + const quatile = shape.getPoint(0.25); + const quatileColor = cfg.quatileColor || '#333'; + // add a circle on the quatile of the path + // 在四分位点上放置一个圆形 + group.addShape('circle', { + attrs: { + r: 5, + fill: quatileColor || '#333', + x: quatile.x, + y: quatile.y, + }, + }); + }, + update: undefined, + }, + 'cubic', +); + +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 100, + }, + { + id: 'node2', + x: 300, + y: 100, + }, + { + id: 'node3', + x: 300, + y: 200, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + midPointColor: '#f00', + quatileColor: '#f00', + }, + { + source: 'node1', + target: 'node3', + midPointColor: '#0f0', + quatileColor: '#0f0', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-node', 'drag-canvas'], + }, + defaultEdge: { + type: 'extra-shape-edge', + style: { + stroke: '#F6BD16', + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customEdge/demo/meta.json b/packages/site/examples/item/customEdge/demo/meta.json new file mode 100644 index 0000000000..22df7cc4c8 --- /dev/null +++ b/packages/site/examples/item/customEdge/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "extraShape.js", + "title": { + "zh": "添加额外图形", + "en": "Custom Edge with Extra Shapes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*L4rZQbqwaRYAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "customPolyline.js", + "title": { + "zh": "自定义折线", + "en": "Custom Polyline" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*FuS3Qq_lDiIAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "customPolyline2.js", + "title": { + "zh": "自定义折线 2", + "en": "Custom Polyline 2" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ACNnQLPy20EAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "edgeMulLabel.js", + "title": { + "zh": "多标签边", + "en": "Multiple Labels" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*01YORI5jRp8AAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/item/customEdge/index.en.md b/packages/site/examples/item/customEdge/index.en.md new file mode 100644 index 0000000000..f6911fdeba --- /dev/null +++ b/packages/site/examples/item/customEdge/index.en.md @@ -0,0 +1,19 @@ +--- +title: Custom Edge +order: 5 +--- + +The custom edge mechanism in G6 allows users to design their own edge when there are no appropriate built-in edges for their scenario. + +## Usage + +The first two demos below show how to customize polyline edge. There are two ways to customize an edge: + +1. Extend the line edge, override `getPath` and `getShapeStyle`; +2. Override `draw`. + +Updating the control points of polyline while dragging the end nodes is an important problem of polyline edge. For this situation, we recommend users to use built-in `polyline` edge. + +The third demo shows how to customize an edge with multiple labels. + +For more information, please refer to the document [Custom Edge](/en/docs/manual/middle/elements/edges/custom-edge). diff --git a/packages/site/examples/item/customEdge/index.zh.md b/packages/site/examples/item/customEdge/index.zh.md new file mode 100644 index 0000000000..2080fdd769 --- /dev/null +++ b/packages/site/examples/item/customEdge/index.zh.md @@ -0,0 +1,19 @@ +--- +title: 自定义边 +order: 5 +--- + +当 G6 的内置边不能满足需求时,G6 的自定义边机制允许用户设计和定制新的边类型。 + +## 使用指南 + +下面的前两个代码演示了自定义折线 polyline 边。自定义边可以通过两种方式实现: + +1. 继承 line,复写 `getPath` 和 `getShapeStyle` 方法; +2. 复写 `draw` 方法。 + +拖动边的两个端点时,常常需要动态更新折线的控制点位置。建议使用 G6 内置的 `polyline` 满足这一需求。 + +第三个代码演示了自定义带有多个文本标签的边。 + +更多信息参见[自定义边](/zh/docs/manual/middle/elements/edges/custom-edge)。 diff --git a/packages/site/examples/item/customNode/API.en.md b/packages/site/examples/item/customNode/API.en.md new file mode 100644 index 0000000000..8f1203f627 --- /dev/null +++ b/packages/site/examples/item/customNode/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/item/customNode/API.zh.md b/packages/site/examples/item/customNode/API.zh.md new file mode 100644 index 0000000000..c655cc24c6 --- /dev/null +++ b/packages/site/examples/item/customNode/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/item/customNode/demo/areaChart.js b/packages/site/examples/item/customNode/demo/areaChart.js new file mode 100644 index 0000000000..77b1665d43 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/areaChart.js @@ -0,0 +1,213 @@ +import G6 from '@antv/g6'; + +/** + * Custom a area chart node + * by Jingxi + * + */ + +/** + * Register a area chart node + */ +G6.registerNode('area', { + draw(cfg, group) { + const baseR = 30; + + // Ref line + let refR = baseR; + const refInc = 10; + for (let i = 0; i < 6; i++) { + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: (refR += refInc), + stroke: '#bae7ff', + lineDash: [4, 4], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + } + const everyIncAngle = (2 * Math.PI * (360 / 5)) / 360; + const tempIncValues = [baseR, baseR, baseR, baseR, baseR]; + const allRs = []; + cfg.details.forEach((cat) => { + const oneRs = []; + cat.values.forEach((v, i) => { + const R = tempIncValues[i] + v * 0.4; + oneRs.push(R); + tempIncValues[i] = R; + }); + allRs.push(oneRs); + }); + const strokeColors = [ + 'rgba(91, 143, 249,1)', + 'rgba(90, 216, 166,1)', + 'rgba(246, 189, 22,1)', + 'rgba(232, 104, 74,1)', + 'rgba(255, 157, 77,1)', + ]; + const fillColors = [ + 'rgba(91, 143, 249,0.5)', + 'rgba(90, 216, 166,0.5)', + 'rgba(246, 189, 22,0.5)', + 'rgba(232, 104, 74,0.5)', + 'rgba(255, 157, 77,0.5)', + ]; + + allRs.reverse().forEach((Rs, index) => { + let curAngle = 0; + const poss = []; + Rs.forEach((r) => { + const xPos = r * Math.cos(curAngle); + const yPos = r * Math.sin(curAngle); + curAngle += everyIncAngle; + poss.push([xPos, yPos]); + }); + const Ls = poss.map((p, i) => { + if (i === 0) { + return ['M', ...p]; + } + return ['L', ...p]; + }); + + group.addShape('path', { + attrs: { + path: [ + ...Ls, + ['Z'], // close the path + ], + stroke: strokeColors[index], + fill: fillColors[index], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape1', + }); + }); + let nowAngle2 = 0; + const everyIncAngleCat = (2 * Math.PI * (360 / 5)) / 360; + for (let i = 0; i < 5; i++) { + const r = 30 + 60; + const xPos = r * Math.cos(nowAngle2); + const yPos = r * Math.sin(nowAngle2); + + group.addShape('path', { + attrs: { + path: [ + ['M', 0, 0], + ['L', xPos, yPos], + ], + lineDash: [4, 4], + stroke: 'darkgray', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape2', + }); + nowAngle2 += everyIncAngleCat; + } + + // add a circle with the same filling color with background + group.addShape('circle', { + // attrs: style + attrs: { + x: 0, // 居中 + y: 0, + r: baseR, + fill: cfg.centerColor, + stroke: 'darkgray', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + + if (cfg.label) { + group.addShape('text', { + // attrs: style + attrs: { + x: 0, + y: 0, + textAlign: 'center', + textBaseline: 'middle', + text: cfg.label, + fill: 'white', + fontStyle: 'bold', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', + }); + } + return group; + }, +}); + +/** data */ +const data = { + nodes: [ + { + id: 'nodeD', + x: 150, + y: 150, + label: 'Area1', + type: 'area', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + details: [ + { cat: 'pv', values: [20, 30, 40, 30, 30], color: '#5ad8a6' }, + { cat: 'dal', values: [40, 30, 20, 30, 50], color: '#ff99c3' }, + { cat: 'uv', values: [40, 30, 30, 40, 40], color: '#6dc8ec' }, + { cat: 'sal', values: [20, 30, 50, 20, 20], color: '#269a99' }, + { cat: 'cal', values: [10, 10, 20, 20, 20], color: '#9270CA' }, + ], + centerColor: '#5b8ff9', + }, + { + id: 'nodeD2', + x: 500, + y: 150, + label: 'Area2', + type: 'area', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + details: [ + { cat: 'pv', values: [10, 10, 80, 20, 10], color: '#5ad8a6' }, + { cat: 'dal', values: [20, 30, 10, 50, 40], color: '#ff99c3' }, + { cat: 'uv', values: [10, 50, 30, 20, 30], color: '#6dc8ec' }, + { cat: 'sal', values: [70, 30, 20, 20, 20], color: '#269a99' }, + { cat: 'cal', values: [50, 10, 20, 70, 30], color: '#9270CA' }, + ], + centerColor: '#5b8ff9', + }, + ], + edges: [ + { + source: 'nodeD', + target: 'nodeD2', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/barChart.js b/packages/site/examples/item/customNode/demo/barChart.js new file mode 100644 index 0000000000..676784225c --- /dev/null +++ b/packages/site/examples/item/customNode/demo/barChart.js @@ -0,0 +1,186 @@ +import G6 from '@antv/g6'; + +/** + * Custom a Nightingale char node + * by Jingxi + */ + +/** + * Custom a Nightingale char node + */ + +const getPath = (cx, cy, rs, re, startAngle, endAngle, clockwise) => { + const flag1 = clockwise ? 1 : 0; + const flag2 = clockwise ? 0 : 1; + return [ + ['M', Math.cos(startAngle) * rs + cx, Math.sin(startAngle) * rs + cy], + ['L', Math.cos(startAngle) * re + cx, Math.sin(startAngle) * re + cy], + ['A', re, re, 0, 0, flag1, Math.cos(endAngle) * re + cx, Math.sin(endAngle) * re + cy], + ['L', Math.cos(endAngle) * rs + cx, Math.sin(endAngle) * rs + cy], + ['A', rs, rs, 0, 0, flag2, Math.cos(startAngle) * rs + cx, Math.sin(startAngle) * rs + cy], + ['Z'], + ]; +}; +G6.registerNode('circleBar', { + draw(cfg, group) { + /* + G: + Fan + x: the circle center of the fan + y: the circle center of the fan + rs: inner radius + re: outer radius + startAngle: start angle + endAngle: end angle + clockwise: render clockwisely if it is true + */ + const baseR = 30; + let nowAngle = 0; + const everyIncAngle = (2 * Math.PI * (360 / 5 / 5)) / 360; + cfg.details.forEach((cat) => { + cat.values.forEach((item) => { + const re = item + baseR; + const path0 = getPath( + 0, + 0, + baseR, + item + baseR, + nowAngle, + (nowAngle += everyIncAngle), + false, + ); + const fan = group.addShape('path', { + attrs: { + path: path0, + stroke: 'darkgray', + fill: cat.color, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + // behavior animation + fan.on('mouseenter', () => { + fan.animate( + { + re: re + 8, + }, + { + repeat: false, + duration: 300, + }, + ); + }); + fan.on('mouseleave', () => { + fan.animate( + { + re, + }, + { + repeat: false, + duration: 300, + }, + ); + }); + // set the name + fan.set('name', 'littleCircle'); + }); + }); + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: baseR, + fill: cfg.centerColor, + stroke: 'darkgray', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + if (cfg.label) { + group.addShape('text', { + attrs: { + x: 0, + y: 0, + textAlign: 'center', + textBaseline: 'middle', + text: cfg.label, + fill: 'white', + fontStyle: 'bold', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', + }); + } + return group; + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, +}); + +const data = { + nodes: [ + { + id: 'nodeA', + x: 150, + y: 150, + label: 'Bar1', + type: 'circleBar', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + details: [ + { cat: 'pv', values: [20, 30, 40, 30, 30], color: '#5B8FF9' }, + { cat: 'dal', values: [40, 30, 20, 30, 50], color: '#5AD8A6' }, + { cat: 'uv', values: [40, 30, 30, 40, 40], color: '#5D7092' }, + { cat: 'sal', values: [20, 30, 50, 20, 20], color: '#F6BD16' }, + { cat: 'cal', values: [10, 10, 20, 20, 20], color: '#E8684A' }, + ], + centerColor: '#5b8ff9', + }, + { + id: 'nodeA2', + x: 500, + y: 150, + label: 'Bar2', + type: 'circleBar', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + details: [ + { cat: 'pv', values: [10, 10, 80, 20, 10], color: '#5ad8a6' }, + { cat: 'dal', values: [20, 30, 10, 50, 40], color: '#ff99c3' }, + { cat: 'uv', values: [10, 50, 30, 20, 30], color: '#6dc8ec' }, + { cat: 'sal', values: [70, 30, 20, 20, 20], color: '#269a99' }, + { cat: 'cal', values: [50, 10, 20, 70, 30], color: '#9270CA' }, + ], + centerColor: '#5b8ff9', + }, + ], + edges: [ + { + source: 'nodeA', + target: 'nodeA2', + }, + ], +}; + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/card.js b/packages/site/examples/item/customNode/demo/card.js new file mode 100644 index 0000000000..dd52f7a9cb --- /dev/null +++ b/packages/site/examples/item/customNode/demo/card.js @@ -0,0 +1,490 @@ +import G6 from '@antv/g6'; + +let graph; + +const ERROR_COLOR = '#F5222D'; +const getNodeConfig = (node) => { + if (node.nodeError) { + return { + basicColor: ERROR_COLOR, + fontColor: '#FFF', + borderColor: ERROR_COLOR, + bgColor: '#E66A6C', + }; + } + let config = { + basicColor: '#5B8FF9', + fontColor: '#5B8FF9', + borderColor: '#5B8FF9', + bgColor: '#C6E5FF', + }; + switch (node.type) { + case 'root': { + config = { + basicColor: '#E3E6E8', + fontColor: 'rgba(0,0,0,0.85)', + borderColor: '#E3E6E8', + bgColor: '#5b8ff9', + }; + break; + } + default: + break; + } + return config; +}; + +const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ]; +}; +const EXPAND_ICON = function EXPAND_ICON(x, y, r) { + return [ + ['M', x - r, y], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x - r + 4, y], + ['L', x - r + 2 * r - 4, y], + ['M', x - r + r, y - r + 4], + ['L', x, y + r - 4], + ]; +}; +const nodeBasicMethod = { + createNodeBox: (group, config, w, h, isRoot) => { + /* 最外面的大矩形 */ + const container = group.addShape('rect', { + attrs: { + x: 0, + y: 0, + width: w, + height: h, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'big-rect-shape', + }); + if (!isRoot) { + /* 左边的小圆点 */ + group.addShape('circle', { + attrs: { + x: 3, + y: h / 2, + r: 6, + fill: config.basicColor, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'left-dot-shape', + }); + } + /* 矩形 */ + group.addShape('rect', { + attrs: { + x: 3, + y: 0, + width: w - 19, + height: h, + fill: config.bgColor, + stroke: config.borderColor, + radius: 2, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', + }); + + /* 左边的粗线 */ + group.addShape('rect', { + attrs: { + x: 3, + y: 0, + width: 3, + height: h, + fill: config.basicColor, + radius: 1.5, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'left-border-shape', + }); + return container; + }, + /* 生成树上的 marker */ + createNodeMarker: (group, collapsed, x, y) => { + group.addShape('circle', { + attrs: { + x, + y, + r: 13, + fill: 'rgba(47, 84, 235, 0.05)', + opacity: 0, + zIndex: -2, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'collapse-icon-bg', + }); + group.addShape('marker', { + attrs: { + x, + y, + r: 7, + symbol: collapsed ? EXPAND_ICON : COLLAPSE_ICON, + stroke: 'rgba(0,0,0,0.25)', + fill: 'rgba(0,0,0,0)', + lineWidth: 1, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'collapse-icon', + }); + }, + afterDraw: (cfg, group) => { + /* 操作 marker 的背景色显示隐藏 */ + const icon = group.find((element) => element.get('name') === 'collapse-icon'); + if (icon) { + const bg = group.find((element) => element.get('name') === 'collapse-icon-bg'); + icon.on('mouseenter', () => { + bg.attr('opacity', 1); + graph.get('canvas').draw(); + }); + icon.on('mouseleave', () => { + bg.attr('opacity', 0); + graph.get('canvas').draw(); + }); + } + /* ip 显示 */ + const ipBox = group.find((element) => element.get('name') === 'ip-box'); + if (ipBox) { + /* ip 复制的几个元素 */ + const ipLine = group.find((element) => element.get('name') === 'ip-cp-line'); + const ipBG = group.find((element) => element.get('name') === 'ip-cp-bg'); + const ipIcon = group.find((element) => element.get('name') === 'ip-cp-icon'); + const ipCPBox = group.find((element) => element.get('name') === 'ip-cp-box'); + + const onMouseEnter = () => { + ipLine.attr('opacity', 1); + ipBG.attr('opacity', 1); + ipIcon.attr('opacity', 1); + graph.get('canvas').draw(); + }; + const onMouseLeave = () => { + ipLine.attr('opacity', 0); + ipBG.attr('opacity', 0); + ipIcon.attr('opacity', 0); + graph.get('canvas').draw(); + }; + ipBox.on('mouseenter', () => { + onMouseEnter(); + }); + ipBox.on('mouseleave', () => { + onMouseLeave(); + }); + ipCPBox.on('mouseenter', () => { + onMouseEnter(); + }); + ipCPBox.on('mouseleave', () => { + onMouseLeave(); + }); + ipCPBox.on('click', () => { }); + } + }, + setState: (name, value, item) => { + const hasOpacityClass = [ + 'ip-cp-line', + 'ip-cp-bg', + 'ip-cp-icon', + 'ip-cp-box', + 'ip-box', + 'collapse-icon-bg', + ]; + const group = item.getContainer(); + const childrens = group.get('children'); + graph.setAutoPaint(false); + if (name === 'emptiness') { + if (value) { + childrens.forEach((shape) => { + if (hasOpacityClass.indexOf(shape.get('name')) > -1) { + return; + } + shape.attr('opacity', 0.4); + }); + } else { + childrens.forEach((shape) => { + if (hasOpacityClass.indexOf(shape.get('name')) > -1) { + return; + } + shape.attr('opacity', 1); + }); + } + } + graph.setAutoPaint(true); + }, +}; + +G6.registerNode('card-node', { + draw: (cfg, group) => { + const config = getNodeConfig(cfg); + const isRoot = cfg.dataType === 'root'; + const nodeError = cfg.nodeError; + /* the biggest rect */ + const container = nodeBasicMethod.createNodeBox(group, config, 243, 64, isRoot); + + if (cfg.dataType !== 'root') { + /* the type text */ + group.addShape('text', { + attrs: { + text: cfg.dataType, + x: 3, + y: -10, + fontSize: 12, + textAlign: 'left', + textBaseline: 'middle', + fill: 'rgba(0,0,0,0.65)', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'type-text-shape', + }); + } + + if (cfg.ip) { + /* ip start */ + /* ipBox */ + const ipRect = group.addShape('rect', { + attrs: { + fill: nodeError ? null : '#FFF', + stroke: nodeError ? 'rgba(255,255,255,0.65)' : null, + radius: 2, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'ip-container-shape', + }); + + /* ip */ + const ipText = group.addShape('text', { + attrs: { + text: cfg.ip, + x: 0, + y: 19, + fontSize: 12, + textAlign: 'left', + textBaseline: 'middle', + fill: nodeError ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.65)', + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'ip-text-shape', + }); + + const ipBBox = ipText.getBBox(); + /* the distance from the IP to the right is 12px */ + ipText.attr({ + x: 224 - 12 - ipBBox.width, + }); + /* ipBox */ + ipRect.attr({ + x: 224 - 12 - ipBBox.width - 4, + y: ipBBox.minY - 5, + width: ipBBox.width + 8, + height: ipBBox.height + 10, + }); + + /* a transparent shape on the IP for click listener */ + group.addShape('rect', { + attrs: { + stroke: '', + cursor: 'pointer', + x: 224 - 12 - ipBBox.width - 4, + y: ipBBox.minY - 5, + width: ipBBox.width + 8, + height: ipBBox.height + 10, + fill: '#fff', + opacity: 0, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'ip-box', + }); + + /* copyIpLine */ + group.addShape('rect', { + attrs: { + x: 194, + y: 7, + width: 1, + height: 24, + fill: '#E3E6E8', + opacity: 0, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'ip-cp-line', + }); + /* copyIpBG */ + group.addShape('rect', { + attrs: { + x: 195, + y: 8, + width: 22, + height: 22, + fill: '#FFF', + cursor: 'pointer', + opacity: 0, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'ip-cp-bg', + }); + /* copyIpIcon */ + group.addShape('image', { + attrs: { + x: 200, + y: 13, + height: 12, + width: 10, + img: 'https://os.alipayobjects.com/rmsportal/DFhnQEhHyPjSGYW.png', + cursor: 'pointer', + opacity: 0, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'ip-cp-icon', + }); + /* a transparent rect on the icon area for click listener */ + group.addShape('rect', { + attrs: { + x: 195, + y: 8, + width: 22, + height: 22, + fill: '#FFF', + cursor: 'pointer', + opacity: 0, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'ip-cp-box', + tooltip: 'Copy the IP', + }); + + /* ip end */ + } + + /* name */ + group.addShape('text', { + attrs: { + text: cfg.name, + x: 19, + y: 19, + fontSize: 14, + fontWeight: 700, + textAlign: 'left', + textBaseline: 'middle', + fill: config.fontColor, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'name-text-shape', + }); + + /* the description text */ + group.addShape('text', { + attrs: { + text: cfg.keyInfo, + x: 19, + y: 45, + fontSize: 14, + textAlign: 'left', + textBaseline: 'middle', + fill: config.fontColor, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'bottom-text-shape', + }); + + if (nodeError) { + group.addShape('text', { + attrs: { + x: 191, + y: 62, + text: '⚠️', + fill: '#000', + fontSize: 18, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'error-text-shape', + }); + } + + const hasChildren = cfg.children && cfg.children.length > 0; + if (hasChildren) { + nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 236, 32); + } + return container; + }, + afterDraw: nodeBasicMethod.afterDraw, + setState: nodeBasicMethod.setState, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-node'], + }, + defaultNode: { + type: 'card-node', + }, +}); + +const data = { + nodes: [ + { + name: 'cardNodeApp', + ip: '127.0.0.1', + nodeError: true, + dataType: 'root', + keyInfo: 'this is a card node info', + x: 100, + y: 50, + }, + { + name: 'cardNodeApp', + ip: '127.0.0.1', + nodeError: false, + dataType: 'subRoot', + keyInfo: 'this is sub root', + x: 100, + y: 150, + }, + { + name: 'cardNodeApp', + ip: '127.0.0.1', + nodeError: false, + dataType: 'subRoot', + keyInfo: 'this is sub root', + x: 100, + y: 250, + children: [ + { + name: 'sub', + }, + ], + }, + ], + edges: [], +}; + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/cardNode.js b/packages/site/examples/item/customNode/demo/cardNode.js new file mode 100644 index 0000000000..d0ab1a3745 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/cardNode.js @@ -0,0 +1,196 @@ +import G6 from '@antv/g6'; + +const ICON_MAP = { + a: 'https://gw.alipayobjects.com/mdn/rms_8fd2eb/afts/img/A*0HC-SawWYUoAAAAAAAAAAABkARQnAQ', + b: 'https://gw.alipayobjects.com/mdn/rms_8fd2eb/afts/img/A*sxK0RJ1UhNkAAAAAAAAAAABkARQnAQ', +}; + +G6.registerNode( + 'card-node', + { + drawShape: function drawShape(cfg, group) { + const color = cfg.error ? '#F4664A' : '#30BF78'; + const r = 2; + const shape = group.addShape('rect', { + attrs: { + x: 0, + y: 0, + width: 200, + height: 60, + stroke: color, + radius: r, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'main-box', + draggable: true, + }); + + group.addShape('rect', { + attrs: { + x: 0, + y: 0, + width: 200, + height: 20, + fill: color, + radius: [r, r, 0, 0], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'title-box', + draggable: true, + }); + + // left icon + group.addShape('image', { + attrs: { + x: 4, + y: 2, + height: 16, + width: 16, + cursor: 'pointer', + img: ICON_MAP[cfg.nodeType || 'app'], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'node-icon', + }); + + // title text + group.addShape('text', { + attrs: { + textBaseline: 'top', + y: 2, + x: 24, + lineHeight: 20, + text: cfg.title, + fill: '#fff', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'title', + }); + + if (cfg.nodeLevel > 0) { + group.addShape('marker', { + attrs: { + x: 184, + y: 30, + r: 6, + cursor: 'pointer', + symbol: cfg.collapse ? G6.Marker.expand : G6.Marker.collapse, + stroke: '#666', + lineWidth: 1, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'collapse-icon', + }); + } + + // The content list + cfg.panels.forEach((item, index) => { + // name text + group.addShape('text', { + attrs: { + textBaseline: 'top', + y: 25, + x: 24 + index * 60, + lineHeight: 20, + text: item.title, + fill: 'rgba(0,0,0, 0.4)', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `index-title-${index}`, + }); + + // value text + group.addShape('text', { + attrs: { + textBaseline: 'top', + y: 42, + x: 24 + index * 60, + lineHeight: 20, + text: item.value, + fill: '#595959', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `index-title-${index}`, + }); + }); + return shape; + }, + }, + 'single-node', +); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + defaultNode: { + type: 'card-node', + }, + fitView: true, +}); + +const data = { + nodes: [ + { + title: 'node1', + error: true, + nodeType: 'a', + id: 'node1', + nodeLevel: 2, + panels: [ + { title: '成功率', value: '11%' }, + { title: '耗时', value: '111' }, + { title: '错误数', value: '111' }, + ], + x: 100, + y: 100, + }, + { + title: 'node2', + error: false, + nodeType: 'b', + id: 'node2', + nodeLevel: 0, + panels: [ + { title: '成功率', value: '11%' }, + { title: '耗时', value: '111' }, + { title: '错误数', value: '111' }, + ], + x: 100, + y: 200, + }, + { + title: 'node3', + error: false, + nodeType: 'a', + id: 'node3', + nodeLevel: 3, + panels: [ + { title: '成功率', value: '11%' }, + { title: '耗时', value: '111' }, + { title: '错误数', value: '111' }, + ], + collapse: true, + x: 100, + y: 300, + }, + ], +}; + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/intervalChartNode.js b/packages/site/examples/item/customNode/demo/intervalChartNode.js new file mode 100644 index 0000000000..c1b1582cb9 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/intervalChartNode.js @@ -0,0 +1,193 @@ +import G6 from '@antv/g6'; +import Chart from '@antv/chart-node-g6'; + +G6.registerNode( + 'node-with-interval', + { + draw(cfg, group) { + const keyShape = group.addShape('rect', { + attrs: { + x: 0, + y: 0, + width: 400, + height: 200, + fill: cfg.style.fill, + }, + }); + + group.addShape('rect', { + attrs: { + x: 0, + y: 0, + width: 400, + height: 40, + fill: '#69c0ff', + }, + }); + + group.addShape('text', { + attrs: { + text: '浏览申请完成率', + x: 10, + y: 25, + fontSize: 14, + fill: '#fff', + }, + }); + + group.addShape('text', { + attrs: { + text: '2020-06-07 ~ 2020-06-14 | 均值', + x: 20, + y: 70, + fontSize: 13, + fill: '#8c8c8c', + }, + }); + + group.addShape('text', { + attrs: { + text: '8.8%', + x: 20, + y: 110, + fontSize: 30, + fill: '#000', + }, + }); + + // 实际开发中把 (Chart || window.Chart) 换成 Chart + // Replace (Chart || window.Chart) by Chart in your project + const view = new (Chart || window.Chart)({ + group, + padding: 1, + width: 360, + height: 70, + x: 20, + y: 100, + }); + + view.data(cfg.trendData); + + view.interval().position('genre*sold').color('genre'); + + view.legend('genre', false); + + view.scale({ + genre: { + alias: '游戏种类', // 列定义,定义该属性显示的别名 + }, + sold: { + alias: '销售量', + }, + }); + + view.axis('sold', false); + + // 极坐标下的柱状图 + // view.coordinate('polar'); + + view.render(); + + keyShape.set('intervalView', view); + + return keyShape; + }, + update(cfg, item) { + const keyShape = item.getKeyShape(); + const view = keyShape.get('intervalView'); + view.changeData(cfg.trendData); + }, + }, + 'single-node', +); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const trendData = [ + { genre: 'Sports', sold: 275 }, + { genre: 'Strategy', sold: 115 }, + { genre: 'Action', sold: 120 }, + { genre: 'Shooter', sold: 350 }, + { genre: 'Other', sold: 150 }, +]; + +const data = { + nodes: [ + { + id: 'node0', + trendData, + x: 10, + y: 100, + }, + { + id: 'node1', + trendData, + x: 550, + y: 100, + }, + ], + edges: [{ source: 'node0', target: 'node1' }], +}; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + linkCenter: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + defaultNode: { + type: 'node-with-interval', + style: { + fill: '#e6f7ff', + }, + }, + nodeStateStyles: { + hover: { + stroke: '#b37feb', + }, + }, + defaultEdge: { + style: { + lineWidth: 5, + stroke: '#666', + }, + }, +}); + +graph.data(data); +graph.render(); + +// 点击节点,更新柱状图数据 +graph.on('node:click', (evt) => { + const newTrendData = [ + { genre: 'Sports', sold: 75 }, + { genre: 'Strategy', sold: 115 }, + { genre: 'Action', sold: 320 }, + { genre: 'Shooter', sold: 150 }, + { genre: 'Other', sold: 250 }, + ]; + + graph.updateItem(evt.item, { + trendData: newTrendData, + }); +}); + +graph.on('node:mouseenter', (evt) => { + graph.setItemState(evt.item, 'hover', true); +}); + +graph.on('node:mouseleave', (evt) => { + graph.setItemState(evt.item, 'hover', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/jsxNode.js b/packages/site/examples/item/customNode/demo/jsxNode.js new file mode 100644 index 0000000000..7b1de1f360 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/jsxNode.js @@ -0,0 +1,104 @@ +import G6 from '@antv/g6'; + +/** + * Custom a JSX node + * by Dominic Ming + * + */ + +G6.registerNode( + 'rect-jsx', + (cfg) => ` + + + + {{label}} + + + 描述: {{description}} + 创建者: {{meta.creatorName}} + + + + + + `, +); + +const data = { + nodes: [ + { + x: 150, + y: 150, + description: 'ant_type_name_...', + label: 'Type / ReferType', + color: '#2196f3', + meta: { + creatorName: 'a_creator', + }, + id: 'node1', + type: 'rect-jsx', + }, + { + x: 350, + y: 150, + description: 'node2_name...', + label: 'JSX Node', + color: '#2196f3', + meta: { + creatorName: 'a_creator', + }, + id: 'node2', + type: 'rect-jsx', + }, + ], + edges: [{ source: 'node1', target: 'node2' }], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-node', 'zoom-canvas'], + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/jsxNodeWithAnimate.js b/packages/site/examples/item/customNode/demo/jsxNodeWithAnimate.js new file mode 100644 index 0000000000..d9d9256c58 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/jsxNodeWithAnimate.js @@ -0,0 +1,108 @@ +import G6 from '@antv/g6'; + +/** + * Custom a xml node + * by Dominic Ming + * + */ + +G6.registerNode('rect-xml', { + jsx: (cfg) => ` + + + + {{label}} + + + 描述: {{description}} + 创建者: {{meta.creatorName}} + + + + + + + `, + afterDraw: (cfg, group) => { + console.log(group); + const img = group.findAllByName('img'); + if (img[0]) { + img[0].animate( + (ratio) => { + return { + opacity: Math.abs(0.5 - ratio), + }; + }, + { + duration: 3000, + repeat: true, + }, + ); + } + }, +}); + +const data = { + nodes: [ + { + x: 150, + y: 150, + description: 'ant_type_name_...', + label: 'Type / ReferType', + color: '#2196f3', + meta: { + creatorName: 'a_creator', + }, + id: 'test', + type: 'rect-xml', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/lineChart.js b/packages/site/examples/item/customNode/demo/lineChart.js new file mode 100644 index 0000000000..1cff970676 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/lineChart.js @@ -0,0 +1,209 @@ +import G6 from '@antv/g6'; + +/** + * Custom a line chart node + * by Jingxi + * + */ + +// Custom a line chart node +G6.registerNode('circleLine', { + draw(cfg, group) { + const baseR = 30; + let nowAngle = 0; + + // Ref line + let refR = baseR; + const refInc = 10; + for (let i = 0; i < 5; i++) { + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: (refR += refInc), + stroke: '#bae7ff', + lineDash: [4, 4], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + } + + const everyIncAngle = (2 * Math.PI * (360 / 5 / 5)) / 360; + cfg.details.forEach((cat) => { + // 计算一系列点的位置 + const postions = []; + cat.values.forEach((item, index) => { + const r = baseR + item; + const xPos = r * Math.cos(nowAngle); + const yPos = r * Math.sin(nowAngle); + nowAngle += everyIncAngle; + postions.push([xPos, yPos]); + if (index === 4) { + const r = baseR + item; + const xPos = r * Math.cos(nowAngle); + const yPos = r * Math.sin(nowAngle); + postions.push([xPos, yPos]); + } + }); + const pathArrayL = postions.map((item) => ['L', ...item]); + // add the connecting line + group.addShape('path', { + attrs: { + path: [ + ['M', 0, 0], // the top vertex + ...pathArrayL, + ['Z'], // close the path + ], + stroke: cat.color, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + + postions.forEach((pos, index) => { + if (index !== 5) { + const littleCircle = group.addShape('circle', { + // attrs: style + attrs: { + x: pos[0], + y: pos[1], + r: 2, + fill: 'black', + stroke: cat.color, + cursor: 'pointer', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + // 加上交互动画 + littleCircle.on('mouseenter', function () { + littleCircle.animate( + { + r: 5, + }, + { + repeat: false, + duration: 200, + }, + ); + }); + littleCircle.on('mouseleave', function () { + littleCircle.animate( + { + r: 2, + }, + { + repeat: false, + duration: 200, + }, + ); + }); + // set the name + littleCircle.set('name', 'littleCircle'); + } + }); + }); + + // add a circle with the same color with the background color + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: baseR, + fill: cfg.centerColor, + stroke: 'darkgray', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + if (cfg.label) { + group.addShape('text', { + attrs: { + x: 0, // 居中 + y: 0, + textAlign: 'center', + textBaseline: 'middle', + text: cfg.label, + fill: 'white', + fontStyle: 'bold', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', + }); + } + return group; + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, +}); + +const data = { + nodes: [ + { + id: 'nodeB', + x: 150, + y: 150, + label: 'Line1', + type: 'circleLine', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + details: [ + { cat: 'pv', values: [20, 30, 40, 30, 30], color: '#5B8FF9' }, + { cat: 'dal', values: [40, 30, 20, 30, 50], color: '#5AD8A6' }, + { cat: 'uv', values: [40, 30, 30, 40, 40], color: '#5D7092' }, + { cat: 'sal', values: [20, 30, 50, 20, 20], color: '#F6BD16' }, + { cat: 'cal', values: [10, 10, 20, 20, 20], color: '#E8684A' }, + ], + centerColor: '#5b8ff9', + }, + { + id: 'nodeB2', + x: 500, + y: 150, + label: 'Line2', + type: 'circleLine', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + details: [ + { cat: 'pv', values: [10, 10, 50, 20, 10], color: '#5ad8a6' }, + { cat: 'dal', values: [20, 30, 10, 50, 40], color: '#ff99c3' }, + { cat: 'uv', values: [10, 50, 30, 20, 30], color: '#6dc8ec' }, + { cat: 'sal', values: [50, 30, 20, 20, 20], color: '#269a99' }, + { cat: 'cal', values: [50, 10, 20, 50, 30], color: '#9270CA' }, + ], + centerColor: '#5b8ff9', + }, + ], + edges: [ + { + source: 'nodeB', + target: 'nodeB2', + }, + ], +}; + +// graph.get('container').style.background = '#5d7092'; + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/lineChartNode.js b/packages/site/examples/item/customNode/demo/lineChartNode.js new file mode 100644 index 0000000000..78e3a21207 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/lineChartNode.js @@ -0,0 +1,160 @@ +import G6 from '@antv/g6'; +import Chart from '@antv/chart-node-g6'; + +G6.registerNode( + 'node-with-line', + { + draw(cfg, group) { + const keyShape = group.addShape('rect', { + attrs: { + x: 0, + y: 0, + width: 400, + height: 200, + fill: cfg.style.fill, + }, + }); + + group.addShape('rect', { + attrs: { + x: 0, + y: 0, + width: 400, + height: 40, + fill: '#69c0ff', + }, + }); + + group.addShape('text', { + attrs: { + text: '浏览申请完成率', + x: 10, + y: 25, + fontSize: 14, + fill: '#fff', + }, + }); + + group.addShape('text', { + attrs: { + text: '2020-06-07 ~ 2020-06-14 | 均值', + x: 20, + y: 70, + fontSize: 13, + fill: '#8c8c8c', + }, + }); + + group.addShape('text', { + attrs: { + text: '8.8%', + x: 20, + y: 110, + fontSize: 30, + fill: '#000', + }, + }); + + // 实际开发中把 (Chart || window.Chart) 换成 Chart + // Replace (Chart || window.Chart) by Chart in your project + const view = new (Chart || window.Chart)({ + group, + padding: 5, + width: 360, + height: 70, + x: 20, + y: 100, + }); + + view.data(cfg.trendData); + + view.line().position('genre*sold').color('#9AD681').shape('dash'); + + view.legend('genre', false); + + view.axis('sold', false); + + view.render(); + + return keyShape; + }, + update: null, + }, + 'single-node', +); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const trendData = [ + { genre: 'Sports', sold: 275 }, + { genre: 'Strategy', sold: 115 }, + { genre: 'Action', sold: 120 }, + { genre: 'Shooter', sold: 350 }, + { genre: 'Other', sold: 150 }, +]; + +const data = { + nodes: [ + { + id: 'node0', + trendData, + x: 10, + y: 100, + }, + { + id: 'node1', + trendData, + x: 550, + y: 100, + }, + ], + edges: [{ source: 'node0', target: 'node1' }], +}; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + linkCenter: true, + modes: { + default: ['zoom-canvas', 'drag-canvas', 'drag-node'], + }, + defaultNode: { + type: 'node-with-line', + style: { + fill: '#e6f7ff', + }, + }, + nodeStateStyles: { + hover: { + stroke: '#b37feb', + }, + }, + defaultEdge: { + style: { + lineWidth: 5, + stroke: '#666', + }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + graph.setItemState(evt.item, 'hover', true); +}); + +graph.on('node:mouseleave', (evt) => { + graph.setItemState(evt.item, 'hover', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/list.js b/packages/site/examples/item/customNode/demo/list.js new file mode 100644 index 0000000000..a051faf608 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/list.js @@ -0,0 +1,232 @@ +import G6 from '@antv/g6'; + +/** + * 该案例演示以下功能: + * 1、使用G6如何自定义列表节点; + * 2、列表节点中如何包括详情信息; + * 3、点击节点,可展开详情列表信息。 + * + */ +G6.registerNode('expandNode', { + draw: function draw(cfg, group) { + const mainGroup = group.addGroup({ + id: 'main-group', + }); + const keyShape = mainGroup.addShape('rect', { + attrs: { + x: 0, + y: 0, + width: 100 + 60 * cfg.values.length, + height: 50, + fill: '#C6E5FF', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'key-rect-shape', + }); + + // name text + mainGroup.addShape('text', { + attrs: { + text: cfg.name, + fill: '#000', + width: 130, + x: 10, + y: 32, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'name-text-shape', + }); + + const subGroup = group.addGroup({ + id: 'sub-group', + }); + cfg.values.forEach(function (data, index) { + subGroup.addShape('rect', { + attrs: { + x: 110 + index * 60, + y: 0, + width: 50, + height: 50, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', + }); + + subGroup.addShape('text', { + attrs: { + text: data.key, + fill: '#000', + x: 130 + index * 60, + y: 20, + fontSize: 10, + textBaseline: 'middle', + className: 'sub-group-text', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'sub-text-shape1', + }); + + subGroup.addShape('text', { + attrs: { + text: data.value, + fill: '#000', + x: 130 + index * 60, + y: 30, + fontSize: 10, + textBaseline: 'middle', + textAlign: 'left', + className: 'sub-group-text', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'name-text-shape2', + }); + }); + + const listGroup = group.addGroup({ + id: 'detail-list-group', + }); + + listGroup.addShape('rect', { + attrs: { + width: 100 + 60 * cfg.values.length - 70, + height: 30 * cfg.properties.length + 20, + fill: '#fff', + x: 70, + y: 30, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'list-rect-shape1', + }); + + const rectWidth = 100 + 60 * cfg.values.length - 80; + cfg.properties.forEach(function (property, index) { + listGroup.addShape('rect', { + attrs: { + width: rectWidth, + height: 30, + fill: '#9EC9FF', + x: 80, + y: 40 * index + 40, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'list-rect-shape2', + }); + let count = 0; + for (const p in property) { + // 每个rect中添加5个文本 + listGroup.addShape('text', { + attrs: { + text: property[p], + fill: '#000', + x: 85 + count * (rectWidth / cfg.values.length) - count * 10, + y: 40 * index + 40 + 15, + fontSize: 10, + textBaseline: 'middle', + textAlign: 'left', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', + }); + count++; + } + }); + listGroup.hide(); + return keyShape; + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas'], + }, + fitView: true, +}); + +const data = { + nodes: [ + { + id: 'shape2', + x: 0, + y: 0, + type: 'expandNode', + name: '网站引流', + values: [ + { + key: '曝光率', + value: '19.09', + }, + { + key: '流入UV', + value: '910', + }, + { + key: '点击率', + value: '90', + }, + { + key: '占比', + value: '90', + }, + ], + properties: [ + { + name: '搜索', + value1: '102', + value2: '102', + value3: '102', + value4: '102', + }, + { + name: '扫一扫', + value1: '102', + value2: '102', + value3: '102', + value4: '102', + }, + ], + }, + ], +}; +graph.data(data); +graph.render(); + +// 点击node,展开详情 +graph.on('node:click', function (evt) { + const target = evt.target; + + const parentGroup = target.get('parent').get('parent'); + const detailGroup = parentGroup.findById('detail-list-group'); + // 将sub-group中的内容网上移动一段距离 + const subGroup = parentGroup.findById('sub-group'); + const keyTexts = subGroup.findAll(function (item) { + return item.attr('className') === 'sub-group-text'; + }); + const isVisible = detailGroup.get('visible'); + if (isVisible) { + detailGroup.hide(); + keyTexts.forEach(function (text) { + const top = text.attr('y'); + text.attr('y', top + 10); + }); + } else { + keyTexts.forEach(function (text) { + const top = text.attr('y'); + text.attr('y', top - 10); + }); + detailGroup.show(); + } + graph.paint(); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/meta.json b/packages/site/examples/item/customNode/demo/meta.json new file mode 100644 index 0000000000..3332b5bfc4 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/meta.json @@ -0,0 +1,144 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "card.js", + "title": { + "zh": "卡片", + "en": "Card" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*3cRGRb5nB_UAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "cardNode.js", + "title": { + "zh": "卡片 2", + "en": "Card 2" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*b-g0RoOpI3sAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "jsxNode.js", + "title": { + "zh": "使用 JSX 自定义节点", + "en": "Custom Node with JSX" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*NcHWTKo3sEoAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "jsxNodeWithAnimate.js", + "title": { + "zh": "使用 JSX 自定义节点(带动画)", + "en": "Custom Node with JSX and animation" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*XfMbSZSrlREAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "lineChartNode.js", + "title": { + "zh": "节点中嵌入 G2 的折线图", + "en": "G2 Line Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ZmavSL0dRJIAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "intervalChartNode.js", + "title": { + "zh": "节点中嵌入 G2 的柱状图,点击节点可切换数据", + "en": "G2 Interval Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*D00uR5wb__kAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "pointChartNode.js", + "title": { + "zh": "节点中嵌入 G2 的散点图", + "en": "G2 Point Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*K7r7S7x-YhcAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "pieChartNode.js", + "title": { + "zh": "节点中嵌入 G2 的饼图", + "en": "G2 Pie Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wCxXSK4eWPoAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "svgDom.js", + "title": { + "zh": "DOM 节点", + "en": "Dom Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*VgQlQK1MdbIAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "areaChart.js", + "title": { + "zh": "使用 G 自定义的面积图节点", + "en": "Area Chart Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*YWuhR5ZjZfkAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "stackChart.js", + "title": { + "zh": "使用 G 自定义的堆叠图节点", + "en": "Stacked Chart Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*giZSRafSctgAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "lineChart.js", + "title": { + "zh": "使用 G 自定义的折线图节点", + "en": "Line Chart Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*mKmaTIBtVAUAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "barChart.js", + "title": { + "zh": "使用 G 自定义的南丁格尔图节点", + "en": "Nightingale Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*lX3gRZWvbp4AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "pointChart.js", + "title": { + "zh": "使用 G 自定义的点线图节点", + "en": "Point Chart Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*M0ycSarMV2sAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "pieChart.js", + "title": { + "zh": "使用 G 自定义的饼图节点", + "en": "Pie Chart Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*idENQpnwA1sAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "list.js", + "title": { + "zh": "列表", + "en": "List" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*pxKjQ5dZx80AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "scrollNode.js", + "title": { + "zh": "节点内容可滚动", + "en": "Scrollable Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*n-ipTrTaPD4AAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/item/customNode/demo/pieChart.js b/packages/site/examples/item/customNode/demo/pieChart.js new file mode 100644 index 0000000000..15ead6fda9 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/pieChart.js @@ -0,0 +1,108 @@ +import G6 from '@antv/g6'; + +/** + * Custom a pie chart node + * by Shiwu + * + */ +const lightBlue = '#5b8ff9'; +const lightOrange = '#5ad8a6'; + +// register a pie chart node +G6.registerNode('pie-node', { + draw: (cfg, group) => { + const radius = cfg.size / 2; // node radius + const inPercentage = cfg.inDegree / cfg.degree; // the ratio of indegree to outdegree + const inAngle = inPercentage * Math.PI * 2; // the anble for the indegree fan + const inArcEnd = [radius * Math.cos(inAngle), -radius * Math.sin(inAngle)]; // the end position for the in-degree fan + let isInBigArc = 0, + isOutBigArc = 1; + if (inAngle > Math.PI) { + isInBigArc = 1; + isOutBigArc = 0; + } + // fan shape for the in degree + const fanIn = group.addShape('path', { + attrs: { + path: [ + ['M', radius, 0], + ['A', radius, radius, 0, isInBigArc, 0, inArcEnd[0], inArcEnd[1]], + ['L', 0, 0], + ['Z'], + ], + lineWidth: 0, + fill: lightOrange, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'in-fan-shape', + }); + // draw the fan shape + group.addShape('path', { + attrs: { + path: [ + ['M', inArcEnd[0], inArcEnd[1]], + ['A', radius, radius, 0, isOutBigArc, 0, radius, 0], + ['L', 0, 0], + ['Z'], + ], + lineWidth: 0, + fill: lightBlue, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'out-fan-shape', + }); + // 返回 keyshape + return fanIn; + }, +}); + +const data = { + nodes: [ + { + id: 'pie1', + size: 80, + inDegree: 80, + degree: 360, + x: 150, + y: 150, + }, + { + id: 'pie2', + size: 80, + inDegree: 280, + degree: 360, + x: 350, + y: 150, + }, + ], + edges: [ + { + source: 'pie1', + target: 'pie2', + }, + ], +}; +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + linkCenter: true, + defaultNode: { + type: 'pie-node', + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/pieChartNode.js b/packages/site/examples/item/customNode/demo/pieChartNode.js new file mode 100644 index 0000000000..12a2b65c5b --- /dev/null +++ b/packages/site/examples/item/customNode/demo/pieChartNode.js @@ -0,0 +1,161 @@ +import G6 from '@antv/g6'; +import Chart from '@antv/chart-node-g6'; + +G6.registerNode( + 'node-with-pie', + { + draw(cfg, group) { + const keyShape = group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: 100, + fill: cfg.style.fill, + stroke: cfg.style.stroke, + fillOpacity: 0, + }, + }); + + // 实际开发中把 (Chart || window.Chart) 换成 Chart + // Replace (Chart || window.Chart) by Chart in your project + const view = new (Chart || window.Chart)({ + group, + width: 400, + height: 200, + x: -200, + y: -100, + }); + + view.data(cfg.trendData); + + view + .interval() + .position('year*population') + .label('year', { + offset: -15, + }) + .color('year') + .style({ + lineWidth: 1, + stroke: '#95de64', + fontSize: 8, + }); + + view.axis(false); + + view.legend(false); + + // 极坐标下的柱状图 + view.coordinate('polar'); + + view.render(); + + keyShape.set('intervalView', view); + + return keyShape; + }, + update(cfg, item) { + const keyShape = item.getKeyShape(); + const view = keyShape.get('intervalView'); + view.changeData(cfg.trendData); + }, + }, + 'single-node', +); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const trendData1 = [ + { year: '2001', population: 41.8 }, + { year: '2002', population: 38 }, + { year: '2003', population: 33.7 }, + { year: '2004', population: 30.7 }, + { year: '2005', population: 25.8 }, + { year: '2006', population: 31.7 }, + { year: '2007', population: 33 }, + { year: '2008', population: 46 }, + { year: '2009', population: 38.3 }, + { year: '2010', population: 28 }, + { year: '2011', population: 42.5 }, + { year: '2012', population: 30.3 }, +]; + +const trendData2 = [ + { year: '2001', population: 11.8 }, + { year: '2002', population: 28 }, + { year: '2003', population: 43.7 }, + { year: '2004', population: 50.7 }, + { year: '2005', population: 15.8 }, + { year: '2006', population: 21.7 }, + { year: '2007', population: 13 }, + { year: '2008', population: 26 }, + { year: '2009', population: 18.3 }, + { year: '2010', population: 58 }, + { year: '2011', population: 62.5 }, + { year: '2012', population: 10.3 }, +]; + +const data = { + nodes: [ + { + id: 'node0', + trendData: trendData1, + x: 10, + y: 100, + }, + { + id: 'node1', + trendData: trendData2, + x: 550, + y: 100, + }, + ], + edges: [{ source: 'node0', target: 'node1' }], +}; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + linkCenter: true, + modes: { + default: ['zoom-canvas', 'drag-canvas', 'drag-node'], + }, + defaultNode: { + type: 'node-with-pie', + style: { + fill: '#e6f7ff', + }, + }, + nodeStateStyles: { + hover: { + stroke: '#b37feb', + }, + }, + defaultEdge: { + style: { + lineWidth: 5, + stroke: '#666', + }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + graph.setItemState(evt.item, 'hover', true); +}); + +graph.on('node:mouseleave', (evt) => { + graph.setItemState(evt.item, 'hover', false); +}); + +window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); +}; diff --git a/packages/site/examples/item/customNode/demo/pointChart.js b/packages/site/examples/item/customNode/demo/pointChart.js new file mode 100644 index 0000000000..65ccb19970 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/pointChart.js @@ -0,0 +1,182 @@ +import G6 from '@antv/g6'; + +// Custom a mark node +G6.registerNode('justPoints', { + draw(cfg, group) { + const baseR = 30; + let nowAngle = 0; + + // Ref line + let refR = baseR; + const refInc = 10; + for (let i = 0; i < 5; i++) { + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: (refR += refInc), + stroke: '#5ad8a6', + lineDash: [4, 4], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + } + const everyIncAngle = (2 * Math.PI * (360 / 5 / 5)) / 360; + nowAngle = nowAngle + everyIncAngle / 2; + cfg.details.forEach((cat) => { + // Calculate the positions for vertexes + const postions = []; + cat.values.forEach((item, index) => { + const r = baseR + item; + const xPos = r * Math.cos(nowAngle); + const yPos = r * Math.sin(nowAngle); + nowAngle += everyIncAngle; + postions.push([xPos, yPos]); + if (index === 4) { + const r = baseR + item; + const xPos = r * Math.cos(nowAngle); + const yPos = r * Math.sin(nowAngle); + postions.push([xPos, yPos]); + } + }); + + // add marks + postions.forEach((pos, index) => { + if (index !== 5) { + group.addShape('circle', { + attrs: { + x: pos[0], + y: pos[1], + r: 3, + lineWidth: 2, + stroke: cat.color, + }, + name: 'circle-marker-shape', + }); + } + }); + }); + + let nowAngle2 = 0; + const everyIncAngleCat = (2 * Math.PI * (360 / 5)) / 360; + for (let i = 0; i < 5; i++) { + const r = 30 + 50; + const xPos = r * Math.cos(nowAngle2); + const yPos = r * Math.sin(nowAngle2); + + group.addShape('path', { + attrs: { + path: [ + ['M', 0, 0], + ['L', xPos, yPos], + ], + lineDash: [4, 4], + stroke: '#5ad8a6', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + }); + nowAngle2 += everyIncAngleCat; + } + // add a circle with the same color with the background color + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: baseR, + fill: cfg.centerColor, + stroke: 'darkgray', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + + if (cfg.label) { + group.addShape('text', { + attrs: { + x: 0, + y: 0, + textAlign: 'center', + textBaseline: 'middle', + text: cfg.label, + fill: '#fff', + fontStyle: 'bold', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', + }); + } + return group; + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, +}); + +const data = { + nodes: [ + { + id: 'nodeC', + x: 150, + y: 150, + label: 'Point2', + type: 'justPoints', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + details: [ + { cat: 'pv', values: [20, 30, 40, 30, 30], color: '#5B8FF9' }, + { cat: 'dal', values: [40, 30, 20, 30, 50], color: '#5AD8A6' }, + { cat: 'uv', values: [40, 30, 30, 40, 40], color: '#5D7092' }, + { cat: 'sal', values: [20, 30, 50, 20, 20], color: '#F6BD16' }, + { cat: 'cal', values: [10, 10, 20, 20, 20], color: '#E8684A' }, + ], + centerColor: '#5b8ff9', + }, + { + id: 'nodeC2', + x: 500, + y: 150, + label: 'Point2', + type: 'justPoints', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + details: [ + { cat: 'pv', values: [10, 10, 50, 20, 10], color: '#5ad8a6' }, + { cat: 'dal', values: [20, 30, 10, 50, 40], color: '#ff99c3' }, + { cat: 'uv', values: [10, 50, 30, 20, 30], color: '#6dc8ec' }, + { cat: 'sal', values: [50, 30, 20, 20, 20], color: '#269a99' }, + { cat: 'cal', values: [50, 10, 20, 50, 30], color: '#9270CA' }, + ], + centerColor: '#5b8ff9', + }, + ], + edges: [ + { + source: 'nodeC', + target: 'nodeC2', + }, + ], +}; + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/pointChartNode.js b/packages/site/examples/item/customNode/demo/pointChartNode.js new file mode 100644 index 0000000000..9a1c5bb8eb --- /dev/null +++ b/packages/site/examples/item/customNode/demo/pointChartNode.js @@ -0,0 +1,590 @@ +import G6 from '@antv/g6'; +import Chart from '@antv/chart-node-g6'; + +G6.registerNode( + 'node-with-point', + { + draw(cfg, group) { + const keyShape = group.addShape('rect', { + attrs: { + x: 0, + y: 0, + width: 400, + height: 200, + fill: cfg.style.fill, + }, + }); + + group.addShape('rect', { + attrs: { + x: 0, + y: 0, + width: 400, + height: 40, + fill: '#69c0ff', + }, + }); + + group.addShape('text', { + attrs: { + text: '浏览申请完成率', + x: 10, + y: 25, + fontSize: 14, + fill: '#fff', + }, + }); + + group.addShape('text', { + attrs: { + text: '2020-06-07 ~ 2020-06-14 | 均值', + x: 20, + y: 70, + fontSize: 13, + fill: '#8c8c8c', + }, + }); + + group.addShape('text', { + attrs: { + text: '8.8%', + x: 20, + y: 110, + fontSize: 30, + fill: '#000', + }, + }); + + const colorMap = { + Asia: '#1890FF', + Americas: '#2FC25B', + Europe: '#FACC14', + Oceania: '#223273', + }; + + // 实际开发中把 (Chart || window.Chart) 换成 Chart + // Replace (Chart || window.Chart) by Chart in your project + const chart = new (Chart || window.Chart)({ + group, + width: 360, + height: 70, + x: 20, + y: 100, + }); + + chart.data(cfg.trendData); + + chart + .point() + .position('GDP*LifeExpectancy') + .size('Population', [1, 15]) + .color('continent', (val) => { + return colorMap[val]; + }) + .shape('circle') + .tooltip('Country*Population*GDP*LifeExpectancy') + .style('continent', (val) => { + return { + lineWidth: 1, + strokeOpacity: 1, + fillOpacity: 0.3, + opacity: 0.65, + stroke: colorMap[val], + }; + }); + + chart.legend('Population', false); + chart.axis(false); + + chart.render(); + + return keyShape; + }, + update: null, + }, + 'single-node', +); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const trendData = [ + { + continent: 'Americas', + Country: 'Argentina', + LifeExpectancy: 75.32, + GDP: 12779.37964, + Population: 40301927, + }, + { + continent: 'Americas', + Country: 'Brazil', + LifeExpectancy: 72.39, + GDP: 9065.800825, + Population: 190010647, + }, + { + continent: 'Americas', + Country: 'Canada', + LifeExpectancy: 80.653, + GDP: 36319.23501, + Population: 33390141, + }, + { + continent: 'Americas', + Country: 'Chile', + LifeExpectancy: 78.553, + GDP: 13171.63885, + Population: 16284741, + }, + { + continent: 'Americas', + Country: 'Colombia', + LifeExpectancy: 72.889, + GDP: 7006.580419, + Population: 44227550, + }, + { + continent: 'Americas', + Country: 'Costa Rica', + LifeExpectancy: 78.782, + GDP: 9645.06142, + Population: 4133884, + }, + { + continent: 'Americas', + Country: 'Cuba', + LifeExpectancy: 78.273, + GDP: 8948.102923, + Population: 11416987, + }, + { + continent: 'Americas', + Country: 'Dominican Republic', + LifeExpectancy: 72.235, + GDP: 6025.374752, + Population: 9319622, + }, + { + continent: 'Americas', + Country: 'Ecuador', + LifeExpectancy: 74.994, + GDP: 6873.262326, + Population: 13755680, + }, + { + continent: 'Americas', + Country: 'El Salvador', + LifeExpectancy: 71.878, + GDP: 5728.353514, + Population: 6939688, + }, + { + continent: 'Americas', + Country: 'Guatemala', + LifeExpectancy: 70.259, + GDP: 5186.050003, + Population: 12572928, + }, + { + continent: 'Americas', + Country: 'Honduras', + LifeExpectancy: 70.198, + GDP: 3548.330846, + Population: 7483763, + }, + { + continent: 'Americas', + Country: 'Jamaica', + LifeExpectancy: 72.567, + GDP: 7320.880262, + Population: 2780132, + }, + { + continent: 'Americas', + Country: 'Mexico', + LifeExpectancy: 76.195, + GDP: 11977.57496, + Population: 108700891, + }, + { + continent: 'Americas', + Country: 'Nicaragua', + LifeExpectancy: 72.899, + GDP: 2749.320965, + Population: 5675356, + }, + { + continent: 'Americas', + Country: 'Panama', + LifeExpectancy: 75.537, + GDP: 9809.185636, + Population: 3242173, + }, + { + continent: 'Americas', + Country: 'Paraguay', + LifeExpectancy: 71.752, + GDP: 4172.838464, + Population: 6667147, + }, + { + continent: 'Americas', + Country: 'Peru', + LifeExpectancy: 71.421, + GDP: 7408.905561, + Population: 28674757, + }, + { + continent: 'Americas', + Country: 'Puerto Rico', + LifeExpectancy: 78.746, + GDP: 19328.70901, + Population: 3942491, + }, + { + continent: 'Americas', + Country: 'Trinidad and Tobago', + LifeExpectancy: 69.819, + GDP: 18008.50924, + Population: 1056608, + }, + { + continent: 'Americas', + Country: 'United States', + LifeExpectancy: 78.242, + GDP: 42951.65309, + Population: 301139947, + }, + { + continent: 'Americas', + Country: 'Uruguay', + LifeExpectancy: 76.384, + GDP: 10611.46299, + Population: 3447496, + }, + { + continent: 'Americas', + Country: 'Venezuela', + LifeExpectancy: 73.747, + GDP: 11415.80569, + Population: 26084662, + }, + { + continent: 'Asia', + Country: 'China', + LifeExpectancy: 72.961, + GDP: 4959.114854, + Population: 1318683096, + }, + { + continent: 'Asia', + Country: 'Hong Kong, China', + LifeExpectancy: 82.208, + GDP: 39724.97867, + Population: 6980412, + }, + { + continent: 'Asia', + Country: 'Japan', + LifeExpectancy: 82.603, + GDP: 31656.06806, + Population: 127467972, + }, + { + continent: 'Asia', + Country: 'Korea, Dem. Rep.', + LifeExpectancy: 67.297, + GDP: 1593.06548, + Population: 23301725, + }, + { + continent: 'Asia', + Country: 'Korea, Rep.', + LifeExpectancy: 78.623, + GDP: 23348.13973, + Population: 49044790, + }, + { + continent: 'Europe', + Country: 'Albania', + LifeExpectancy: 76.423, + GDP: 5937.029526, + Population: 3600523, + }, + { + continent: 'Europe', + Country: 'Austria', + LifeExpectancy: 79.829, + GDP: 36126.4927, + Population: 8199783, + }, + { + continent: 'Europe', + Country: 'Belgium', + LifeExpectancy: 79.441, + GDP: 33692.60508, + Population: 10392226, + }, + { + continent: 'Europe', + Country: 'Bosnia and Herzegovina', + LifeExpectancy: 74.852, + GDP: 7446.298803, + Population: 4552198, + }, + { + continent: 'Europe', + Country: 'Bulgaria', + LifeExpectancy: 73.005, + GDP: 10680.79282, + Population: 7322858, + }, + { + continent: 'Europe', + Country: 'Croatia', + LifeExpectancy: 75.748, + GDP: 14619.22272, + Population: 4493312, + }, + { + continent: 'Europe', + Country: 'Czech Republic', + LifeExpectancy: 76.486, + GDP: 22833.30851, + Population: 10228744, + }, + { + continent: 'Europe', + Country: 'Denmark', + LifeExpectancy: 78.332, + GDP: 35278.41874, + Population: 5468120, + }, + { + continent: 'Europe', + Country: 'Finland', + LifeExpectancy: 79.313, + GDP: 33207.0844, + Population: 5238460, + }, + { + continent: 'Europe', + Country: 'France', + LifeExpectancy: 80.657, + GDP: 30470.0167, + Population: 61083916, + }, + { + continent: 'Europe', + Country: 'Germany', + LifeExpectancy: 79.406, + GDP: 32170.37442, + Population: 82400996, + }, + { + continent: 'Europe', + Country: 'Greece', + LifeExpectancy: 79.483, + GDP: 27538.41188, + Population: 10706290, + }, + { + continent: 'Europe', + Country: 'Hungary', + LifeExpectancy: 73.338, + GDP: 18008.94444, + Population: 9956108, + }, + { + continent: 'Europe', + Country: 'Iceland', + LifeExpectancy: 81.757, + GDP: 36180.78919, + Population: 301931, + }, + { + continent: 'Europe', + Country: 'Ireland', + LifeExpectancy: 78.885, + GDP: 40675.99635, + Population: 4109086, + }, + { + continent: 'Europe', + Country: 'Italy', + LifeExpectancy: 80.546, + GDP: 28569.7197, + Population: 58147733, + }, + { + continent: 'Europe', + Country: 'Montenegro', + LifeExpectancy: 74.543, + GDP: 9253.896111, + Population: 684736, + }, + { + continent: 'Europe', + Country: 'Netherlands', + LifeExpectancy: 79.762, + GDP: 36797.93332, + Population: 16570613, + }, + { + continent: 'Europe', + Country: 'Norway', + LifeExpectancy: 80.196, + GDP: 49357.19017, + Population: 4627926, + }, + { + continent: 'Europe', + Country: 'Poland', + LifeExpectancy: 75.563, + GDP: 15389.92468, + Population: 38518241, + }, + { + continent: 'Europe', + Country: 'Portugal', + LifeExpectancy: 78.098, + GDP: 20509.64777, + Population: 10642836, + }, + { + continent: 'Europe', + Country: 'Romania', + LifeExpectancy: 72.476, + GDP: 10808.47561, + Population: 22276056, + }, + { + continent: 'Europe', + Country: 'Serbia', + LifeExpectancy: 74.002, + GDP: 9786.534714, + Population: 10150265, + }, + { + continent: 'Europe', + Country: 'Slovak Republic', + LifeExpectancy: 74.663, + GDP: 18678.31435, + Population: 5447502, + }, + { + continent: 'Europe', + Country: 'Slovenia', + LifeExpectancy: 77.926, + GDP: 25768.25759, + Population: 2009245, + }, + { + continent: 'Europe', + Country: 'Spain', + LifeExpectancy: 80.941, + GDP: 28821.0637, + Population: 40448191, + }, + { + continent: 'Europe', + Country: 'Sweden', + LifeExpectancy: 80.884, + GDP: 33859.74835, + Population: 9031088, + }, + { + continent: 'Europe', + Country: 'Switzerland', + LifeExpectancy: 81.701, + GDP: 37506.41907, + Population: 7554661, + }, + { + continent: 'Europe', + Country: 'Turkey', + LifeExpectancy: 71.777, + GDP: 8458.276384, + Population: 71158647, + }, + { + continent: 'Europe', + Country: 'United Kingdom', + LifeExpectancy: 79.425, + GDP: 33203.26128, + Population: 60776238, + }, + { + continent: 'Oceania', + Country: 'Australia', + LifeExpectancy: 81.235, + GDP: 34435.36744, + Population: 20434176, + }, + { + continent: 'Oceania', + Country: 'New Zealand', + LifeExpectancy: 80.204, + GDP: 25185.00911, + Population: 4115771, + }, +]; + +const data = { + nodes: [ + { + id: 'node0', + trendData, + x: 10, + y: 100, + }, + { + id: 'node1', + trendData, + x: 550, + y: 100, + }, + ], + edges: [{ source: 'node0', target: 'node1' }], +}; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + linkCenter: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + defaultNode: { + type: 'node-with-point', + style: { + fill: '#e6f7ff', + }, + }, + defaultEdge: { + style: { + stroke: '#666', + lineWidth: 5, + }, + }, + defaultEdge: { + style: { + lineWidth: 5, + stroke: '#666', + }, + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/scrollNode.js b/packages/site/examples/item/customNode/demo/scrollNode.js new file mode 100644 index 0000000000..532862e41c --- /dev/null +++ b/packages/site/examples/item/customNode/demo/scrollNode.js @@ -0,0 +1,668 @@ +import G6 from '@antv/g6'; + +const { + Util, + registerBehavior, + registerEdge, + registerNode +} = G6; + +const rawData = [{ + "id": "info", + "label": "Employee", + "attrs": [{ + "key": "id", + "type": "number(6)" + }, + { + "key": "key", + "type": "varchar(255)" + }, + { + "key": "gender", + "type": "enum(M, F)" + }, + { + "key": "birthday", + "type": "date" + }, + { + "key": "hometown", + "type": "varchar(255)" + }, + { + "key": "country", + "type": "varchar(255)" + }, + { + "key": "nation", + "type": "varchar(255)" + }, + { + "key": "jobId", + "type": "number(3)", + "relation": [{ + "key": "id", + "nodeId": "job" + }] + }, + { + "key": "phone", + "type": "varchar(255)" + }, + { + "key": "deptId", + "type": "number(6)", + "relation": [{ + "key": "id", + "nodeId": "dept" + }] + }, + { + "key": "startTime", + "type": "date" + }, + { + "key": "leaveTime", + "type": "date" + } + ] + }, + { + "id": "dept", + "label": "Department", + "attrs": [{ + "key": "id", + "type": "number(6)" + }, + { + "key": "title", + "type": "varchar(255)" + }, + { + "key": "desc", + "type": "text" + }, + { + "key": "parent", + "type": "number(6)", + "relation": [{ + "key": "id", + "nodeId": "dept" + }] + }, + { + "key": "manager", + "type": "number(6)" + } + ] + } +] + +const isInBBox = (point, bbox) => { + const { + x, + y + } = point; + const { + minX, + minY, + maxX, + maxY + } = bbox; + + return x < maxX && x > minX && y > minY && y < maxY; +}; + +const itemHeight = 20; +registerBehavior("dice-er-scroll", { + getDefaultCfg() { + return { + multiple: true, + }; + }, + getEvents() { + return { + itemHeight, + wheel: "scorll", + click: "click", + "node:mousemove": "move", + }; + }, + scorll(e) { + e.preventDefault(); + const { + graph + } = this; + const nodes = graph.getNodes().filter((n) => { + const bbox = n.getBBox(); + + return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox); + }); + + const x = e.deltaX || e.movementX; + let y = e.deltaY || e.movementY; + if (!y && navigator.userAgent.indexOf('Firefox') > -1) y = (-e.wheelDelta * 125) / 3 + + if (nodes) { + const edgesToUpdate = new Set(); + nodes.forEach((node) => { + const model = node.getModel(); + if (model.attrs.length < 2) { + return; + } + const idx = model.startIndex || 0; + let startX = model.startX || 0.5; + let startIndex = idx + y * 0.02; + startX -= x; + if (startIndex < 0) { + startIndex = 0; + } + if (startX > 0) { + startX = 0; + } + if (startIndex > model.attrs.length - 1) { + startIndex = model.attrs.length - 1; + } + graph.updateItem(node, { + startIndex, + startX, + }); + node.getEdges().forEach(edge => edgesToUpdate.add(edge)) + }); + // G6 update the related edges when graph.updateItem with a node according to the new properties + // here you need to update the related edges manualy since the new properties { startIndex, startX } for the nodes are custom, and cannot be recognized by G6 + edgesToUpdate.forEach(edge => edge.refresh()) + } + + + }, + click(e) { + const { + graph + } = this; + const item = e.item; + const shape = e.shape; + if (!item) { + return; + } + + if (shape.get("name") === "collapse") { + graph.updateItem(item, { + collapsed: true, + size: [300, 50], + }); + setTimeout(() => graph.layout(), 100); + } else if (shape.get("name") === "expand") { + graph.updateItem(item, { + collapsed: false, + size: [300, 80], + }); + setTimeout(() => graph.layout(), 100); + } + }, + move(e) { + const name = e.shape.get("name"); + const item = e.item; + + if (name && name.startsWith("item")) { + this.graph.updateItem(item, { + selectedIndex: Number(name.split("-")[1]), + }); + } else { + this.graph.updateItem(item, { + selectedIndex: NaN, + }); + } + }, +}); + +registerEdge("dice-er-edge", { + draw(cfg, group) { + const edge = group.cfg.item; + const sourceNode = edge.getSource().getModel(); + const targetNode = edge.getTarget().getModel(); + + const sourceIndex = sourceNode.attrs.findIndex( + (e) => e.key === cfg.sourceKey + ); + + const sourceStartIndex = sourceNode.startIndex || 0; + + let sourceY = 15; + + if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) { + sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * itemHeight; + sourceY = Math.min(sourceY, 80); + } + + const targetIndex = targetNode.attrs.findIndex( + (e) => e.key === cfg.targetKey + ); + + const targetStartIndex = targetNode.startIndex || 0; + + let targetY = 15; + + if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) { + targetY = (targetIndex - targetStartIndex + 0.5) * itemHeight + 30; + targetY = Math.min(targetY, 80); + } + + const startPoint = { + ...cfg.startPoint + }; + const endPoint = { + ...cfg.endPoint + }; + + startPoint.y = startPoint.y + sourceY; + endPoint.y = endPoint.y + targetY; + + let shape; + if (sourceNode.id !== targetNode.id) { + shape = group.addShape("path", { + attrs: { + stroke: "#5B8FF9", + path: [ + ["M", startPoint.x, startPoint.y], + [ + "C", + endPoint.x / 3 + (2 / 3) * startPoint.x, + startPoint.y, + endPoint.x / 3 + (2 / 3) * startPoint.x, + endPoint.y, + endPoint.x, + endPoint.y, + ], + ], + endArrow: true, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: "path-shape", + }); + } else if (!sourceNode.collapsed) { + let gap = Math.abs((startPoint.y - endPoint.y) / 3); + if (startPoint["index"] === 1) { + gap = -gap; + } + shape = group.addShape("path", { + attrs: { + stroke: "#5B8FF9", + path: [ + ["M", startPoint.x, startPoint.y], + [ + "C", + startPoint.x - gap, + startPoint.y, + startPoint.x - gap, + endPoint.y, + startPoint.x, + endPoint.y, + ], + ], + endArrow: true, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: "path-shape", + }); + } + + return shape; + }, + afterDraw(cfg, group) { + const labelCfg = cfg.labelCfg || {}; + const edge = group.cfg.item; + const sourceNode = edge.getSource().getModel(); + const targetNode = edge.getTarget().getModel(); + if (sourceNode.collapsed && targetNode.collapsed) { + return; + } + const path = group.find( + (element) => element.get("name") === "path-shape" + ); + + const labelStyle = Util.getLabelPosition(path, 0.5, 0, 0, true); + const label = group.addShape("text", { + attrs: { + ...labelStyle, + text: cfg.label || '', + fill: "#000", + textAlign: "center", + stroke: "#fff", + lineWidth: 1, + }, + }); + label.rotateAtStart(labelStyle.rotate); + }, +}); + +registerNode("dice-er-box", { + draw(cfg, group) { + const width = 250; + const height = 96; + const itemCount = 10; + const boxStyle = { + stroke: "#096DD9", + radius: 4, + }; + + const { + attrs = [], + startIndex = 0, + selectedIndex, + collapsed, + icon, + } = cfg; + const list = attrs; + const afterList = list.slice( + Math.floor(startIndex), + Math.floor(startIndex + itemCount - 1) + ); + const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30; + + group.addShape("rect", { + attrs: { + fill: boxStyle.stroke, + height: 30, + width, + radius: [boxStyle.radius, boxStyle.radius, 0, 0], + }, + draggable: true, + }); + + let fontLeft = 12; + + if (icon && icon.show !== false) { + group.addShape("image", { + attrs: { + x: 8, + y: 8, + height: 16, + width: 16, + ...icon, + }, + }); + fontLeft += 18; + } + + group.addShape("text", { + attrs: { + y: 22, + x: fontLeft, + fill: "#fff", + text: cfg.label, + fontSize: 12, + fontWeight: 500, + }, + }); + + group.addShape("rect", { + attrs: { + x: 0, + y: collapsed ? 30 : 80, + height: 15, + width, + fill: "#eee", + radius: [0, 0, boxStyle.radius, boxStyle.radius], + cursor: "pointer", + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: collapsed ? "expand" : "collapse", + }); + + group.addShape("text", { + attrs: { + x: width / 2 - 6, + y: (collapsed ? 30 : 80) + 12, + text: collapsed ? "+" : "-", + width, + fill: "#000", + radius: [0, 0, boxStyle.radius, boxStyle.radius], + cursor: "pointer", + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: collapsed ? "expand" : "collapse", + }); + + const keyshape = group.addShape("rect", { + attrs: { + x: 0, + y: 0, + width, + height: collapsed ? 45 : height, + ...boxStyle, + }, + draggable: true, + }); + + if (collapsed) { + return keyshape; + } + + const listContainer = group.addGroup({}); + listContainer.setClip({ + type: "rect", + attrs: { + x: -8, + y: 30, + width: width + 16, + height: 80 - 30, + }, + }); + listContainer.addShape({ + type: "rect", + attrs: { + x: 1, + y: 30, + width: width - 2, + height: 80 - 30, + fill: "#fff", + }, + draggable: true, + }); + + if (list.length > itemCount) { + const barStyle = { + width: 4, + padding: 0, + boxStyle: { + stroke: "#00000022", + }, + innerStyle: { + fill: "#00000022", + }, + }; + + listContainer.addShape("rect", { + attrs: { + y: 30, + x: width - barStyle.padding - barStyle.width, + width: barStyle.width, + height: height - 30, + ...barStyle.boxStyle, + }, + }); + + const indexHeight = + afterList.length > itemCount ? + (afterList.length / list.length) * height : + 10; + + listContainer.addShape("rect", { + attrs: { + y: 30 + + barStyle.padding + + (startIndex / list.length) * (height - 30), + x: width - barStyle.padding - barStyle.width, + width: barStyle.width, + height: Math.min(height, indexHeight), + ...barStyle.innerStyle, + }, + }); + } + if (afterList) { + afterList.forEach((e, i) => { + const isSelected = + Math.floor(startIndex) + i === Number(selectedIndex); + let { + key = "", type + } = e; + if (type) { + key += " - " + type; + } + const label = key.length > 26 ? key.slice(0, 24) + "..." : key; + + listContainer.addShape("rect", { + attrs: { + x: 1, + y: i * itemHeight - itemHeight / 2 + offsetY, + width: width - 4, + height: itemHeight, + radius: 2, + lineWidth: 1, + cursor: "pointer", + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `item-${Math.floor(startIndex) + i}-content`, + draggable: true, + }); + + if (!cfg.hideDot) { + listContainer.addShape("circle", { + attrs: { + x: 0, + y: i * itemHeight + offsetY, + r: 3, + stroke: boxStyle.stroke, + fill: "white", + radius: 2, + lineWidth: 1, + cursor: "pointer", + }, + }); + listContainer.addShape("circle", { + attrs: { + x: width, + y: i * itemHeight + offsetY, + r: 3, + stroke: boxStyle.stroke, + fill: "white", + radius: 2, + lineWidth: 1, + cursor: "pointer", + }, + }); + } + + listContainer.addShape("text", { + attrs: { + x: 12, + y: i * itemHeight + offsetY + 6, + text: label, + fontSize: 12, + fill: "#000", + fontFamily: "Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol", + full: e, + fontWeight: isSelected ? 500 : 100, + cursor: "pointer", + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `item-${Math.floor(startIndex) + i}`, + }); + }); + } + + + + return keyshape; + }, + getAnchorPoints() { + return [ + [0, 0], + [1, 0], + ]; + }, +}); + +const dataTransform = (data) => { + const nodes = []; + const edges = []; + + data.map((node) => { + nodes.push({ + ...node + }); + if (node.attrs) { + node.attrs.forEach((attr) => { + if (attr.relation) { + attr.relation.forEach((relation) => { + edges.push({ + source: node.id, + target: relation.nodeId, + sourceKey: attr.key, + targetKey: relation.key, + label: relation.label, + }); + }); + } + + }); + } + }); + + return { + nodes, + edges, + }; +} + +const container = document.getElementById('container'); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultNode: { + size: [300, 200], + type: 'dice-er-box', + color: '#5B8FF9', + style: { + fill: '#9EC9FF', + lineWidth: 3, + }, + labelCfg: { + style: { + fill: 'black', + fontSize: 20, + }, + }, + }, + defaultEdge: { + type: 'dice-er-edge', + style: { + stroke: '#e2e2e2', + lineWidth: 4, + endArrow: true, + }, + }, + modes: { + default: ['dice-er-scroll', 'drag-node', 'drag-canvas'], + }, + layout: { + type: 'dagre', + rankdir: 'LR', + align: 'UL', + controlPoints: true, + nodesepFunc: () => 0.2, + ranksepFunc: () => 0.5, + }, + animate: true, + fitView: true +}) + +graph.data(dataTransform(rawData)); + +graph.render(); diff --git a/packages/site/examples/item/customNode/demo/stackChart.js b/packages/site/examples/item/customNode/demo/stackChart.js new file mode 100644 index 0000000000..396ae15a3e --- /dev/null +++ b/packages/site/examples/item/customNode/demo/stackChart.js @@ -0,0 +1,181 @@ +import G6 from '@antv/g6'; + +/** + * Custom stacked bar chart node + * by Jingxi + * + */ + +const getPath = (cx, cy, rs, re, startAngle, endAngle, clockwise) => { + const flag1 = clockwise ? 1 : 0; + const flag2 = clockwise ? 0 : 1; + return [ + ['M', Math.cos(startAngle) * rs + cx, Math.sin(startAngle) * rs + cy], + ['L', Math.cos(startAngle) * re + cx, Math.sin(startAngle) * re + cy], + ['A', re, re, 0, 0, flag1, Math.cos(endAngle) * re + cx, Math.sin(endAngle) * re + cy], + ['L', Math.cos(endAngle) * rs + cx, Math.sin(endAngle) * rs + cy], + ['A', rs, rs, 0, 0, flag2, Math.cos(startAngle) * rs + cx, Math.sin(startAngle) * rs + cy], + ['Z'], + ]; +}; + +// Custom stacked bar chart node +G6.registerNode('stacked-bar-node', { + draw(cfg, group) { + /* + G: + Fan + x: the circle center of the fan + y: the circle center of the fan + rs: inner radius + re: outer radius + startAngle: start angle + endAngle: end angle + clockwise: render clockwisely if it is true + */ + const baseR = 30; + let nowAngle = 0; + const everyIncAngle = (2 * Math.PI * (360 / 5 / 5)) / 360; + cfg.details.forEach((cat) => { + cat.values.forEach((item) => { + const baseNbr = Math.ceil(item / 10); + const baseIncR = 7; + let nowStartR = baseR; + const last = item % 10; + const endAngle = nowAngle + everyIncAngle; + for (let i = 0; i < baseNbr; i++) { + const path0 = getPath(0, 0, nowStartR, nowStartR + baseIncR, nowAngle, endAngle, false); + group.addShape('path', { + attrs: { + path: path0, + stroke: 'darkgray', + fill: cat.color, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape1', + }); + nowStartR = nowStartR + baseIncR + 2; + if (i === baseNbr - 1 && last !== 0) { + const path1 = getPath( + 0, + 0, + nowStartR, + nowStartR + (baseIncR * last) / 10, + nowAngle, + endAngle, + false, + ); + group.addShape('path', { + attrs: { + path: path1, + stroke: 'darkgray', + fill: cat.color, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape2', + }); + } + } + nowAngle = endAngle; + }); + }); + + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: baseR, + fill: cfg.centerColor, + stroke: 'darkgray', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + if (cfg.label) { + group.addShape('text', { + attrs: { + x: 0, + y: 0, + textAlign: 'center', + textBaseline: 'middle', + text: cfg.label, + fill: 'white', + fontStyle: 'bold', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', + }); + } + return group; + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, +}); + +const data = { + nodes: [ + { + id: 'nodeF', + x: 150, + y: 150, + label: 'StackedBar1', + type: 'stacked-bar-node', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + details: [ + { cat: 'pv', values: [20, 30, 40, 30, 30], color: '#5B8FF9' }, + { cat: 'dal', values: [40, 30, 20, 30, 50], color: '#5AD8A6' }, + { cat: 'uv', values: [40, 30, 30, 40, 40], color: '#5D7092' }, + { cat: 'sal', values: [20, 30, 50, 20, 20], color: '#F6BD16' }, + { cat: 'cal', values: [10, 10, 20, 20, 20], color: '#E8684A' }, + ], + centerColor: '#5b8ff9', + }, + { + id: 'nodeF2', + x: 500, + y: 150, + label: 'StackedBar2', + type: 'stacked-bar-node', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + details: [ + { cat: 'pv', values: [10, 10, 80, 20, 10], color: '#5ad8a6' }, + { cat: 'dal', values: [20, 30, 10, 50, 40], color: '#ff99c3' }, + { cat: 'uv', values: [10, 50, 30, 20, 30], color: '#6dc8ec' }, + { cat: 'sal', values: [70, 30, 20, 20, 20], color: '#269a99' }, + { cat: 'cal', values: [50, 10, 20, 70, 30], color: '#9270CA' }, + ], + centerColor: '#5b8ff9', + }, + ], + edges: [ + { + source: 'nodeF', + target: 'nodeF2', + }, + ], +}; + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/item/customNode/demo/svgDom.js b/packages/site/examples/item/customNode/demo/svgDom.js new file mode 100644 index 0000000000..e8714d3b74 --- /dev/null +++ b/packages/site/examples/item/customNode/demo/svgDom.js @@ -0,0 +1,139 @@ +import G6 from '@antv/g6'; +import { useEffect } from 'react'; + +/** + * This demo shows how to register a custom node with SVG DOM shape + * by Shiwu + * + */ + +/** + * Register a node type with DOM + */ +G6.registerNode('dom-node', { + draw: (cfg, group) => { + const stroke = cfg.style ? cfg.style.stroke || '#5B8FF9' : '#5B8FF9'; + const shape = group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + html: ` +
+
+ +
+ ${cfg.label} +
+ `, + }, + draggable: true, + }); + return shape; + }, +}); + +/** 数据 */ +const data = { + nodes: [ + { + id: 'node1', + x: 10, + y: 100, + label: 'Homepage', + }, + { + id: 'node2', + x: 200, + y: 100, + label: 'Subpage', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 100; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = `由于打包问题,本 demo 的 111-113 行被暂时注释。需要您在代码栏中打开 111-113 行的注释以得到自定义 DOM 节点正确的交互。
Due to the packing problem of the site, we have to note the line 111-113 of this demo temporary. Unnote them to see the result of custom DOM node with interactions please.`; +container.appendChild(descriptionDiv); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + renderer: 'svg', + linkCenter: true, + defaultNode: { + type: 'dom-node', + size: [120, 40], + }, +}); + +graph.data(data); +graph.render(); + +// click listener for dom nodes to response the click by changing stroke color +const listener = (dom) => { + const nodeId = dom.id; + if (!nodeId) return; + const node = graph.findById(nodeId); + let stroke = ''; + if (!node.hasState('selected')) { + stroke = '#f00'; + graph.setItemState(node, 'selected', true); + } else { + stroke = '#5B8FF9'; + graph.setItemState(node, 'selected', false); + } + graph.updateItem(nodeId, { + style: { + stroke, + }, + }); +}; + +const bindClickListener = () => { + const domNodes = document.getElementsByClassName('dom-node'); + for (let i = 0; i < domNodes.length; i++) { + const dom = domNodes[i]; + // open the following lines pls! + // dom.addEventListener('click', (e) => { + // listener(dom); + // }); + } +}; + +bindClickListener(); + +// after update the item, all the DOMs will be re-rendered +// so the listeners should be rebinded to the new DOMs +graph.on('afterupdateitem', (e) => { + bindClickListener(); +}); +graph.on('afterrender', (e) => { + bindClickListener(); +}); +// if it is TreeGraph and with default animate:true, you should bind the litsener after animation +// graph.on('afteranimate', (e) => { +// bindClickListener(); +// }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 100); + }; diff --git a/packages/site/examples/item/customNode/index.en.md b/packages/site/examples/item/customNode/index.en.md new file mode 100644 index 0000000000..0c07d21204 --- /dev/null +++ b/packages/site/examples/item/customNode/index.en.md @@ -0,0 +1,14 @@ +--- +title: Custom Node +order: 4 +--- + +The custom node mechanism in G6 allows users to design their own node when there are no appropriate built-in nodes for their scenario. + +Since from G6 V3.6.0, G6 nodes support the use of G2 statistical charts, such as line charts, bar charts, pie charts, dashboards, etc. If you want to use these statistical charts, by importing the `@antv/chart-node-g6` package to implement. Refer to [G2 Documentation](https://g2.antv.vision/zh/docs/manual/tutorial/overview). + +For more information on how to use G2 charts in G6 nodes, please refer to [Document](https://www.yuque.com/antv/g6-blog/pwg00q). + +## Usage + +For more information, please refer to [Custom Node](/en/docs/manual/middle/elements/nodes/custom-node). diff --git a/packages/site/examples/item/customNode/index.zh.md b/packages/site/examples/item/customNode/index.zh.md new file mode 100644 index 0000000000..41363eef60 --- /dev/null +++ b/packages/site/examples/item/customNode/index.zh.md @@ -0,0 +1,14 @@ +--- +title: 自定义节点 +order: 4 +--- + +当 G6 的内置节点不能满足需求时,G6 的自定义节点机制允许用户设计和定制新的节点类型。 + +从 G6 3.6.0 版本开始,G6 的节点中支持使用 G2 的统计图表,如折线图、柱状图、饼图、仪表盘等。如果要使用这些统计图表,通过引入 `@antv/chart-node-g6` 包来实现,使用方式参考 [G2 文档](https://g2.antv.vision/zh/docs/manual/tutorial/overview)。 + +更多关于 G6 节点中如何使用 G2 图表的内容请参考[文档](https://www.yuque.com/antv/g6-blog/pwg00q)。 + +## 使用指南 + +下面示例展示了自定义统计图表、卡片、列表类的节点。更多信息参见[自定义节点](/zh/docs/manual/middle/elements/nodes/custom-node)。 diff --git a/packages/site/examples/item/defaultCombos/API.en.md b/packages/site/examples/item/defaultCombos/API.en.md new file mode 100644 index 0000000000..06f7394681 --- /dev/null +++ b/packages/site/examples/item/defaultCombos/API.en.md @@ -0,0 +1,19 @@ +--- +title: API +--- + +# Combo Configurations + + + +# Common Functions for Item + + + +# Functions for Node (Combo extends from Node) + + + +# Combo Functions + + diff --git a/packages/site/examples/item/defaultCombos/API.zh.md b/packages/site/examples/item/defaultCombos/API.zh.md new file mode 100644 index 0000000000..a784484dd1 --- /dev/null +++ b/packages/site/examples/item/defaultCombos/API.zh.md @@ -0,0 +1,19 @@ +--- +title: API +--- + +# Combo 配置项 + + + +# 元素通用方法 + + + +# Node 实例用方法(Combo 继承 Node) + + + +# Combo 实例方法 + + diff --git a/packages/site/examples/item/defaultCombos/demo/circle.js b/packages/site/examples/item/defaultCombos/demo/circle.js new file mode 100644 index 0000000000..17c448d99a --- /dev/null +++ b/packages/site/examples/item/defaultCombos/demo/circle.js @@ -0,0 +1,109 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'node1', + x: 250, + y: 150, + comboId: 'combo', + }, + { + id: 'node2', + x: 350, + y: 150, + comboId: 'combo', + }, + { + id: 'node3', + x: 250, + y: 300, + comboId: 'combo2', + }, + { + id: 'node4', + x: 450, + y: 300, + comboId: 'combo2', + }, + ], + combos: [ + { + id: 'combo', + label: 'Combo', + }, + { + id: 'combo2', + label: 'with substitute icon while collapsed', + collapsed: true, + collapsedSubstituteIcon: { + show: true, + img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IEQFS5VtXX8AAAAAAAAAAABkARQnAQ', + width: 72, + height: 72 + } + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + modes: { + default: ['drag-canvas', 'drag-node', 'drag-combo', 'collapse-expand-combo'], + }, + defaultCombo: { + type: 'circle', + /* style for the keyShape */ + // style: { + // lineWidth: 1, + // }, + labelCfg: { + /* label's offset to the keyShape */ + // refY: 10, + /* label's position, options: center, top, bottom, left, right */ + position: 'top', + /* label's style */ + // style: { + // fontSize: 18, + // }, + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + /* you can extend it or override it as you want */ + // comboStateStyles: { + // active: { + // fill: '#f00', + // opacity: 0.5 + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('combo:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('combo:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); +graph.on('combo:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getCombos().forEach((combo) => { + graph.clearItemStates(combo); + }); +}); diff --git a/packages/site/examples/item/defaultCombos/demo/meta.json b/packages/site/examples/item/defaultCombos/demo/meta.json new file mode 100644 index 0000000000..1ca69037d9 --- /dev/null +++ b/packages/site/examples/item/defaultCombos/demo/meta.json @@ -0,0 +1,18 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "circle.js", + "title": "圆形", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Rvx9SYSHGsIAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "rect.js", + "title": "矩形", + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*qvmVTZc-iNcAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/item/defaultCombos/demo/rect.js b/packages/site/examples/item/defaultCombos/demo/rect.js new file mode 100644 index 0000000000..4e04d0bc5f --- /dev/null +++ b/packages/site/examples/item/defaultCombos/demo/rect.js @@ -0,0 +1,111 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'node1', + x: 250, + y: 150, + comboId: 'combo', + }, + { + id: 'node2', + x: 350, + y: 150, + comboId: 'combo', + }, + { + id: 'node3', + x: 250, + y: 300, + comboId: 'combo2', + }, + { + id: 'node4', + x: 450, + y: 300, + comboId: 'combo2', + }, + ], + combos: [ + { + id: 'combo', + label: 'Combo', + }, + { + id: 'combo2', + label: 'with substitute icon while collapsed', + collapsed: true, + collapsedSubstituteIcon: { + show: true, + img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IEQFS5VtXX8AAAAAAAAAAABkARQnAQ', + width: 72, + height: 72 + } + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + // Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos + groupByTypes: false, + modes: { + default: ['drag-canvas', 'drag-node', 'drag-combo', 'collapse-expand-combo'], + }, + defaultCombo: { + type: 'rect', + /* The minimum size of the combo. combo 最小大小 */ + size: [50, 50], + /* style for the keyShape */ + // style: { + // lineWidth: 1, + // }, + labelCfg: { + /* label's offset to the keyShape */ + // refY: 10, + /* label's position, options: center, top, bottom, left, right */ + position: 'top', + /* label's style */ + // style: { + // fontSize: 18, + // }, + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + /* you can extend it or override it as you want */ + // comboStateStyles: { + // active: { + // fill: '#f00', + // opacity: 0.5 + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('combo:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('combo:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); +graph.on('combo:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getCombos().forEach((combo) => { + graph.clearItemStates(combo); + }); +}); diff --git a/packages/site/examples/item/defaultCombos/index.en.md b/packages/site/examples/item/defaultCombos/index.en.md new file mode 100644 index 0000000000..cdbfc85c32 --- /dev/null +++ b/packages/site/examples/item/defaultCombos/index.en.md @@ -0,0 +1,12 @@ +--- +title: Built-in Combos +order: 2 +--- + +New feature of V3.5. There are 2 kinds of built-in combos in G6, which can be extended by [configurations](/en/docs/manual/middle/elements/combos/defaultCombo#common-property) and [custom combo mechanism](/en/docs/manual/middle/elements/combos/custom-combo). The following two examples allow users to drag the combo or node to change the hierarchy, and double click to collapse/expand a combo. + +## Usage + +2 built-in combos and their extensions allow users to select appropriate ones for their scenario. + +Please refer to [Built-in Combos](/en/docs/manual/middle/elements/combos/built-in/circle) for more information. diff --git a/packages/site/examples/item/defaultCombos/index.zh.md b/packages/site/examples/item/defaultCombos/index.zh.md new file mode 100644 index 0000000000..4a996f7f45 --- /dev/null +++ b/packages/site/examples/item/defaultCombos/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 内置 Combo +order: 2 +--- + +自 V3.5 起支全新节点分组机制 Combo。内置了 2 种不同类型的 Combo,支持通过[配置](/zh/docs/manual/middle/elements/combos/defaultCombo#combo-的通用属性)进行扩展,支持[自定义](/zh/docs/manual/middle/elements/combos/custom-combo)。下面两个例子允许节点和 Combo 的拖拽改变从属关系,双击收缩/展开 Combo。 + +## 何时使用 + +用户可根据具体的业务场景,选择合适的内置 Combo。更多内容请参考 [内置 Combo](/zh/docs/manual/middle/elements/combos/built-in/circle)。 diff --git a/packages/site/examples/item/defaultEdges/API.en.md b/packages/site/examples/item/defaultEdges/API.en.md new file mode 100644 index 0000000000..1237f4551f --- /dev/null +++ b/packages/site/examples/item/defaultEdges/API.en.md @@ -0,0 +1,15 @@ +--- +title: API +--- + +# Edge Configurations + + + +# Common Functions for Item + + + +# Edge Functions + + diff --git a/packages/site/examples/item/defaultEdges/API.zh.md b/packages/site/examples/item/defaultEdges/API.zh.md new file mode 100644 index 0000000000..aaf7e93390 --- /dev/null +++ b/packages/site/examples/item/defaultEdges/API.zh.md @@ -0,0 +1,15 @@ +--- +title: API +--- + +# 边配置 + + + +# 元素通用方法 + + + +# 边实例方法 + + diff --git a/packages/site/examples/item/defaultEdges/demo/arc.js b/packages/site/examples/item/defaultEdges/demo/arc.js new file mode 100644 index 0000000000..005872d105 --- /dev/null +++ b/packages/site/examples/item/defaultEdges/demo/arc.js @@ -0,0 +1,107 @@ +import G6 from '@antv/g6'; + +/** + * The usage of arc edge + * by Shiwu + */ + +const data = { + nodes: [ + { + id: '0', + x: 150, + y: 50, + }, + { + id: '1', + x: 350, + y: 250, + }, + ], + edges: [ + // Built-in arc edges + { + id: 'edge0', + source: '0', + target: '1', + label: 'curveOffset = 20', + curveOffset: 20, + }, + { + id: 'edge1', + source: '0', + target: '1', + label: 'curveOffset = 50', // the bending degree + curveOffset: 50, + }, + { + id: 'edge2', + source: '0', + target: '1', + label: 'curveOffset = -50', // the bending degree + curveOffset: -50, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + // 支持的 behavior + default: ['drag-node'], + }, + defaultEdge: { + type: 'arc', + /* you can configure the global edge style as following lines */ + // style: { + // stroke: '#F6BD16', + // }, + labelCfg: { + autoRotate: true, + refY: -10, + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // edgeStateStyles: { + // // edge style of active state + // active: { + // opacity: 0.5, + // stroke: '#f00' + // }, + // // edge style of selected state + // selected: { + // stroke: '#ff0' + // lineWidth: 3, + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('edge:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('edge:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('edge:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getEdges().forEach((edge) => { + graph.clearItemStates(edge); + }); +}); diff --git a/packages/site/examples/item/defaultEdges/demo/cubic1.js b/packages/site/examples/item/defaultEdges/demo/cubic1.js new file mode 100644 index 0000000000..5bc37d92e4 --- /dev/null +++ b/packages/site/examples/item/defaultEdges/demo/cubic1.js @@ -0,0 +1,131 @@ +import G6 from '@antv/g6'; + +/** + * The usage of cubic edge + * **/ + +G6.registerNode( + 'my-rect', + { + getAnchorPoints: function getAnchorPoints() { + return [ + [0.5, 0], + [0.5, 1], + ]; + }, + }, + 'rect', +); + +const data = { + nodes: [ + { + id: 'node0', + x: 200, + y: 10, + size: 20, + }, + { + id: 'node1', + x: 200, + y: 50, + label: '1222', + type: 'my-rect', + }, + { + id: 'node2', + x: 150, + y: 150, + type: 'my-rect', + }, + { + id: 'node3', + x: 250, + y: 150, + type: 'my-rect', + }, + { + id: 'node4', + x: 200, + y: 250, + type: 'my-rect', + }, + ], + edges: [ + { + source: 'node0', + target: 'node1', + }, + { + source: 'node1', + target: 'node2', + }, + { + source: 'node1', + target: 'node3', + }, + { + source: 'node2', + target: 'node4', + }, + { + source: 'node3', + target: 'node4', + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-canvas'], + }, + defaultEdge: { + type: 'cubic-vertical', + /* you can configure the global edge style as following lines */ + // style: { + // stroke: '#F6BD16', + // }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // edgeStateStyles: { + // // edge style of active state + // active: { + // opacity: 0.5, + // stroke: '#f00' + // }, + // // edge style of selected state + // selected: { + // stroke: '#ff0' + // lineWidth: 3, + // }, + // }, +}); +graph.data(data); +graph.render(); + +graph.on('edge:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('edge:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('edge:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getEdges().forEach((edge) => { + graph.clearItemStates(edge); + }); +}); diff --git a/packages/site/examples/item/defaultEdges/demo/cubic2.js b/packages/site/examples/item/defaultEdges/demo/cubic2.js new file mode 100644 index 0000000000..ddce01011a --- /dev/null +++ b/packages/site/examples/item/defaultEdges/demo/cubic2.js @@ -0,0 +1,107 @@ +import G6 from '@antv/g6'; + +/** + * The usage of cubic edge + * + * **/ +const data = { + nodes: [ + { + id: 'node5', + x: 150, + y: 200, + label: '5', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + { + id: 'node6', + x: 300, + y: 150, + label: '6', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + { + id: 'node7', + x: 300, + y: 250, + label: '7', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + ], + edges: [ + { + source: 'node5', + target: 'node6', + type: 'cubic-horizontal', + }, + { + source: 'node5', + target: 'node7', + type: 'cubic-horizontal', + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-canvas'], + }, + defaultEdge: { + type: 'cubic-horizontal', + /* you can configure the global edge style as following lines */ + // style: { + // stroke: '#F6BD16', + // }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // edgeStateStyles: { + // // edge style of active state + // active: { + // opacity: 0.5, + // stroke: '#f00' + // }, + // // edge style of selected state + // selected: { + // stroke: '#ff0' + // lineWidth: 3, + // }, + // }, +}); +graph.data(data); +graph.render(); + +graph.on('edge:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('edge:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('edge:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getEdges().forEach((edge) => { + graph.clearItemStates(edge); + }); +}); diff --git a/packages/site/examples/item/defaultEdges/demo/loop.js b/packages/site/examples/item/defaultEdges/demo/loop.js new file mode 100644 index 0000000000..b7e1f4e6f0 --- /dev/null +++ b/packages/site/examples/item/defaultEdges/demo/loop.js @@ -0,0 +1,96 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + x: 150, + y: 150, + }, + { + id: '1', + x: 350, + y: 150, + }, + ], + edges: [ + // 内置 loop + { + source: '0', + target: '0', + }, + { + source: '1', + target: '1', + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center + fitCenter: true, + modes: { + // 支持的 behavior + default: ['drag-node'], + }, + defaultEdge: { + type: 'loop', + /* you can configure the global edge style as following lines */ + style: { + // stroke: '#F6BD16', + endArrow: { + path: 'M 0,0 L 20,10 L 20,-10 Z', + fill: '#eee', + }, + }, + // 更多关于 loop 的配置请参考http://antv.alipay.com/zh/docs/manual/middle/elements/edges/loop/#%E8%87%AA%E7%8E%AF%E7%89%B9%E6%AE%8A%E9%85%8D%E7%BD%AE-loopcfg + loopCfg: { + position: 'top', + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // edgeStateStyles: { + // // edge style of active state + // active: { + // opacity: 0.5, + // stroke: '#f00' + // }, + // // edge style of selected state + // selected: { + // stroke: '#ff0' + // lineWidth: 3, + // }, + // }, +}); + +// 需要等 G 4.0 局部渲染完善后,就不用临时关闭了 +const canvas = graph.get('canvas'); +canvas.set('localRefresh', false); + +graph.data(data); +graph.render(); + +graph.on('edge:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('edge:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('edge:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getEdges().forEach((edge) => { + graph.clearItemStates(edge); + }); +}); diff --git a/packages/site/examples/item/defaultEdges/demo/meta.json b/packages/site/examples/item/defaultEdges/demo/meta.json new file mode 100644 index 0000000000..4a3d0d109a --- /dev/null +++ b/packages/site/examples/item/defaultEdges/demo/meta.json @@ -0,0 +1,64 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "polyline1.js", + "title": { + "zh": "折线 1", + "en": "Polyline 1" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*tGX6S5r94wkAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "polyline2.js", + "title": { + "zh": "折线 2", + "en": "Polyline 2" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*CA7oQJIkIl8AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "polyline3.js", + "title": { + "zh": "折线 3", + "en": "Polyline 3" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*E-MJQqf9mSUAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "cubic1.js", + "title": { + "zh": "三次贝塞尔曲线-垂直", + "en": "Vertical Cubic" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*UO_sQp0XftMAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "cubic2.js", + "title": { + "zh": "三次贝塞尔曲线-水平", + "en": "Horizontal Cubic" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*LxFySJ-uvhMAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "arc.js", + "title": { + "zh": "弧线", + "en": "Arc" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*xkK6QoNUszgAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "loop.js", + "title": { + "zh": "自环边", + "en": "Loop" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*W8wxRJk_q8wAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/item/defaultEdges/demo/polyline1.js b/packages/site/examples/item/defaultEdges/demo/polyline1.js new file mode 100644 index 0000000000..db5f533753 --- /dev/null +++ b/packages/site/examples/item/defaultEdges/demo/polyline1.js @@ -0,0 +1,87 @@ +import G6 from '@antv/g6'; + +/** + * The usage of built-in polyline + * by Shiwu + */ + +const data = { + nodes: [ + { + id: '0', + x: 150, + y: 100, + }, + { + id: '1', + x: 350, + y: 300, + }, + ], + edges: [ + // Built-in polyline + { + source: '0', + target: '1', + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + // make the edge link to the centers of the end nodes + linkCenter: true, + modes: { + // behavior + default: ['drag-node'], + }, + defaultEdge: { + type: 'polyline', + /* you can configure the global edge style as following lines */ + // style: { + // stroke: '#F6BD16', + // }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // edgeStateStyles: { + // // edge style of active state + // active: { + // opacity: 0.5, + // stroke: '#f00' + // }, + // // edge style of selected state + // selected: { + // stroke: '#ff0' + // lineWidth: 3, + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('edge:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('edge:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('edge:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getEdges().forEach((edge) => { + graph.clearItemStates(edge); + }); +}); diff --git a/packages/site/examples/item/defaultEdges/demo/polyline2.js b/packages/site/examples/item/defaultEdges/demo/polyline2.js new file mode 100644 index 0000000000..6962600f24 --- /dev/null +++ b/packages/site/examples/item/defaultEdges/demo/polyline2.js @@ -0,0 +1,88 @@ +import G6 from '@antv/g6'; + +/** + * Built-in polyline edge with configurations + * by 十吾 + */ + +const data = { + nodes: [ + { + id: '2', + x: 150, + y: 150, + }, + { + id: '3', + x: 350, + y: 250, + }, + ], + edges: [ + { + source: '2', + target: '3', + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + /* translate the graph to align the canvas's center, support by v3.5.1 */ + fitCenter: true, + modes: { + /* behavior */ + default: ['drag-node'], + }, + defaultEdge: { + type: 'polyline', + /* configure the bending radius and min distance to the end nodes */ + style: { + radius: 10, + offset: 30, + endArrow: true, + /* and other styles */ + // stroke: '#F6BD16', + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // edgeStateStyles: { + // // edge style of active state + // active: { + // opacity: 0.5, + // stroke: '#f00' + // }, + // // edge style of selected state + // selected: { + // stroke: '#ff0' + // lineWidth: 3, + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('edge:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('edge:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('edge:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getEdges().forEach((edge) => { + graph.clearItemStates(edge); + }); +}); diff --git a/packages/site/examples/item/defaultEdges/demo/polyline3.js b/packages/site/examples/item/defaultEdges/demo/polyline3.js new file mode 100644 index 0000000000..e7483ac76f --- /dev/null +++ b/packages/site/examples/item/defaultEdges/demo/polyline3.js @@ -0,0 +1,109 @@ +import G6 from '@antv/g6'; + +/** + * Usage of built-in polyline edge with controlPoints + * by 十吾 + */ + +const data = { + nodes: [ + { + id: '4', + x: 150, + y: 100, + }, + { + id: '5', + x: 350, + y: 250, + }, + ], + edges: [ + { + source: '4', + target: '5', + // assign the control points to control the bending positions + controlPoints: [ + { + x: 260, + y: 80, + }, + { + x: 320, + y: 50, + }, + { + x: 390, + y: 110, + }, + { + x: 420, + y: 110, + }, + { + x: 420, + y: 140, + }, + ], + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + // make the edge link to the centers of the end nodes + linkCenter: true, + modes: { + // behavior + default: ['drag-node'], + }, + defaultEdge: { + type: 'polyline', + /* you can configure the global edge style as following lines */ + // style: { + // stroke: '#F6BD16', + // }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // edgeStateStyles: { + // // edge style of active state + // active: { + // opacity: 0.5, + // stroke: '#f00' + // }, + // // edge style of selected state + // selected: { + // stroke: '#ff0' + // lineWidth: 3, + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('edge:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('edge:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('edge:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getEdges().forEach((edge) => { + graph.clearItemStates(edge); + }); +}); diff --git a/packages/site/examples/item/defaultEdges/index.en.md b/packages/site/examples/item/defaultEdges/index.en.md new file mode 100644 index 0000000000..02e49028bf --- /dev/null +++ b/packages/site/examples/item/defaultEdges/index.en.md @@ -0,0 +1,12 @@ +--- +title: Built-in Edges +order: 1 +--- + +There are several built-in edges in G6, including line, polyline, bezier curve, arc, loop, and so on. + +## Usage + +The demos below show the usage of `polyline`、`cubic`、`arc` and `loop`. + +Please refer to [Built-in Edges in G6](/zh/docs/manual/middle/elements/edges/defaultEdge) for more information. diff --git a/packages/site/examples/item/defaultEdges/index.zh.md b/packages/site/examples/item/defaultEdges/index.zh.md new file mode 100644 index 0000000000..a2ce004064 --- /dev/null +++ b/packages/site/examples/item/defaultEdges/index.zh.md @@ -0,0 +1,12 @@ +--- +title: 内置边 +order: 1 +--- + +G6 共内置了多种不同类型的边,包括直线、折线、曲线、自环等。 + +## 何时使用 + +以下代码演示了:`polyline`、`cubic`、`arc` 和 `loop` 四种内置边。 + +更多信息参见 [G6 的内置边](/zh/docs/manual/middle/elements/edges/defaultEdge) diff --git a/packages/site/examples/item/defaultNodes/API.en.md b/packages/site/examples/item/defaultNodes/API.en.md new file mode 100644 index 0000000000..f6ef9cf665 --- /dev/null +++ b/packages/site/examples/item/defaultNodes/API.en.md @@ -0,0 +1,15 @@ +--- +title: API +--- + +# Node Configurations + + + +# Common Functions for Item + + + +# Node Functions + + diff --git a/packages/site/examples/item/defaultNodes/API.zh.md b/packages/site/examples/item/defaultNodes/API.zh.md new file mode 100644 index 0000000000..6bc4fa17d0 --- /dev/null +++ b/packages/site/examples/item/defaultNodes/API.zh.md @@ -0,0 +1,15 @@ +--- +title: API +--- + +# 节点配置 + + + +# 元素通用方法 + + + +# 节点实例方法 + + diff --git a/packages/site/examples/item/defaultNodes/demo/circle.js b/packages/site/examples/item/defaultNodes/demo/circle.js new file mode 100644 index 0000000000..d959326fa6 --- /dev/null +++ b/packages/site/examples/item/defaultNodes/demo/circle.js @@ -0,0 +1,106 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'circle', + label: 'Circle', + x: 250, + y: 150, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + defaultNode: { + /* node type */ + type: 'circle', + /* node size */ + size: [60], + /* style for the keyShape */ + // style: { + // fill: '#9EC9FF', + // stroke: '#5B8FF9', + // lineWidth: 3, + // }, + labelCfg: { + /* label's position, options: center, top, bottom, left, right */ + position: 'bottom', + /* label's offset to the keyShape, 4 by default */ + // offset: 12, + /* label's style */ + // style: { + // fontSize: 20, + // fill: '#ccc', + // fontWeight: 500 + // } + }, + /* configurations for four linkpoints */ + linkPoints: { + top: true, + right: true, + bottom: true, + left: true, + /* linkPoints' size, 8 by default */ + // size: 5, + /* linkPoints' style */ + // fill: '#ccc', + // stroke: '#333', + // lineWidth: 2, + }, + /* icon configuration */ + icon: { + /* whether show the icon, false by default */ + show: true, + /* icon's img address, string type */ + // img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', + /* icon's size, 20 * 20 by default: */ + // width: 40, + // height: 40 + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // nodeStateStyles: { + // // node style of active state + // active: { + // fillOpacity: 0.8, + // }, + // // node style of selected state + // selected: { + // lineWidth: 5, + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('node:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getNodes().forEach((node) => { + graph.clearItemStates(node); + }); +}); diff --git a/packages/site/examples/item/defaultNodes/demo/diamond.js b/packages/site/examples/item/defaultNodes/demo/diamond.js new file mode 100644 index 0000000000..0a22f56798 --- /dev/null +++ b/packages/site/examples/item/defaultNodes/demo/diamond.js @@ -0,0 +1,106 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'diamond', + label: 'Diamond', + x: 250, + y: 150, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + defaultNode: { + type: 'diamond', + /* node size */ + size: [80, 60], + /* style for the keyShape */ + // style: { + // fill: '#9EC9FF', + // stroke: '#5B8FF9', + // lineWidth: 3, + // }, + labelCfg: { + /* label's position, options: center, top, bottom, left, right */ + position: 'bottom', + /* label's offset to the keyShape, 4 by default */ + // offset: 12, + /* label's style */ + // style: { + // fontSize: 20, + // fill: '#ccc', + // fontWeight: 500 + // } + }, + /* configurations for four linkpoints */ + linkPoints: { + top: true, + right: true, + bottom: true, + left: true, + /* linkPoints' size, 8 by default */ + // size: 5, + /* linkPoints' style */ + // fill: '#ccc', + // stroke: '#333', + // lineWidth: 2, + }, + /* icon configuration */ + icon: { + /* whether show the icon, false by default */ + show: true, + /* icon's img address, string type */ + // img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', + /* icon's size, 20 * 20 by default: */ + // width: 40, + // height: 40 + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // nodeStateStyles: { + // // node style of active state + // active: { + // fillOpacity: 0.8, + // }, + // // node style of selected state + // selected: { + // lineWidth: 5, + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('node:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); + +graph.on('canvas:click', (evt) => { + graph.getNodes().forEach((node) => { + graph.clearItemStates(node); + }); +}); diff --git a/packages/site/examples/item/defaultNodes/demo/donut.js b/packages/site/examples/item/defaultNodes/demo/donut.js new file mode 100644 index 0000000000..91c2c6d9ca --- /dev/null +++ b/packages/site/examples/item/defaultNodes/demo/donut.js @@ -0,0 +1,118 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'donut', + label: 'Donut', + x: 250, + y: 150, + // the attributes for drawing donut + donutAttrs: { + prop1: 10, + prop2: 20, + prop3: 25 + }, + // the color map for drawing donut + donutColorMap: { + prop1: '#8eaade', + prop2: '#5c7cb8', + prop3: '#1e3f7d' + }, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + defaultNode: { + /* node type, the priority is lower than the type in the node data */ + type: 'donut', + /* node size */ + size: 60, + /* style for the keyShape */ + // style: { + // fill: '#9EC9FF', + // stroke: '#5B8FF9', + // lineWidth: 3, + // }, + labelCfg: { + /* label's position, options: center, top, bottom, left, right */ + position: 'bottom', + /* label's offset to the keyShape, 4 by default */ + // offset: 12, + /* label's style */ + // style: { + // fontSize: 20, + // fill: '#ccc', + // fontWeight: 500 + // } + }, + /* configurations for four linkpoints */ + // linkPoints: { + // top: true, + // right: true, + // bottom: true, + // left: true, + // /* linkPoints' size, 8 by default */ + // // size: 5, + // /* linkPoints' style */ + // // fill: '#ccc', + // // stroke: '#333', + // // lineWidth: 2, + // }, + /* icon configuration */ + icon: { + /* whether show the icon, false by default */ + show: true, + /* icon's img address, string type */ + // img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', + /* icon's size, 20 * 20 by default: */ + // width: 40, + // height: 40 + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // nodeStateStyles: { + // // node style of active state + // active: { + // fillOpacity: 0.8, + // }, + // // node style of selected state + // selected: { + // lineWidth: 5, + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('node:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getNodes().forEach((node) => { + graph.clearItemStates(node); + }); +}); diff --git a/packages/site/examples/item/defaultNodes/demo/ellipse.js b/packages/site/examples/item/defaultNodes/demo/ellipse.js new file mode 100644 index 0000000000..ad077bb11b --- /dev/null +++ b/packages/site/examples/item/defaultNodes/demo/ellipse.js @@ -0,0 +1,105 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'ellipse', + label: 'Ellipse', + x: 250, + y: 150, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + defaultNode: { + type: 'ellipse', + size: [80, 50], + /* style for the keyShape */ + // style: { + // fill: '#9EC9FF', + // stroke: '#5B8FF9', + // lineWidth: 3, + // }, + labelCfg: { + /* label's position, options: center, top, bottom, left, right */ + position: 'bottom', + /* label's offset to the keyShape, 4 by default */ + // offset: 12, + /* label's style */ + // style: { + // fontSize: 20, + // fill: '#ccc', + // fontWeight: 500 + // } + }, + /* configurations for four linkpoints */ + linkPoints: { + top: true, + right: true, + bottom: true, + left: true, + /* linkPoints' size, 8 by default */ + // size: 5, + /* linkPoints' style */ + // fill: '#ccc', + // stroke: '#333', + // lineWidth: 2, + }, + /* icon configuration */ + icon: { + /* whether show the icon, false by default */ + show: true, + /* icon's img address, string type */ + // img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', + /* icon's size, 20 * 20 by default: */ + // width: 40, + // height: 40 + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // nodeStateStyles: { + // // node style of active state + // active: { + // fillOpacity: 0.8, + // }, + // // node style of selected state + // selected: { + // lineWidth: 5, + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('node:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); + +graph.on('canvas:click', (evt) => { + graph.getNodes().forEach((node) => { + graph.clearItemStates(node); + }); +}); diff --git a/packages/site/examples/item/defaultNodes/demo/image.js b/packages/site/examples/item/defaultNodes/demo/image.js new file mode 100644 index 0000000000..4ce28d7cf7 --- /dev/null +++ b/packages/site/examples/item/defaultNodes/demo/image.js @@ -0,0 +1,43 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'image', + img: 'https://gw.alipayobjects.com/os/s/prod/antv/assets/image/logo-with-text-73b8a.svg', + x: 250, + y: 150, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + defaultNode: { + type: 'image', + size: [260, 80], + clipCfg: { + show: false, + // Clip type options: circle, ellipse, rect, path + type: 'circle', + // circle + r: 30, + // clip style + style: { + lineWidth: 1, + }, + }, + }, + modes: { + default: ['drag-canvas', 'drag-node'], + }, +}); + +graph.data(data); +graph.render(); diff --git a/packages/site/examples/item/defaultNodes/demo/meta.json b/packages/site/examples/item/defaultNodes/demo/meta.json new file mode 100644 index 0000000000..f478f9e44a --- /dev/null +++ b/packages/site/examples/item/defaultNodes/demo/meta.json @@ -0,0 +1,80 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "circle.js", + "title": { + "zh": "圆形", + "en": "Circle" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*8QIsS5rud6oAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "donut.js", + "title": { + "zh": "甜甜圈", + "en": "Donut" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*NRJ7RpkMPNsAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "ellipse.js", + "title": { + "zh": "椭圆", + "en": "Ellipse" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*tWyKToNjzWMAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "rect.js", + "title": { + "zh": "矩形", + "en": "Rect" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*pnaTQYJ3kaQAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "modelRect.js", + "title": { + "zh": "模态矩形", + "en": "ModelRect" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*w4kQSYQ9djQAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "diamond.js", + "title": { + "zh": "模态菱形矩形", + "en": "Diamond" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*6XXCTZxhib0AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "star.js", + "title": { + "zh": "五角星", + "en": "Star" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-unkSZtlDeAAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "triangle.js", + "title": { + "zh": "三角形", + "en": "Triangle" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*i2tETYnJ0dIAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "image.js", + "title": { + "zh": "图片", + "en": "Image" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*o-MuTpQaj7EAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/item/defaultNodes/demo/modelRect.js b/packages/site/examples/item/defaultNodes/demo/modelRect.js new file mode 100644 index 0000000000..5af9ffd47c --- /dev/null +++ b/packages/site/examples/item/defaultNodes/demo/modelRect.js @@ -0,0 +1,112 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'rect1', + label: 'rect1', + description: 'description, hidden when undefined', + x: 250, + y: 150, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + defaultNode: { + type: 'modelRect', + size: [270, 80], + style: { + radius: 5, + stroke: '#69c0ff', + fill: '#ffffff', + lineWidth: 1, + fillOpacity: 1, + }, + // label configurations + labelCfg: { + style: { + fill: '#595959', + fontSize: 14, + }, + offset: 30, + }, + // left rect + preRect: { + show: true, + width: 4, + fill: '#40a9ff', + radius: 2, + }, + // configurations for the four linkpoints + linkPoints: { + top: false, + right: false, + bottom: false, + left: false, + // the size of the linkpoints' circle + size: 10, + lineWidth: 1, + fill: '#72CC4A', + stroke: '#72CC4A', + }, + // configurations for the icon + logoIcon: { + // whether to show the icon + show: true, + x: 0, + y: 0, + // the image url for the icon, string type + img: + 'https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg', + width: 16, + height: 16, + // adjust the offset along x-axis for the icon + offset: 0, + }, + // configurations for state icon + stateIcon: { + // whether to show the icon + show: true, + x: 0, + y: 0, + // the image url for the icon, string type + img: + 'https://gw.alipayobjects.com/zos/basement_prod/300a2523-67e0-4cbf-9d4a-67c077b40395.svg', + width: 16, + height: 16, + // adjust hte offset along x-axis for the icon + offset: -5, + }, + }, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + nodeStateStyles: { + hover: { + lineWidth: 2, + stroke: '#1890ff', + fill: '#e6f7ff', + }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'hover', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'hover', false); +}); diff --git a/packages/site/examples/item/defaultNodes/demo/rect.js b/packages/site/examples/item/defaultNodes/demo/rect.js new file mode 100644 index 0000000000..b0861ff70f --- /dev/null +++ b/packages/site/examples/item/defaultNodes/demo/rect.js @@ -0,0 +1,108 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'rect', + label: 'rect', + x: 250, + y: 150, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + defaultNode: { + /* node type */ + type: 'rect', + /* node size */ + size: [80, 40], + /* style for the keyShape */ + // style: { + // fill: '#9EC9FF', + // stroke: '#5B8FF9', + // lineWidth: 3, + // radius: 10 + // }, + labelCfg: { + /* label's position, options: center, top, bottom, left, right */ + position: 'center', + /* label's offset to the keyShape, 4 by default */ + // offset: 12, + /* label's style */ + // style: { + // fontSize: 20, + // fill: '#ccc', + // fontWeight: 500 + // } + }, + /* configurations for four linkpoints */ + linkPoints: { + top: true, + right: true, + bottom: true, + left: true, + /* linkPoints' size, 8 by default */ + // size: 5, + /* linkPoints' style */ + // fill: '#ccc', + // stroke: '#333', + // lineWidth: 2, + }, + /* icon configuration */ + icon: { + /* whether show the icon, false by default */ + show: true, + /* icon's img address, string type */ + // img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', + /* icon's size, 20 * 20 by default: */ + // width: 40, + // height: 40 + }, + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // nodeStateStyles: { + // // node style of active state + // active: { + // fillOpacity: 0.8, + // }, + // // node style of selected state + // selected: { + // lineWidth: 5, + // }, + // }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('node:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); + +graph.on('canvas:click', (evt) => { + graph.getNodes().forEach((node) => { + graph.clearItemStates(node); + }); +}); diff --git a/packages/site/examples/item/defaultNodes/demo/star.js b/packages/site/examples/item/defaultNodes/demo/star.js new file mode 100644 index 0000000000..ca0c601ed0 --- /dev/null +++ b/packages/site/examples/item/defaultNodes/demo/star.js @@ -0,0 +1,106 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'star', + label: 'Star', + x: 250, + y: 200, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + defaultNode: { + /* node type */ + type: 'star', + /* node size */ + size: [60, 30], + /* style for the keyShape */ + // style: { + // fill: '#9EC9FF', + // stroke: '#5B8FF9', + // lineWidth: 3, + // }, + labelCfg: { + /* label's position, options: center, top, bottom, left, right */ + position: 'bottom', + /* label's offset to the keyShape, 4 by default */ + offset: 20, + /* label's style */ + // style: { + // fontSize: 20, + // fill: '#ccc', + // fontWeight: 500 + // } + }, + /* configurations for four linkpoints */ + linkPoints: { + top: true, + right: true, + bottom: true, + left: true, + /* linkPoints' size, 8 by default */ + // size: 5, + /* linkPoints' style */ + // fill: '#ccc', + // stroke: '#333', + // lineWidth: 2, + }, + /* icon configuration */ + icon: { + /* whether show the icon, false by default */ + show: true, + /* icon's img address, string type */ + // img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', + /* icon's size, 20 * 20 by default: */ + // width: 40, + // height: 40 + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // nodeStateStyles: { + // // node style of active state + // active: { + // fillOpacity: 0.8, + // }, + // // node style of selected state + // selected: { + // lineWidth: 5, + // }, + // }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('node:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getNodes().forEach((node) => { + graph.clearItemStates(node); + }); +}); diff --git a/packages/site/examples/item/defaultNodes/demo/triangle.js b/packages/site/examples/item/defaultNodes/demo/triangle.js new file mode 100644 index 0000000000..23c543ebd4 --- /dev/null +++ b/packages/site/examples/item/defaultNodes/demo/triangle.js @@ -0,0 +1,106 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'circle', + label: 'Triangle', + x: 250, + y: 200, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + defaultNode: { + type: 'triangle', + size: [40], + // options: down, up + direction: 'up', + /* style for the keyShape */ + // style: { + // fill: '#9EC9FF', + // stroke: '#5B8FF9', + // lineWidth: 3, + // }, + labelCfg: { + /* label's position, options: center, top, bottom, left, right */ + position: 'bottom', + /* label's offset to the keyShape, 4 by default */ + offset: 20, + /* label's style */ + // style: { + // fontSize: 20, + // fill: '#ccc', + // fontWeight: 500 + // } + }, + /* configurations for four linkpoints */ + linkPoints: { + top: true, + right: true, + bottom: true, + left: true, + /* linkPoints' size, 8 by default */ + // size: 5, + /* linkPoints' style */ + // fill: '#ccc', + // stroke: '#333', + // lineWidth: 2, + }, + /* icon configuration */ + icon: { + /* whether show the icon, false by default */ + show: true, + /* icon's img address, string type */ + // img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', + /* icon's size, 20 * 20 by default: */ + // width: 40, + // height: 40 + }, + /* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */ + // nodeStateStyles: { + // // node style of active state + // active: { + // fillOpacity: 0.8, + // }, + // // node style of selected state + // selected: { + // lineWidth: 5, + // }, + // }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'active', false); +}); + +graph.on('node:click', (evt) => { + const { item } = evt; + graph.setItemState(item, 'selected', true); +}); +graph.on('canvas:click', (evt) => { + graph.getNodes().forEach((node) => { + graph.clearItemStates(node); + }); +}); diff --git a/packages/site/examples/item/defaultNodes/index.en.md b/packages/site/examples/item/defaultNodes/index.en.md new file mode 100644 index 0000000000..2423c76d81 --- /dev/null +++ b/packages/site/examples/item/defaultNodes/index.en.md @@ -0,0 +1,12 @@ +--- +title: Built-in Nodes +order: 0 +--- + +There are 8 kinds of built-in nodes in G6, which can be extended by configurations. + +## Usage + +8 built-in nodes and their extensions allow users to select appropriate ones for their scenario. For example, rect node is suitable for flow graph. Parameter `linkPoint` is the link points for related edges. + +Please refer to [Built-in Nodes in G6](/zh/docs/manual/middle/elements/nodes/defaultNode) for more information. diff --git a/packages/site/examples/item/defaultNodes/index.zh.md b/packages/site/examples/item/defaultNodes/index.zh.md new file mode 100644 index 0000000000..8c16c60739 --- /dev/null +++ b/packages/site/examples/item/defaultNodes/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 内置节点 +order: 0 +--- + +G6 共内置了 8 种不同类型的节点,支持通过配置项进行扩展。 + +## 何时使用 + +用户可根据具体的业务场景,选择合适的内置节点。如,矩形节点适合在流程图中使用。 `linkPoints` 用于配置相关边的连入位置。更多内容请参考 [G6 内置的节点](/zh/docs/manual/middle/elements/nodes/defaultNode)。 diff --git a/packages/site/examples/item/label/demo/copyLabel.js b/packages/site/examples/item/label/demo/copyLabel.js new file mode 100644 index 0000000000..72c265c670 --- /dev/null +++ b/packages/site/examples/item/label/demo/copyLabel.js @@ -0,0 +1,290 @@ +import G6 from "@antv/g6"; + +/** + * Process the long label, hover to show the complete label, click the icon to copy the label + * provided by GitHub user @WontonCat + * Thanks for contributing! + */ + + const tipDiv = document.createElement('div'); + tipDiv.innerHTML = `Hover to show the complete label, click the icon to copy the content. Hover 显示完整 label,点击左侧 icon 复制 label 内容`; + document.getElementById('container').appendChild(tipDiv); + + +/** + * format the string + * @param {string} str The origin string + * @param {number} maxWidth max width + * @param {number} fontSize font size + * @return {string} the processed result + */ +const fittingString = (str, maxWidth, fontSize) => { + const ellipsis = "..."; + const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0]; + let currentWidth = 0; + let res = str; + const pattern = new RegExp("[\u4E00-\u9FA5]+"); // distinguish the Chinese charactors and letters + str.split("").forEach((letter, i) => { + if (currentWidth > maxWidth - ellipsisLength) return; + if (pattern.test(letter)) { + // Chinese charactors + currentWidth += fontSize; + } else { + // get the width of single letter according to the fontSize + currentWidth += G6.Util.getLetterWidth(letter, fontSize); + } + if (currentWidth > maxWidth - ellipsisLength) { + res = `${str.substr(0, i)}${ellipsis}`; + } + }); + return res; +}; + +const copyStr = (str) => { + const input = document.createElement("textarea"); + input.value = str; + document.body.appendChild(input); + input.select(); + document.execCommand("Copy"); + document.body.removeChild(input); + alert("Copy Success!"); +}; + +const data = { + nodes: [ + { + x: 100, + y: 0, + id: "node1", + topText: "This label is too long to be displayed", + bottomText: "This label is too long to be displayed" + }, + { + x: 100, + y: 100, + id: "node2", + topText: "Short Label", + bottomText: "Click the Logo to Copy!" + } + ] +}; + +const width = document.getElementById("container").scrollWidth; +const height = document.getElementById("container").scrollHeight || 500; +const graph = new G6.Graph({ + container: "container", + width, + height: height - 50, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + defaultNode: { + type: "copy-node" + }, + defaultEdge: { + color: "#F6BD16" + } +}); + +const nodeHeight = 80; +const nodeWidth = 200; +const fillColor = "#f6e9d7"; +const fontColor = "#ff7900"; +const padding = 7; + +G6.registerNode( + "copy-node", + { + drawShape: function drawShape(cfg, group) { + const rect = group.addShape("rect", { + attrs: { + x: 0, + y: 0, + height: nodeHeight, + width: nodeWidth, + fill: fillColor, + stroke: fontColor, + lineWidth: 2, + radius: 5 + } + }); + + // 上部文字区域 + const topGroup = group.addGroup({ + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: "top-group" + }); + + topGroup.addShape("rect", { + attrs: { + fill: "#fff", + stroke: "#c7d0d1", + x: padding, + y: padding, + width: nodeWidth - padding * 2, + height: 0.5 * nodeHeight - padding, + lineWidth: 1.5, + radius: 4 + } + }); + + topGroup.addShape("text", { + attrs: { + text: fittingString(cfg.topText, nodeWidth - padding * 2 - 10, 14), + x: 0.5 * nodeWidth, + y: (0.5 * nodeHeight + padding) * 0.5, + fontSize: 14, + textAlign: "center", + textBaseline: "middle", + shadowColor: fontColor, + fill: fontColor + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: "top-text" + }); + + topGroup.addShape("image", { + attrs: { + x: padding + 5, + y: padding + (0.5 * nodeHeight - padding) * 0.5 - 10, + height: 20, + width: 20, + img: "https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png", + opacity: 0, + cursor: "pointer" + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: "top-copy-img" + }); + + // 下部文字区域 + const bottomGroup = group.addGroup({ + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: "bottom-group" + }); + + bottomGroup.addShape("text", { + attrs: { + text: fittingString(cfg.bottomText, nodeWidth - 10, 14), + x: 0.5 * nodeWidth, + y: nodeHeight - (0.5 * nodeHeight + padding) * 0.5, + fontSize: 14, + textAlign: "center", + textBaseline: "middle", + shadowColor: fontColor, + fill: fontColor + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: "bottom-text" + }); + + bottomGroup.addShape("image", { + attrs: { + x: 5, + y: 0.5 * nodeHeight + 8, + height: 20, + width: 20, + img: "https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png", + opacity: 0, + cursor: "pointer" + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: "bottom-copy-img" + }); + + return rect; + }, + setState(name, value, item) { + const group = item.get("group"); + const model = item.get("model"); + const { topText, bottomText } = model; + + if (name === "top-group-active") { + const img = group.find((e) => e.get("name") === "top-copy-img"); + img.attr({ + opacity: value ? 1 : 0 + }); + + const text = group.find((e) => e.get("name") === "top-text"); + // 区域是否够长 ? 居中, 展示内容不变 : 左对其, 展示完整 + const cutStr = fittingString(topText, nodeWidth - padding * 2 - 10, 14); + if (cutStr === topText) { + text.attr({ + fontWeight: value ? 800 : 700, + shadowBlur: value ? 12.2 : 0, + text: value + ? topText + : fittingString(topText, nodeWidth - padding * 2 - 10, 14) + }); + } else { + text.attr({ + fontWeight: value ? 800 : 700, + x: value ? padding + 30 : 0.5 * nodeWidth, + shadowBlur: value ? 12.2 : 0, + textAlign: value ? "left" : "center", + text: value + ? topText + : fittingString(topText, nodeWidth - padding * 2 - 10, 14) + }); + } + } + + if (name === "bottom-group-active") { + const img = group.find((e) => e.get("name") === "bottom-copy-img"); + img.attr({ + opacity: value ? 1 : 0 + }); + + const text = group.find((e) => e.get("name") === "bottom-text"); + const cutStr = fittingString(bottomText, nodeWidth - 10, 14); + + if (cutStr === bottomText) { + text.attr({ + fontWeight: value ? 800 : 700, + shadowBlur: value ? 12.2 : 0, + text: value + ? bottomText + : fittingString(bottomText, nodeWidth - 10, 14) + }); + } else { + text.attr({ + fontWeight: value ? 800 : 700, + x: value ? 30 : 0.5 * nodeWidth, + shadowBlur: value ? 12.2 : 0, + textAlign: value ? "left" : "center", + text: value + ? bottomText + : fittingString(bottomText, nodeWidth - 10, 14) + }); + } + } + } + }, + "single-node" +); + +graph.on("top-group:mouseenter", (e) => { + graph.setItemState(e.item, "top-group-active", true); +}); +graph.on("top-group:mouseleave", (e) => { + graph.setItemState(e.item, "top-group-active", false); +}); +graph.on("bottom-group:mouseenter", (e) => { + graph.setItemState(e.item, "bottom-group-active", true); +}); +graph.on("bottom-group:mouseleave", (e) => { + graph.setItemState(e.item, "bottom-group-active", false); +}); + +graph.on("node:click", (e) => { + const name = e.target.get("name"); + const model = e.item.get("model"); + if (name === "top-copy-img") { + const text = model.topText; + copyStr(text); + } else if (name === "bottom-copy-img") { + const text = model.bottomText; + copyStr(text); + } +}); +graph.data(data); +graph.render(); diff --git a/packages/site/examples/item/label/demo/labelLen.js b/packages/site/examples/item/label/demo/labelLen.js new file mode 100644 index 0000000000..ca13468ca3 --- /dev/null +++ b/packages/site/examples/item/label/demo/labelLen.js @@ -0,0 +1,125 @@ +/** + * When the label is too long to be displayed, hide the overflow characters and show a ellipsis instead + * There are two ways to process the long labels + * 1. Process in the data; + * 2. Process when custom the edge or node like this: + * group.addShape('text', { + * attrs: { + * text: fittingString(cfg.label, 50, 12), + * ... + * }, + * // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + * name: 'text-shape' + * }) + * + */ +import G6 from '@antv/g6'; + +/** + * format the string + * @param {string} str The origin string + * @param {number} maxWidth max width + * @param {number} fontSize font size + * @return {string} the processed result + */ +const fittingString = (str, maxWidth, fontSize) => { + const ellipsis = '...'; + const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0]; + let currentWidth = 0; + let res = str; + const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters + str.split('').forEach((letter, i) => { + if (currentWidth > maxWidth - ellipsisLength) return; + if (pattern.test(letter)) { + // Chinese charactors + currentWidth += fontSize; + } else { + // get the width of single letter according to the fontSize + currentWidth += G6.Util.getLetterWidth(letter, fontSize); + } + if (currentWidth > maxWidth - ellipsisLength) { + res = `${str.substr(0, i)}${ellipsis}`; + } + }); + return res; +}; + +const globalFontSize = 12; + +const data = { + nodes: [ + { + x: 100, + y: 100, + size: 40, + label: 'This label is too long to be displayed', + id: 'node1', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + { + x: 300, + y: 100, + size: 80, + label: 'This label is also too long to be displayed', + id: 'node2', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + label: 'This label is too long to be displayed', + labelCfg: { + refY: 20, + style: { + fontSize: globalFontSize, + }, + }, + style: { + endArrow: true, + }, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + defaultNode: { + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + labelCfg: { + style: { + fontSize: globalFontSize, + }, + }, + }, + defaultEdge: { + color: '#F6BD16', + }, +}); + +// Modify the label in the data +data.nodes.forEach(function (node) { + node.label = fittingString(node.label, node.size, globalFontSize); +}); +data.edges.forEach(function (edge) { + edge.label = fittingString(edge.label, 120, globalFontSize); +}); + +graph.data(data); +graph.render(); diff --git a/packages/site/examples/item/label/demo/labelLen1.js b/packages/site/examples/item/label/demo/labelLen1.js new file mode 100644 index 0000000000..40d3f43283 --- /dev/null +++ b/packages/site/examples/item/label/demo/labelLen1.js @@ -0,0 +1,100 @@ +import G6 from '@antv/g6'; +/** + * Process the long label by breaking the text + * by Jingxi + * + */ + +/** + * format the string + * @param {string} str The origin string + * @param {number} maxWidth max width + * @param {number} fontSize font size + * @return {string} the processed result + */ +const fittingString = (str, maxWidth, fontSize) => { + let currentWidth = 0; + let res = str; + const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters + str.split('').forEach((letter, i) => { + if (currentWidth > maxWidth) return; + if (pattern.test(letter)) { + // Chinese charactors + currentWidth += fontSize; + } else { + // get the width of single letter according to the fontSize + currentWidth += G6.Util.getLetterWidth(letter, fontSize); + } + if (currentWidth > maxWidth) { + res = `${str.substr(0, i)}\n${str.substr(i)}`; + } + }); + return res; +}; + +const globalFontSize = 12; +const data = { + nodes: [ + { + x: 100, + y: 100, + label: fittingString('Break the line if it is too long', 80, globalFontSize), + id: 'node1', + labelCfg: { + position: 'bottom', + }, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + { + x: 300, + y: 100, + label: fittingString('Break the line if it is too long', 80, globalFontSize), + id: 'node2', + labelCfg: { + position: 'bottom', + }, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + label: fittingString('Break the line if it is too long', 100, globalFontSize), + labelCfg: { + refY: 20, + }, + style: { + endArrow: true, + }, + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + defaultNode: { + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + color: '#F6BD16', + }, +}); +graph.data(data); +graph.render(); diff --git a/packages/site/examples/item/label/demo/meta.json b/packages/site/examples/item/label/demo/meta.json new file mode 100644 index 0000000000..b69c1a8f4d --- /dev/null +++ b/packages/site/examples/item/label/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "copyLabel.js", + "title": { + "zh": "文本超长交互与复制", + "en": "Long Label Show and Copy" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*O5DCQ4ziMisAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "labelLen.js", + "title": { + "zh": "文本超长", + "en": "Long Label" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*FNzAQKvcW1kAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "labelLen1.js", + "title": { + "zh": "使用换行符处理文本超长", + "en": "Break Line for Long Label" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*EC0-R7tAn0MAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/item/label/index.en.md b/packages/site/examples/item/label/index.en.md new file mode 100644 index 0000000000..165ec9366e --- /dev/null +++ b/packages/site/examples/item/label/index.en.md @@ -0,0 +1,10 @@ +--- +title: Long Label +order: 7 +--- + +Proccess the long label in G6. + +## Usage + +When the label of node or edge has long text, we recommend to calculate the length by JS, and use `\n` to wrap the worlds. G6 has `Util.getLetterWidth` and `Util.getTextSize` to help users to calculate the length of the text, but they are only precise for default font family. diff --git a/packages/site/examples/item/label/index.zh.md b/packages/site/examples/item/label/index.zh.md new file mode 100644 index 0000000000..bb963ad4f7 --- /dev/null +++ b/packages/site/examples/item/label/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 超长文本 +order: 7 +--- + +G6 中文本的处理。 + +## 使用指南 + +节点或边上的文本超长时,可以通过使用 JS 来计算文本长度,也可以通过使用 `\n` 来进行换行。G6 提供了 `Util.getLetterWidth` 与 `Util.getTextSize` 辅助计算文本长度,但仅适用于默认字体。 diff --git a/packages/site/examples/item/labelBg/demo/edgeBg.js b/packages/site/examples/item/labelBg/demo/edgeBg.js new file mode 100644 index 0000000000..d357129059 --- /dev/null +++ b/packages/site/examples/item/labelBg/demo/edgeBg.js @@ -0,0 +1,88 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'node1', + x: 150, + y: 50, + label: 'node1', + }, + { + id: 'node2', + x: 250, + y: 200, + label: 'node2', + }, + { + id: 'node3', + x: 100, + y: 350, + label: 'node3', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + label: 'edge 1', + }, + { + source: 'node2', + target: 'node3', + label: 'edge 2', + }, + { + source: 'node3', + target: 'node1', + label: 'edge 3', + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + defaultNode: { + type: 'circle', + labelCfg: { + position: 'bottom', + }, + }, + defaultEdge: { + labelCfg: { + autoRotate: true, + style: { + fill: '#1890ff', + fontSize: 14, + background: { + fill: '#ffffff', + stroke: '#9EC9FF', + padding: [2, 2, 2, 2], + radius: 2, + }, + }, + }, + }, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + nodeStateStyles: { + // style configurations for hover state + hover: { + fillOpacity: 0.8, + }, + // style configurations for selected state + selected: { + lineWidth: 5, + }, + }, +}); + +graph.data(data); +graph.render(); diff --git a/packages/site/examples/item/labelBg/demo/meta.json b/packages/site/examples/item/labelBg/demo/meta.json new file mode 100644 index 0000000000..e5d24b1d09 --- /dev/null +++ b/packages/site/examples/item/labelBg/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "nodeBg.js", + "title": { + "zh": "设置节点文本背景", + "en": "Set Node Label Background" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*m5ABT5ra4w4AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "edgeBg.js", + "title": { + "zh": "设置边上文本背景", + "en": "Set Edge label Background" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*w_bNSZTlPXUAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/item/labelBg/demo/nodeBg.js b/packages/site/examples/item/labelBg/demo/nodeBg.js new file mode 100644 index 0000000000..2c98a3ef71 --- /dev/null +++ b/packages/site/examples/item/labelBg/demo/nodeBg.js @@ -0,0 +1,83 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'node1', + x: 150, + y: 50, + label: 'node1', + }, + { + id: 'node2', + x: 250, + y: 200, + label: 'node2', + }, + { + id: 'node3', + x: 100, + y: 350, + label: 'node3', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + label: 'edge 1', + }, + { + source: 'node2', + target: 'node3', + label: 'edge 2', + }, + { + source: 'node3', + target: 'node1', + label: 'edge 3', + }, + ], +}; + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + defaultNode: { + type: 'circle', + labelCfg: { + style: { + fill: '#1890ff', + fontSize: 14, + background: { + fill: '#ffffff', + stroke: '#9EC9FF', + padding: [2, 2, 2, 2], + radius: 2, + }, + }, + position: 'bottom', + }, + }, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + nodeStateStyles: { + // style configurations for hover state + hover: { + fillOpacity: 0.8, + }, + // style configurations for selected state + selected: { + lineWidth: 5, + }, + }, +}); + +graph.data(data); +graph.render(); diff --git a/packages/site/examples/item/labelBg/index.en.md b/packages/site/examples/item/labelBg/index.en.md new file mode 100644 index 0000000000..fc57f9dde2 --- /dev/null +++ b/packages/site/examples/item/labelBg/index.en.md @@ -0,0 +1,47 @@ +--- +title: Label Background +order: 8 +--- + +Set the background of the label on Node or Edge. + +## Usage + +Set the background of the label on edge: + +```javascript +const graph = new G6.Graph({ + // ... + defaultNode: { + position: 'left', + style: { + background: { + fill: '#ffffff', + stroke: 'green', + padding: [3, 2, 3, 2], + radius: 2, + lineWidth: 3, + }, + }, + } +}) +``` + +Set the background of the label on node: + +```javascript +const graph = new G6.Graph({ + // ... + defaultEdge: { + autoRotate: true, + style: { + background: { + fill: '#ffffff', + stroke: '#000000', + padding: [2, 2, 2, 2], + radius: 2, + }, + }, + } +}) +``` diff --git a/packages/site/examples/item/labelBg/index.zh.md b/packages/site/examples/item/labelBg/index.zh.md new file mode 100644 index 0000000000..34fe3315d7 --- /dev/null +++ b/packages/site/examples/item/labelBg/index.zh.md @@ -0,0 +1,49 @@ +--- +title: 设置文本背景 +order: 8 +--- + +设置 Node 或 Edge label 的背景。 + +## 使用指南 + +当节点或边上的 label 需要设置背景时,通过在 style 中增加 background 来实现。 + +设置边的背景: + +```javascript +const graph = new G6.Graph({ + // ... + defaultNode: { + position: 'left', + style: { + background: { + fill: '#ffffff', + stroke: 'green', + padding: [3, 2, 3, 2], + radius: 2, + lineWidth: 3, + }, + }, + } +}) +``` + +设置节点 label 的背景: + +```javascript +const graph = new G6.Graph({ + // ... + defaultEdge: { + autoRotate: true, + style: { + background: { + fill: '#ffffff', + stroke: '#000000', + padding: [2, 2, 2, 2], + radius: 2, + }, + }, + } +}) +``` diff --git a/packages/site/examples/item/multiEdge/demo/meta.json b/packages/site/examples/item/multiEdge/demo/meta.json new file mode 100644 index 0000000000..87dbd53f8f --- /dev/null +++ b/packages/site/examples/item/multiEdge/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "multiEdges.js", + "title": { + "zh": "两节点间存在多条边", + "en": "Multiple Edges Between 2 Nodes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*g2p_Qa_wZcIAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/item/multiEdge/demo/multiEdges.js b/packages/site/examples/item/multiEdge/demo/multiEdges.js new file mode 100644 index 0000000000..c48cbeddc5 --- /dev/null +++ b/packages/site/examples/item/multiEdge/demo/multiEdges.js @@ -0,0 +1,91 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'node1', + x: 50, + y: 350, + label: 'A', + }, + { + id: 'node2', + x: 250, + y: 150, + label: 'B', + }, + { + id: 'node3', + x: 450, + y: 350, + label: 'C', + }, + ], + edges: [], +}; + +for (let i = 0; i < 10; i++) { + data.edges.push({ + source: 'node1', + target: 'node2', + label: `${i}th edge of A-B`, + }); +} +for (let i = 0; i < 5; i++) { + data.edges.push({ + source: 'node2', + target: 'node3', + label: `${i}th edge of B-C`, + }); +} + +G6.Util.processParallelEdges(data.edges); + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + // the edges are linked to the center of source and target ndoes + linkCenter: true, + defaultNode: { + type: 'circle', + size: [40], + color: '#5B8FF9', + style: { + fill: '#9EC9FF', + lineWidth: 3, + }, + labelCfg: { + style: { + fill: '#000', + fontSize: 14, + }, + }, + }, + defaultEdge: { + type: 'quadratic', + labelCfg: { + autoRotate: true, + }, + }, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + nodeStateStyles: { + // style configurations for hover state + hover: { + fillOpacity: 0.8, + }, + // style configurations for selected state + selected: { + lineWidth: 5, + }, + }, +}); + +graph.data(data); +graph.render(); diff --git a/packages/site/examples/item/multiEdge/index.en.md b/packages/site/examples/item/multiEdge/index.en.md new file mode 100644 index 0000000000..997c6a0716 --- /dev/null +++ b/packages/site/examples/item/multiEdge/index.en.md @@ -0,0 +1,6 @@ +--- +title: Multiple Edges Between 2 Nodes +order: 9 +--- + +Process the parellel edges whose end nodes are the same by the built-in Util method `G6.Util.processParallelEdges(data.edges)`. If the edges are loop, they will be asigned `type: 'loop'` with different position and loop height to avoid edge overlappings. If the edges are not loop, they will be assigned `type: quadratic` with different control points to avoid edge overlappings. To achieve better rendering result, we recommended configure the `linkCenter` to be `true` while instantiating the Graph. diff --git a/packages/site/examples/item/multiEdge/index.zh.md b/packages/site/examples/item/multiEdge/index.zh.md new file mode 100644 index 0000000000..e65efbffee --- /dev/null +++ b/packages/site/examples/item/multiEdge/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 两节点间存在多条边 +order: 9 +--- + +两节点间存在多条边时,可以使用 G6 内置的工具函数 `G6.Util.processParallelEdges(data.edges)` 处理。它将会自动检测边数据中两端点相同的边。若为自环边,则它们的 `type` 将会被设置为 `'loop'`,并且自动计算自环高度与位置以放置重叠;若不是自环边,则它们的 `type` 将会被设置为 `'quadratic'` 二次贝塞尔曲线,并自动计算控制点位置,以避免相互重叠。为达到更好的展示效果,建议在实例化图时,将 `linkCenter` 设置为 `true`。 diff --git a/packages/site/examples/net/aiLayout/API.en.md b/packages/site/examples/net/aiLayout/API.en.md new file mode 100644 index 0000000000..6a8a6bc10e --- /dev/null +++ b/packages/site/examples/net/aiLayout/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/aiLayout/API.zh.md b/packages/site/examples/net/aiLayout/API.zh.md new file mode 100644 index 0000000000..f873c27ce8 --- /dev/null +++ b/packages/site/examples/net/aiLayout/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/aiLayout/demo/layoutPrediction.js b/packages/site/examples/net/aiLayout/demo/layoutPrediction.js new file mode 100644 index 0000000000..e3bd0d95cf --- /dev/null +++ b/packages/site/examples/net/aiLayout/demo/layoutPrediction.js @@ -0,0 +1,159 @@ +import G6 from '@antv/g6'; +// import by this way in your project. 在您的项目中请这样引入 +// import { GraphLayoutPredict } from '@antv/vis-predict-engine'; + +const { GraphLayoutPredict } = window.GraphLayoutPredict + +const data = { + nodes: [ + { + id: 'node-0', + label: '0', + }, + { + id: 'node-1', + label: '1', + }, + { + id: 'node-2', + label: '2', + }, + { + id: 'node-3', + label: '3', + }, + { + id: 'node-4', + label: '4', + }, + { + id: 'node-5', + label: '5', + }, + { + id: 'node-6', + label: '6', + }, + { + id: 'node-7', + label: '7', + }, + { + id: 'node-8', + label: '8', + }, + { + id: 'node-9', + label: '9', + }, + ], + edges: [ + { + id: 'edge-0', + source: 'node-0', + target: 'node-1', + }, + { + id: 'edge-1', + source: 'node-0', + target: 'node-2', + }, + { + id: 'edge-2', + source: 'node-0', + target: 'node-3', + }, + { + id: 'edge-4', + source: 'node-0', + target: 'node-4', + }, + { + id: 'edge-5', + source: 'node-0', + target: 'node-5', + }, + { + id: 'edge-6', + source: 'node-1', + target: 'node-6', + }, + { + id: 'edge-7', + source: 'node-6', + target: 'node-7', + }, + { + id: 'edge-8', + source: 'node-5', + target: 'node-7', + }, + { + id: 'edge-9', + source: 'node-1', + target: 'node-4', + }, + { + id: 'edge-10', + source: 'node-4', + target: 'node-5', + }, + { + id: 'edge-11', + source: 'node-3', + target: 'node-7', + }, + { + id: 'edge-12', + source: 'node-2', + target: 'node-5', + }, + { + id: 'edge-13', + source: 'node-2', + target: 'node-1', + }, + ], +}; + + +async function layoutPredict() { + return await GraphLayoutPredict.predict(data); +} + +(async function () { + // predictLayout 表示预测的布局 + // confidence 表示预测的可信度 + const { predictLayout, confidence } = await layoutPredict(); + console.log("----predictLayout---", predictLayout); + console.log("----confidence---", confidence); + const container = document.getElementById("container"); + const graph = new G6.Graph({ + container, + width: container.scrollWidth, + height: container.scrollHeight || 500, + layout: { + type: predictLayout, + nodeSize: 50, + preventOverlap: true + }, + modes: { + default: ["drag-canvas", "zoom-canvas", "drag-node"] + } + }); + + graph.data(data); + graph.render(); + graph.on("afterlayout", () => { + graph.fitView(); + }); + + if (typeof window !== "undefined") { + window.onresize = () => { + if (!graph || graph.get("destroyed")) return; + if (!container || !container.scrollWidth || !container.scrollHeight) + return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + } +})(); diff --git a/packages/site/examples/net/aiLayout/demo/meta.json b/packages/site/examples/net/aiLayout/demo/meta.json new file mode 100644 index 0000000000..51227e9701 --- /dev/null +++ b/packages/site/examples/net/aiLayout/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "layoutPrediction.js", + "title": { + "zh": "自动布局推荐", + "en": "Layout Prediction" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*eZRNQ4tpkecAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/aiLayout/index.en.md b/packages/site/examples/net/aiLayout/index.en.md new file mode 100644 index 0000000000..4829567f29 --- /dev/null +++ b/packages/site/examples/net/aiLayout/index.en.md @@ -0,0 +1,26 @@ +--- +title: AI Layout Prediction +order: 20 +--- + +The AntV team encapsulated the ability of graph layout prediction into the NPM package @antv/vis-predict-engine, and used the predict method to predict the layout of the provided data. The basic usage is as follows: + +```javascript +import G6 from '@antv/g6' +import { GraphLayoutPredict } from '@antv/vis-predict-engine' +const data = { + nodes: [], + edges: [] +} +// predictLayout indicates the predicted layout type, such as 'force' or 'radial' +// 'confidence' is the confidence of the prediction +const { predictLayout, confidence } = await GraphLayoutPredict.predict(data); +const graph = new G6.Graph({ + ... // other configurations + layout: { + type: predictLayout + } +}) +``` + +Please refer to [AI Layout Prediction](/en/docs/manual/middle/layout/ai-layout.en.md) diff --git a/packages/site/examples/net/aiLayout/index.zh.md b/packages/site/examples/net/aiLayout/index.zh.md new file mode 100644 index 0000000000..b347b78efe --- /dev/null +++ b/packages/site/examples/net/aiLayout/index.zh.md @@ -0,0 +1,26 @@ +--- +title: AI 智能布局推荐 +order: 20 +--- + +AntV 团队将图布局预测的能力封装成 NPM 包 @antv/vis-predict-engine,通过 predict 方法来预测提供的数据应该使用什么布局,基本用法如下。 + +```javascript +import G6 from '@antv/g6' +import { GraphLayoutPredict } from '@antv/vis-predict-engine' +const data = { + nodes: [], + edges: [] +} +// predictLayout 表示预测的布局,如 force 或 radial +// confidence 表示预测的可信度 +const { predictLayout, confidence } = await GraphLayoutPredict.predict(data); +const graph = new G6.Graph({ + // 省略其他配置 + layout: { + type: predictLayout + } +}) +``` + +更详情的内容请参考[AI 智能布局推荐](/zh/docs/manual/middle/layout/ai-layout.zh.md)。 \ No newline at end of file diff --git a/packages/site/examples/net/arcDiagram/demo/basicArcDiagram.js b/packages/site/examples/net/arcDiagram/demo/basicArcDiagram.js new file mode 100644 index 0000000000..5a131d1d84 --- /dev/null +++ b/packages/site/examples/net/arcDiagram/demo/basicArcDiagram.js @@ -0,0 +1,156 @@ +import G6 from '@antv/g6'; + +const colors = [ + 'rgb(91, 143, 249)', + 'rgb(90, 216, 166)', + 'rgb(93, 112, 146)', + 'rgb(246, 189, 22)', + 'rgb(232, 104, 74)', + 'rgb(109, 200, 236)', + 'rgb(146, 112, 202)', + 'rgb(255, 157, 77)', + 'rgb(38, 154, 153)', + 'rgb(227, 137, 163)', +]; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: [ + { + type: 'edge-tooltip', + formatText: function formatText(model) { + const text = 'source: ' + model.sourceName + '
target: ' + model.targetName; + return text; + }, + }, + ], + }, + defaultNode: { + style: { + opacity: 0.8, + lineWidth: 1, + stroke: '#999', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + opacity: 0.6, + lineAppendWidth: 3, + }, + }, +}); +graph.on('edge:mouseenter', function (e) { + const edge = e.item; + graph.setItemState(edge, 'hover', true); +}); +graph.on('edge:mouseleave', function (e) { + const edge = e.item; + graph.setItemState(edge, 'hover', false); +}); + +fetch('https://gw.alipayobjects.com/os/basement_prod/70cde3be-22e8-4291-98f1-4d5a5b75b62f.json') + .then((res) => res.json()) + .then((data) => { + const edges = data.edges; + const nodes = data.nodes; + const nodeMap = new Map(); + const clusterMap = new Map(); + let clusterId = 0; + const n = nodes.length; + const horiPadding = 10; + const begin = [horiPadding, height * 0.7]; + const end = [width - horiPadding, height * 0.7]; + const xLength = end[0] - begin[0]; + const yLength = end[1] - begin[1]; + const xSep = xLength / n; + const ySep = yLength / n; + nodes.forEach(function (node, i) { + node.x = begin[0] + i * xSep; + node.y = begin[1] + i * ySep; + nodeMap.set(node.id, node); + // cluster + if (node.cluster && clusterMap.get(node.cluster) === undefined) { + clusterMap.set(node.cluster, clusterId); + clusterId++; + } + const id = clusterMap.get(node.cluster); + if (node.style) { + node.style.fill = colors[id % colors.length]; + } else { + node.style = { + fill: colors[id % colors.length], + }; + } + // label + node.label = node.name; + node.labelCfg = { + position: 'bottom', + offset: 5, + style: { + rotate: Math.PI / 2, + textAlign: 'start', + }, + }; + }); + edges.forEach((edge) => { + edge.type = 'arc'; + const source = nodeMap.get(edge.source); + const target = nodeMap.get(edge.target); + const endsSepStep = (target.x - source.x) / xSep; + const sign = endsSepStep < 0 ? -1 : 1; + const curveOffset = sign * 10 * Math.ceil(Math.abs(endsSepStep)); + edge.curveOffset = curveOffset; + edge.color = source.style.fill; + edge.sourceName = source.name; + edge.targetName = target.name; + }); + + // map the value to node size + let maxValue = -9999, + minValue = 9999; + nodes.forEach(function (n) { + if (maxValue < n.value) maxValue = n.value; + if (minValue > n.value) minValue = n.value; + }); + const sizeRange = [3, 25]; + const sizeDataRange = [minValue, maxValue]; + scaleNodeProp(nodes, 'size', 'value', sizeDataRange, sizeRange); + + graph.data(data); + graph.render(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + + // const tooltip = document.getElementsByClassName('g6-tooltip')[0]; + // if (tooltip) { + // tooltip.style.border = '1px solid #e2e2e2'; + // tooltip.style.borderRadius = '4px'; + // tooltip.style.fontSize = '12px'; + // tooltip.style.color = '#545454'; + // tooltip.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'; + // tooltip.style.padding = '10px 8px'; + // tooltip.style.boxShadow = 'rgb(174, 174, 174) 0px 0px 10px'; + // } + }); + +function scaleNodeProp(nodes, propName, refPropName, dataRange, outRange) { + const outLength = outRange[1] - outRange[0]; + const dataLength = dataRange[1] - dataRange[0]; + nodes.forEach(function (n) { + n[propName] = ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0]; + }); +} diff --git a/packages/site/examples/net/arcDiagram/demo/circularArcDiagram.js b/packages/site/examples/net/arcDiagram/demo/circularArcDiagram.js new file mode 100644 index 0000000000..16f410f8da --- /dev/null +++ b/packages/site/examples/net/arcDiagram/demo/circularArcDiagram.js @@ -0,0 +1,145 @@ +import G6 from '@antv/g6'; + +const colors = [ + 'rgb(91, 143, 249)', + 'rgb(90, 216, 166)', + 'rgb(93, 112, 146)', + 'rgb(246, 189, 22)', + 'rgb(232, 104, 74)', + 'rgb(109, 200, 236)', + 'rgb(146, 112, 202)', + 'rgb(255, 157, 77)', + 'rgb(38, 154, 153)', + 'rgb(227, 137, 163)', +]; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: [ + { + type: 'edge-tooltip', + formatText: function formatText(model) { + const text = 'source: ' + model.sourceName + '
target: ' + model.targetName; + return text; + }, + }, + ], + }, + defaultNode: { + style: { + opacity: 0.8, + lineWidth: 1, + stroke: '#999', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + opacity: 0.6, + lineAppendWidth: 3, + }, + }, +}); +graph.on('edge:mouseenter', function (e) { + const edge = e.item; + graph.setItemState(edge, 'hover', true); +}); +graph.on('edge:mouseleave', function (e) { + const edge = e.item; + graph.setItemState(edge, 'hover', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + +fetch('https://gw.alipayobjects.com/os/basement_prod/70cde3be-22e8-4291-98f1-4d5a5b75b62f.json') + .then((res) => res.json()) + .then((data) => { + const origin = [width / 2, height / 2]; + const radius = width < height ? width / 3 : height / 3; + const edges = data.edges; + const nodes = data.nodes; + const nodeMap = new Map(); + const clusterMap = new Map(); + let clusterId = 0; + const n = nodes.length; + const angleSep = (Math.PI * 2) / n; + nodes.forEach(function (node, i) { + const angle = i * angleSep; + node.x = radius * Math.cos(angle) + origin[0]; + node.y = radius * Math.sin(angle) + origin[1]; + node.angle = angle; + nodeMap.set(node.id, node); + // cluster + if (node.cluster && clusterMap.get(node.cluster) === undefined) { + clusterMap.set(node.cluster, clusterId); + clusterId++; + } + const id = clusterMap.get(node.cluster); + if (node.style) { + node.style.fill = colors[id % colors.length]; + } else { + node.style = { + fill: colors[id % colors.length], + }; + } + // label + node.label = node.name; + node.labelCfg = { + position: 'center', + style: { + rotate: angle, + rotateCenter: 'lefttop', + textAlign: 'start', + }, + }; + }); + edges.forEach((edge) => { + edge.type = 'quadratic'; + const source = nodeMap.get(edge.source); + const target = nodeMap.get(edge.target); + edge.controlPoints = [ + { + x: origin[0], + y: origin[1], + }, + ]; + edge.color = source.style.fill; + edge.sourceName = source.name; + edge.targetName = target.name; + }); + + // map the value to node size + let maxValue = -9999, + minValue = 9999; + nodes.forEach(function (n) { + if (maxValue < n.value) maxValue = n.value; + if (minValue > n.value) minValue = n.value; + }); + const sizeRange = [3, 30]; + const sizeDataRange = [minValue, maxValue]; + scaleNodeProp(nodes, 'size', 'value', sizeDataRange, sizeRange); + + graph.data(data); + graph.render(); + }); + +function scaleNodeProp(nodes, propName, refPropName, dataRange, outRange) { + const outLength = outRange[1] - outRange[0]; + const dataLength = dataRange[1] - dataRange[0]; + nodes.forEach(function (n) { + n[propName] = ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0]; + }); +} diff --git a/packages/site/examples/net/arcDiagram/demo/meta.json b/packages/site/examples/net/arcDiagram/demo/meta.json new file mode 100644 index 0000000000..7cbd0a2f79 --- /dev/null +++ b/packages/site/examples/net/arcDiagram/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicArcDiagram.js", + "title": { + "zh": "基本弧线图", + "en": "Basic Arc Diagram" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*_eivQrJXt8sAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "circularArcDiagram.js", + "title": { + "zh": "环形弧线图", + "en": "Circular Arc Diagram" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*o-tESYnAAJYAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/arcDiagram/index.en.md b/packages/site/examples/net/arcDiagram/index.en.md new file mode 100644 index 0000000000..b1db63ee7a --- /dev/null +++ b/packages/site/examples/net/arcDiagram/index.en.md @@ -0,0 +1,11 @@ +--- +title: Arc Diagram +order: 8 +--- + +Arc diagram is a method of graph visualization. It places the nodes on one axis, and connects the nodes by arcs. + +## Usage + +- Example 1 : Place the node positions to an axis, and calculate the heights of the arcs according to the distances between endpoints. +- Example 2 : Circular Arc Diagram is a deformation of Arc Diagram. In this example, the nodes are placed on a circle, the edges are quadratic bezier curves. diff --git a/packages/site/examples/net/arcDiagram/index.zh.md b/packages/site/examples/net/arcDiagram/index.zh.md new file mode 100644 index 0000000000..fbb1f6e966 --- /dev/null +++ b/packages/site/examples/net/arcDiagram/index.zh.md @@ -0,0 +1,11 @@ +--- +title: Arc Diagram 弧线图 +order: 8 +--- + +弧线图是一种图的可视化形式。其将节点排列在一个轴上,边以弧线的形式连接节点。 + +## 使用指南 + +- 代码演示 1 :通过指定节点位置,并根据节点间距计算 arc 弧线类型边的弧高度可以实现基础弧线图。 +- 代码演示 2 :指定节点位置到圆环上,使用 quadratic 二阶贝塞尔曲线类型的边实现了弧线图的变种 —— 环形弧线图。 diff --git a/packages/site/examples/net/circular/API.en.md b/packages/site/examples/net/circular/API.en.md new file mode 100644 index 0000000000..a2253244c4 --- /dev/null +++ b/packages/site/examples/net/circular/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/circular/API.zh.md b/packages/site/examples/net/circular/API.zh.md new file mode 100644 index 0000000000..e148d9173f --- /dev/null +++ b/packages/site/examples/net/circular/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/circular/demo/basicCircular.js b/packages/site/examples/net/circular/demo/basicCircular.js new file mode 100644 index 0000000000..5ecb89100e --- /dev/null +++ b/packages/site/examples/net/circular/demo/basicCircular.js @@ -0,0 +1,412 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'circular', + }, + animate: true, + defaultNode: { + size: 20, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/circular/demo/circularConfigurationTranslate.js b/packages/site/examples/net/circular/demo/circularConfigurationTranslate.js new file mode 100644 index 0000000000..73aa4a5976 --- /dev/null +++ b/packages/site/examples/net/circular/demo/circularConfigurationTranslate.js @@ -0,0 +1,500 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Circular layout with radius: take full use of the canvas, ordering: topology'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'circular', + }, + animate: true, + defaultNode: { + size: 20, + }, + defaultEdge: { + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; + +layoutConfigTranslation(); + +setInterval(function () { + layoutConfigTranslation(); +}, 11500); + +function layoutConfigTranslation() { + setTimeout(function () { + descriptionDiv.innerHTML = 'Circular layout, radius = 200, divisions = 5, ordering: degree'; + graph.updateLayout({ + radius: 200, + startAngle: Math.PI / 4, + endAngle: Math.PI, + divisions: 5, + ordering: 'degree', + }); + }, 1000); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Circular layout, radius = 200, divisions = 3, ordering: degree'; + graph.updateLayout({ + startAngle: Math.PI / 4, + endAngle: Math.PI, + divisions: 3, + }); + }, 2500); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Circular layout, radius = 200, divisions = 8, ordering: degree'; + graph.updateLayout({ + radius: 200, + startAngle: 0, + endAngle: Math.PI / 2, + divisions: 8, + }); + }, 4000); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Circular layout, radius = 10~300(spiral), endAngle: PI, divisions = 1, ordering: degree'; + graph.updateLayout({ + radius: null, + startRadius: 10, + endRadius: 300, + divisions: 1, + startAngle: 0, + endAngle: Math.PI, + }); + }, 5500); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Circular layout, radius = 10~300(spiral),endAngle: 2 * PI, divisions= 1, ordering: degree'; + graph.updateLayout({ + endAngle: 2 * Math.PI, + }); + }, 7000); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Circular layout, radius = 200, ordering: degree'; + graph.updateLayout({ + radius: 200, + }); + }, 8500); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Circular layout, radius = 200, ordering: topology'; + graph.updateLayout({ + radius: 200, + ordering: 'topology', + }); + }, 10000); +} diff --git a/packages/site/examples/net/circular/demo/degreeCircular.js b/packages/site/examples/net/circular/demo/degreeCircular.js new file mode 100644 index 0000000000..d13302dbbe --- /dev/null +++ b/packages/site/examples/net/circular/demo/degreeCircular.js @@ -0,0 +1,421 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'circular', + ordering: 'degree', + }, + animate: true, + defaultNode: { + size: 20, + }, + defaultEdge: { + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/circular/demo/divisionCircular.js b/packages/site/examples/net/circular/demo/divisionCircular.js new file mode 100644 index 0000000000..c0fd143fb3 --- /dev/null +++ b/packages/site/examples/net/circular/demo/divisionCircular.js @@ -0,0 +1,424 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'circular', + divisions: 5, + radius: 200, + startAngle: Math.PI / 4, + endAngle: Math.PI, + }, + animate: true, + defaultNode: { + size: 20, + }, + defaultEdge: { + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/circular/demo/meta.json b/packages/site/examples/net/circular/demo/meta.json new file mode 100644 index 0000000000..ec677dd03d --- /dev/null +++ b/packages/site/examples/net/circular/demo/meta.json @@ -0,0 +1,48 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicCircular.js", + "title": { + "zh": "基本 Circular 布局", + "en": "Basic Circular Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ZSuXQ4PS2F8AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "degreeCircular.js", + "title": { + "zh": "按照节点度数排序的 Circular 布局", + "en": "Degree Ordered Circular" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*MOEeS6ooB7AAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "spiralCircular.js", + "title": { + "zh": "螺旋线布局", + "en": "Spiral Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*bY0iSqTc3z4AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "divisionCircular.js", + "title": { + "zh": "分割环形布局", + "en": "Divided Circular Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*u30nQahg6q0AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "circularConfigurationTranslate.js", + "title": { + "zh": "Circular 布局参数动态变化", + "en": "Update Configurations for Circular Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*D85cS7-yqNEAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/circular/demo/spiralCircular.js b/packages/site/examples/net/circular/demo/spiralCircular.js new file mode 100644 index 0000000000..d88d211b28 --- /dev/null +++ b/packages/site/examples/net/circular/demo/spiralCircular.js @@ -0,0 +1,422 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'circular', + startRadius: 10, + endRadius: 300, + }, + animate: true, + defaultNode: { + size: 20, + }, + defaultEdge: { + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/circular/index.en.md b/packages/site/examples/net/circular/index.en.md new file mode 100644 index 0000000000..054f6c07b3 --- /dev/null +++ b/packages/site/examples/net/circular/index.en.md @@ -0,0 +1,16 @@ +--- +title: Circular Layout +order: 4 +--- + +Circular layout orders the nodes according to the configuration, and then places the nodes on a circle. + +## Usage + +As the demo below, you can deploy it in `layout` while instantiating Graph. it can also be used for [Subgraph Layout](/en/docs/manual/middle/layout/sub-layout). By tuning the configurations, you can adjust the radius, start angle, end angle, nodes' order method, divisions, spiral style, and so on. + +- Example 1 : Basic Circular Layout, the nodes are placed on the circle clockwise in the data order. +- Example 2 : The nodes are placed on the circle clockwise according to their degrees. +- Example 3 : Spiral layout. +- Example 4 : Divide the nodes into several divisions on the circle. +- Example 5 : Translate the parameters in dynamic. diff --git a/packages/site/examples/net/circular/index.zh.md b/packages/site/examples/net/circular/index.zh.md new file mode 100644 index 0000000000..b224981cfb --- /dev/null +++ b/packages/site/examples/net/circular/index.zh.md @@ -0,0 +1,16 @@ +--- +title: Circular 环形布局 +order: 4 +--- + +Circular 环形布局根据参数指定的排序方式对节点进行排序后,将节点排列在圆环上。 + +## 使用指南 + +G6 内置的 Circular 环形布局可在实例化 Graph 时使用该布局。除此之外,还可以如[子图布局](/zh/docs/manual/middle/layout/sub-layout)所示单独使用布局。该布局可以通过配置调整半径、起始和结束角度、节点排序方式、节点分组、螺旋线布局等。 + +- 代码演示 1 :基本的环形布局,节点根据在数据中的顺序逆时针排列。 +- 代码演示 2 :节点根据其度数从大到小逆时针排列。 +- 代码演示 3 :螺旋线布局。 +- 代码演示 4 :分组圆环布局。 +- 代码演示 5 :圆环布局参数动态变化。 diff --git a/packages/site/examples/net/comboLayout/API.en.md b/packages/site/examples/net/comboLayout/API.en.md new file mode 100644 index 0000000000..1fd06ec46f --- /dev/null +++ b/packages/site/examples/net/comboLayout/API.en.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +## ComboCombined + + + +## ComboForce + + diff --git a/packages/site/examples/net/comboLayout/API.zh.md b/packages/site/examples/net/comboLayout/API.zh.md new file mode 100644 index 0000000000..89a35b5c50 --- /dev/null +++ b/packages/site/examples/net/comboLayout/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +## ComboCombined - Combo 组合布局 + + + +## ComboForce - Combo 力导布局 + + diff --git a/packages/site/examples/net/comboLayout/demo/basicComboForce.js b/packages/site/examples/net/comboLayout/demo/basicComboForce.js new file mode 100644 index 0000000000..f421677078 --- /dev/null +++ b/packages/site/examples/net/comboLayout/demo/basicComboForce.js @@ -0,0 +1,473 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + comboId: 'a', + }, + { + id: '1', + comboId: 'a', + }, + { + id: '2', + comboId: 'a', + }, + { + id: '3', + comboId: 'a', + }, + { + id: '4', + comboId: 'a', + }, + { + id: '5', + comboId: 'a', + }, + { + id: '6', + comboId: 'a', + }, + { + id: '7', + comboId: 'a', + }, + { + id: '8', + comboId: 'a', + }, + { + id: '9', + comboId: 'a', + }, + { + id: '10', + comboId: 'a', + }, + { + id: '11', + comboId: 'a', + }, + { + id: '12', + comboId: 'a', + }, + { + id: '13', + comboId: 'a', + }, + { + id: '14', + comboId: 'a', + }, + { + id: '15', + comboId: 'a', + }, + { + id: '16', + comboId: 'b', + }, + { + id: '17', + comboId: 'b', + }, + { + id: '18', + comboId: 'b', + }, + { + id: '19', + comboId: 'b', + }, + { + id: '20', + }, + { + id: '21', + }, + { + id: '22', + }, + { + id: '23', + comboId: 'c', + }, + { + id: '24', + comboId: 'a', + }, + { + id: '25', + }, + { + id: '26', + }, + { + id: '27', + comboId: 'c', + }, + { + id: '28', + comboId: 'c', + }, + { + id: '29', + comboId: 'c', + }, + { + id: '30', + comboId: 'c', + }, + { + id: '31', + comboId: 'c', + }, + { + id: '32', + comboId: 'd', + }, + { + id: '33', + comboId: 'd', + }, + ], + edges: [ + { + source: 'a', + target: 'b', + label: 'Combo A - Combo B', + size: 3, + labelCfg: { + autoRotate: true, + style: { + stroke: '#fff', + lineWidth: 5, + fontSize: 20, + }, + }, + style: { + stroke: 'red', + }, + }, + { + source: 'a', + target: '33', + label: 'Combo-Node', + size: 3, + labelCfg: { + autoRotate: true, + style: { + stroke: '#fff', + lineWidth: 5, + fontSize: 20, + }, + }, + style: { + stroke: 'blue', + }, + }, + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], + combos: [ + { + id: 'a', + label: 'Combo A', + }, + { + id: 'b', + label: 'Combo B', + }, + { + id: 'c', + label: 'Combo D', + }, + { + id: 'd', + label: 'Combo D', + parentId: 'b', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + fitViewPadding: 50, + minZoom: 0.00000001, + layout: { + type: 'comboForce', + nodeSpacing: (d) => 8, + }, + defaultNode: { + size: 15, + color: '#5B8FF9', + style: { + lineWidth: 2, + fill: '#C6E5FF', + }, + }, + defaultEdge: { + size: 2, + color: '#e2e2e2', + }, + modes: { + default: ['drag-combo', 'drag-node', 'drag-canvas', 'zoom-canvas'], + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/comboLayout/demo/comboCombined.js b/packages/site/examples/net/comboLayout/demo/comboCombined.js new file mode 100644 index 0000000000..b0d2b4c5a7 --- /dev/null +++ b/packages/site/examples/net/comboLayout/demo/comboCombined.js @@ -0,0 +1,641 @@ +import G6 from '@antv/g6'; + +// define the CSS with the id of your menu +insertCss(` + .g6-component-contextmenu { + position: absolute; + list-style-type: none; + padding: 10px 8px; + left: -150px; + background-color: rgba(255, 255, 255, 0.9); + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + } + .g6-component-contextmenu span { + cursor: pointer; + list-style-type:none; + list-style: none; + margin-left: 0px; + } + .g6-component-contextmenu span:hover { + color: #5B8FF9; + } +`); + +const tipDiv = document.createElement('div'); +tipDiv.innerHTML = `Double click to collapse/expand a combo; Context menu with right click to uncombo/re-combo. 双击 Combo 进行展开/收起;右键菜单选择解散/重组`; +document.getElementById('container').appendChild(tipDiv); + +const data = { + nodes: [ + { + id: '0', + comboId: 'a', + }, + { + id: '1', + comboId: 'a', + }, + { + id: '2', + comboId: 'a', + }, + { + id: '3', + comboId: 'a', + }, + { + id: '4', + comboId: 'a', + }, + { + id: '5', + comboId: 'a', + }, + { + id: '6', + comboId: 'a', + }, + { + id: '7', + comboId: 'a', + }, + { + id: '8', + comboId: 'a', + }, + { + id: '9', + comboId: 'a', + }, + { + id: '10', + comboId: 'a', + }, + { + id: '11', + comboId: 'a', + }, + { + id: '12', + comboId: 'a', + }, + { + id: '13', + comboId: 'a', + }, + { + id: '14', + comboId: 'a', + }, + { + id: '15', + comboId: 'a', + }, + { + id: '16', + comboId: 'b', + }, + { + id: '17', + comboId: 'b', + }, + { + id: '18', + comboId: 'b', + }, + { + id: '19', + comboId: 'b', + }, + { + id: '20', + }, + { + id: '21', + }, + { + id: '22', + }, + { + id: '23', + comboId: 'c', + }, + { + id: '24', + comboId: 'a', + }, + { + id: '25', + }, + { + id: '26', + }, + { + id: '27', + comboId: 'c', + }, + { + id: '28', + comboId: 'c', + }, + { + id: '29', + comboId: 'c', + }, + { + id: '30', + comboId: 'c', + }, + { + id: '31', + comboId: 'c', + }, + { + id: '32', + comboId: 'd', + }, + { + id: '33', + comboId: 'd', + }, + ], + edges: [ + { + source: 'a', + target: 'b', + label: 'Combo A - Combo B', + size: 3, + labelCfg: { + autoRotate: true, + style: { + stroke: '#fff', + lineWidth: 5, + fontSize: 20, + }, + }, + style: { + stroke: 'red', + }, + }, + { + source: 'a', + target: '33', + label: 'Combo-Node', + size: 3, + labelCfg: { + autoRotate: true, + style: { + stroke: '#fff', + lineWidth: 5, + fontSize: 20, + }, + }, + style: { + stroke: 'blue', + }, + }, + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], + combos: [ + { + id: 'a', + label: 'Combo A', + }, + { + id: 'b', + label: 'Combo B', + }, + { + id: 'c', + label: 'Combo C', + }, + { + id: 'd', + label: 'Combo D', + parentId: 'b', + }, + ], +}; + +// cache the initial combo children infomation +const comboChildrenCache = {}; +// cache the initial parent infomation +const itemComboMap = {}; +// cache the initial node and combo info +const itemMap = {}; +// cache the combo related edges +const comboEdges = {}; +(data.nodes.concat(data.combos)).forEach(item => { + const { id, comboId, parentId } = item; + const parentComboId = comboId || parentId; + if (parentComboId) { + if (!comboChildrenCache[parentComboId]) comboChildrenCache[parentComboId] = []; + comboChildrenCache[parentComboId].push(id); + itemComboMap[id] = parentComboId; + } + itemMap[id] = { ...item }; +}); +const comboIds = data.combos.map(combo => combo.id); +data.edges.forEach(edge => { + const { source, target } = edge; + [source, target].forEach(endId => { + if (comboIds.includes(endId)) { + if (!comboEdges[endId]) comboEdges[endId] = []; + comboEdges[endId].push(edge); + } + }) +}); + +// colorize the nodes and combos +const subjectColors = [ + '#5F95FF', // blue + '#61DDAA', + '#65789B', + '#F6BD16', + '#7262FD', + '#78D3F8', + '#9661BC', + '#F6903D', + '#008685', + '#F08BB4', +]; +const backColor = '#fff'; +const theme = 'default'; +const disableColor = '#777'; +const colorSets = G6.Util.getColorSetsBySubjectColors( + subjectColors, + backColor, + theme, + disableColor, +); +data.combos.forEach((combo, i) => { + const color = colorSets[i % colorSets.length]; + combo.style = { + stroke: color.mainStroke, + fill: color.mainFill, + opacity: 0.8 + } + itemMap[combo.id].style = { ...combo.style } +}) +data.nodes.forEach(node => { + const comboId = itemComboMap[node.id]; + const parentCombo = itemMap[comboId]; + if (parentCombo) { + node.style = { + stroke: parentCombo.style.stroke, + fill: parentCombo.style.fill + } + } +}) + +const contextMenu = new G6.Menu({ + itemTypes: ['combo', 'node'], + shouldBegin: (evt) => { + // avoid showing up context menu in some situations + const type = evt.item.getType(); + const { id, comboId, collapsed } = evt.item.getModel(); + if (collapsed) return false; + + const hasOriComboId = Object.values(comboChildrenCache).find(childrenIds => childrenIds.includes(id)); + if (type === 'node' && (comboId || !hasOriComboId)) return false; + return true; + }, + getContent: (evt) => { + const type = evt.item.getType(); + const { id, comboId, parentId, collapsed } = evt.item.getModel(); + const hasOriComboId = Object.values(comboChildrenCache).find(childrenIds => childrenIds.includes(id)); + + if (type === 'combo') { + // no context menu for collapsed combo + if (collapsed) return '' + // does not have parent currently but had parent at initial + if (hasOriComboId && !parentId) return `uncombo
re-combo`; + // did not have parent at initail + return `uncombo`; + } + + // has combo currently + if (comboId) return ''; + // does not have combo but had combo at initial + if (hasOriComboId) return `re-combo`; + return ''; + }, + handleMenuClick: (target, item) => { + if (target.innerHTML === 'uncombo') { + graph.uncombo(item); + graph.layout(); + } else { + const id = item.getID(); + const comboId = itemComboMap[id]; + if (comboId) { + const childrenIds = comboChildrenCache[comboId].filter(cid => !!graph.findById(cid)); + graph.createCombo({ + ...itemMap[comboId] + }, childrenIds); + // add the related edges back + comboEdges[comboId]?.forEach(edge => { + const { source, target } = edge; + const otherEnd = source === comboId ? target : source; + // add it back only when the other end of the edge exist currently + if (graph.findById(otherEnd)) { + graph.addItem('edge', edge); + } + }); + graph.layout(); + } + } + }, +}) + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 160; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + fitViewPadding: 50, + animate: true, + minZoom: 0.00000001, + plugins: [contextMenu], + layout: { + type: 'comboCombined', + spacing: 5, + outerLayout: new G6.Layout['forceAtlas2']({ + kr: 10 + }) + }, + defaultNode: { + size: 15, + style: { + lineWidth: 2, + fill: '#C6E5FF', + }, + }, + defaultEdge: { + size: 2, + color: '#e2e2e2', + }, + defaultCombo: { + collapsedSubstituteIcon: { + show: true, + img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IEQFS5VtXX8AAAAAAAAAAABkARQnAQ', + width: 68, + height: 68 + } + }, + modes: { + default: ['drag-combo', 'drag-node', 'drag-canvas', 'zoom-canvas', 'collapse-expand-combo'], + }, +}); +graph.data(data); +graph.render(); +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/comboLayout/demo/meta.json b/packages/site/examples/net/comboLayout/demo/meta.json new file mode 100644 index 0000000000..91d854629d --- /dev/null +++ b/packages/site/examples/net/comboLayout/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "comboCombined.js", + "title": { + "zh": "ComboCombined 组合布局", + "en": "Combo Combined Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*odTCQLZkqxIAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "basicComboForce.js", + "title": { + "zh": "基本 Combo 力导向布局", + "en": "Basic Combo Force Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GK34T7F1_CYAAAAAAAAAAABkARQnAQ" + } + ] +} \ No newline at end of file diff --git a/packages/site/examples/net/comboLayout/index.en.md b/packages/site/examples/net/comboLayout/index.en.md new file mode 100644 index 0000000000..f9930ac3c7 --- /dev/null +++ b/packages/site/examples/net/comboLayout/index.en.md @@ -0,0 +1,12 @@ +--- +title: Combo Related Layout +order: 10 +--- + +_New feature of V4.6_ Designed for graph with combos. Support configuring the layout for items inside a combo and the layout for the outer combos and nodes. + +_New feature of V3.5._ Combo Force is designed for the graph with combos based on classical force directed layout algorith. It modifies the forces between nodes according to their combo infomation to achieve a final result with clustering nodes inside each combo and no overlappings. + +## Usage + +We suggest to use `'comboCombined'` or `'comboForce'` for the graph with combos. Other layouts in G6 are also availabel, but they do not consider the combo infomation. diff --git a/packages/site/examples/net/comboLayout/index.zh.md b/packages/site/examples/net/comboLayout/index.zh.md new file mode 100644 index 0000000000..a4610de429 --- /dev/null +++ b/packages/site/examples/net/comboLayout/index.zh.md @@ -0,0 +1,12 @@ +--- +title: Combo 相关布局 +order: 10 +--- + +*V4.6.0 新增功能。*是 G6 自研的、适用于带有 combo 的图,可自由组合内外布局,默认情况下,内部使用同心圆布局,外部使用力导向布局,可以有较好的效果,推荐有 combo 的图使用该布局。 + +*V3.5 新增功能。*Combo Force 是基于力导向的专用于带有 combo 的图的布局算法。通过自研改造经典力导向算法,将根据节点的 combo 信息,施加不同的力以达到同 combo 节点尽可能聚集,不同 combo 之间尽可能无重叠的布局。 + +## 使用指南 + +在有 Combo 的图上,推荐使用 `'comboCombined'` 或 `'comboForce'` 布局。其他内置布局将不会考虑 Combo 信息对布局的影响。 diff --git a/packages/site/examples/net/concentricLayout/API.en.md b/packages/site/examples/net/concentricLayout/API.en.md new file mode 100644 index 0000000000..460fe37bbe --- /dev/null +++ b/packages/site/examples/net/concentricLayout/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/concentricLayout/API.zh.md b/packages/site/examples/net/concentricLayout/API.zh.md new file mode 100644 index 0000000000..7f3d4ae3c8 --- /dev/null +++ b/packages/site/examples/net/concentricLayout/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/concentricLayout/demo/basicConcentric.js b/packages/site/examples/net/concentricLayout/demo/basicConcentric.js new file mode 100644 index 0000000000..101f0711f0 --- /dev/null +++ b/packages/site/examples/net/concentricLayout/demo/basicConcentric.js @@ -0,0 +1,36 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['zoom-canvas', 'drag-canvas', 'drag-node'], + }, + layout: { + type: 'concentric', + maxLevelDiff: 0.5, + sortBy: 'degree', + }, + animate: true, + defaultNode: { + size: 5, + }, +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + +fetch('https://gw.alipayobjects.com/os/basement_prod/8dacf27e-e1bc-4522-b6d3-4b6d9b9ed7df.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + }); diff --git a/packages/site/examples/net/concentricLayout/demo/meta.json b/packages/site/examples/net/concentricLayout/demo/meta.json new file mode 100644 index 0000000000..64ef1e7fb9 --- /dev/null +++ b/packages/site/examples/net/concentricLayout/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicConcentric.js", + "title": { + "zh": "基本 Concentric 同心圆布局", + "en": "Basic Concentric Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*QpyPTbBpO2AAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/concentricLayout/index.en.md b/packages/site/examples/net/concentricLayout/index.en.md new file mode 100644 index 0000000000..c23ebe38a3 --- /dev/null +++ b/packages/site/examples/net/concentricLayout/index.en.md @@ -0,0 +1,10 @@ +--- +title: Concentric Layout +order: 6 +--- + +Concentric Layout places the nodes on concentric circles. + +## Usage + +As the demo below, you can deploy it in `layout` while instantiating Graph. it can also be used for [Subgraph Layout](/en/docs/manual/middle/layout/sub-layout). This algorithm will order the nodes according to the parameters first, then the node in the front of the order will be placed on the center of the concentric circles. diff --git a/packages/site/examples/net/concentricLayout/index.zh.md b/packages/site/examples/net/concentricLayout/index.zh.md new file mode 100644 index 0000000000..704c23969f --- /dev/null +++ b/packages/site/examples/net/concentricLayout/index.zh.md @@ -0,0 +1,10 @@ +--- +title: Concentric 同心圆布局 +order: 6 +--- + +Concentric 同心圆布局将所有节点放置在同心圆上。 + +## 使用指南 + +G6 内置的 Concentric 同心圆布局可在实例化 Graph 时使用该布局。除此之外,还可以如[子图布局](/zh/docs/manual/middle/layout/sub-layout)所示单独使用布局。该算法首先根据参数指定的排序方式对节点进行排序。排序越靠前,节点将会被放置在越中心的位置。 diff --git a/packages/site/examples/net/dagreFlow/API.en.md b/packages/site/examples/net/dagreFlow/API.en.md new file mode 100644 index 0000000000..364934cf22 --- /dev/null +++ b/packages/site/examples/net/dagreFlow/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/dagreFlow/API.zh.md b/packages/site/examples/net/dagreFlow/API.zh.md new file mode 100644 index 0000000000..01d6b15fe2 --- /dev/null +++ b/packages/site/examples/net/dagreFlow/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/dagreFlow/demo/basicDagre.js b/packages/site/examples/net/dagreFlow/demo/basicDagre.js new file mode 100644 index 0000000000..1ef3306efa --- /dev/null +++ b/packages/site/examples/net/dagreFlow/demo/basicDagre.js @@ -0,0 +1,310 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-tooltip { + border-radius: 6px; + font-size: 12px; + color: #fff; + background-color: #000; + padding: 2px 8px; + text-align: center; + } +`); + +const data = { + nodes: [ + { + id: '1', + dataType: 'alps', + name: 'alps_file1', + conf: [ + { + label: 'conf', + value: 'pai_graph.conf', + }, + { + label: 'dot', + value: 'pai_graph.dot', + }, + { + label: 'init', + value: 'init.rc', + }, + ], + }, + { + id: '2', + dataType: 'alps', + name: 'alps_file2', + conf: [ + { + label: 'conf', + value: 'pai_graph.conf', + }, + { + label: 'dot', + value: 'pai_graph.dot', + }, + { + label: 'init', + value: 'init.rc', + }, + ], + }, + { + id: '3', + dataType: 'alps', + name: 'alps_file3', + conf: [ + { + label: 'conf', + value: 'pai_graph.conf', + }, + { + label: 'dot', + value: 'pai_graph.dot', + }, + { + label: 'init', + value: 'init.rc', + }, + ], + }, + { + id: '4', + dataType: 'sql', + name: 'sql_file1', + conf: [ + { + label: 'conf', + value: 'pai_graph.conf', + }, + { + label: 'dot', + value: 'pai_graph.dot', + }, + { + label: 'init', + value: 'init.rc', + }, + ], + }, + { + id: '5', + dataType: 'sql', + name: 'sql_file2', + conf: [ + { + label: 'conf', + value: 'pai_graph.conf', + }, + { + label: 'dot', + value: 'pai_graph.dot', + }, + { + label: 'init', + value: 'init.rc', + }, + ], + }, + { + id: '6', + dataType: 'feature_etl', + name: 'feature_etl_1', + conf: [ + { + label: 'conf', + value: 'pai_graph.conf', + }, + { + label: 'dot', + value: 'pai_graph.dot', + }, + { + label: 'init', + value: 'init.rc', + }, + ], + }, + { + id: '7', + dataType: 'feature_etl', + name: 'feature_etl_1', + conf: [ + { + label: 'conf', + value: 'pai_graph.conf', + }, + { + label: 'dot', + value: 'pai_graph.dot', + }, + { + label: 'init', + value: 'init.rc', + }, + ], + }, + { + id: '8', + dataType: 'feature_extractor', + name: 'feature_extractor', + conf: [ + { + label: 'conf', + value: 'pai_graph.conf', + }, + { + label: 'dot', + value: 'pai_graph.dot', + }, + { + label: 'init', + value: 'init.rc', + }, + ], + }, + ], + edges: [ + { + source: '1', + target: '2', + }, + { + source: '1', + target: '3', + }, + { + source: '2', + target: '4', + }, + { + source: '3', + target: '4', + }, + { + source: '4', + target: '5', + }, + { + source: '5', + target: '6', + }, + { + source: '6', + target: '7', + }, + { + source: '6', + target: '8', + }, + ], +}; + +G6.registerNode( + 'sql', + { + drawShape(cfg, group) { + const rect = group.addShape('rect', { + attrs: { + x: -75, + y: -25, + width: 150, + height: 50, + radius: 10, + stroke: '#5B8FF9', + fill: '#C6E5FF', + lineWidth: 3, + }, + name: 'rect-shape', + }); + if (cfg.name) { + group.addShape('text', { + attrs: { + text: cfg.name, + x: 0, + y: 0, + fill: '#00287E', + fontSize: 14, + textAlign: 'center', + textBaseline: 'middle', + fontWeight: 'bold', + }, + name: 'text-shape', + }); + } + return rect; + }, + }, + 'single-node', +); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'dagre', + nodesepFunc: (d) => { + if (d.id === '3') { + return 500; + } + return 50; + }, + ranksep: 70, + controlPoints: true, + }, + defaultNode: { + type: 'sql', + }, + defaultEdge: { + type: 'polyline', + style: { + radius: 20, + offset: 45, + endArrow: true, + lineWidth: 2, + stroke: '#C2C8D5', + }, + }, + nodeStateStyles: { + selected: { + stroke: '#d9d9d9', + fill: '#5394ef', + }, + }, + modes: { + default: [ + 'drag-canvas', + 'zoom-canvas', + 'click-select', + { + type: 'tooltip', + formatText(model) { + const cfg = model.conf; + const text = []; + cfg.forEach((row) => { + text.push(row.label + ':' + row.value + '
'); + }); + return text.join('\n'); + }, + offset: 30, + }, + ], + }, + fitView: true, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/dagreFlow/demo/dagreCombo.js b/packages/site/examples/net/dagreFlow/demo/dagreCombo.js new file mode 100644 index 0000000000..ea2c19551b --- /dev/null +++ b/packages/site/examples/net/dagreFlow/demo/dagreCombo.js @@ -0,0 +1,233 @@ +import G6 from '@antv/g6'; +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + comboId: 'A', + }, + { + id: '5', + label: '5', + comboId: 'B', + }, + { + id: '6', + label: '6', + comboId: 'A', + }, + { + id: '7', + label: '7', + comboId: 'C', + }, + { + id: '8', + label: '8', + comboId: 'C', + }, + { + id: '9', + label: '9', + comboId: 'A', + }, + { + id: '10', + label: '10', + comboId: 'B', + }, + { + id: '11', + label: '11', + comboId: 'B', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '1', + target: '4', + }, + { + source: '0', + target: '3', + }, + { + source: '3', + target: '4', + }, + { + source: '2', + target: '5', + }, + { + source: '1', + target: '6', + }, + { + source: '1', + target: '7', + }, + { + source: '3', + target: '8', + }, + { + source: '3', + target: '9', + }, + { + source: '5', + target: '10', + }, + { + source: '5', + target: '11', + }, + ], + combos: [ + { + id: 'A', + label: 'combo A', + style: { + fill: '#C4E3B2', + stroke: '#C4E3B2', + }, + }, + { + id: 'B', + label: 'combo B', + style: { + stroke: '#99C0ED', + fill: '#99C0ED', + }, + }, + { + id: 'C', + label: 'combo C', + style: { + stroke: '#eee', + fill: '#eee', + }, + }, + ], +}; + +data.nodes.forEach((node) => { + switch (node.ComboId) { + case 'A': + node.style = { + fill: '#C4E3B2', + stroke: '#aaa', + }; + break; + case 'B': + node.style = { + fill: '#99C0ED', + stroke: '#aaa', + }; + break; + case 'C': + node.style = { + fill: '#eee', + stroke: '#aaa', + }; + break; + default: + node.style = { + fill: '#FDE1CE', + stroke: '#aaa', + }; + break; + } +}); + +let sortByCombo = false; +const descriptionDiv = document.createElement('button'); +descriptionDiv.innerHTML = 'Enable sortByCombo'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +descriptionDiv.addEventListener('click', (e) => { + sortByCombo = !sortByCombo; + descriptionDiv.innerHTML = sortByCombo ? 'Disable sortByCombo' : 'Enable sortByCombo'; + graph.updateLayout({ + sortByCombo, + }); +}); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height: height - 50, + fitView: true, + fitViewPadding: 30, + animate: true, + groupByTypes: false, + modes: { + default: [ + 'drag-combo', + 'drag-node', + 'drag-canvas', + { + type: 'collapse-expand-combo', + relayout: false, + }, + ], + }, + layout: { + type: 'dagre', + sortByCombo: false, + ranksep: 10, + nodesep: 10, + }, + defaultNode: { + size: [60, 30], + type: 'rect', + anchorPoints: [[0.5, 0], [0.5, 1]] + }, + defaultEdge: { + type: 'line', + }, + defaultCombo: { + type: 'rect', + style: { + fillOpacity: 0.1, + }, + }, +}); +graph.data(data); +graph.render(); + +console.log('comboTrees', graph.get('comboTrees')) + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; diff --git a/packages/site/examples/net/dagreFlow/demo/dagreConfigurationTranslate.js b/packages/site/examples/net/dagreFlow/demo/dagreConfigurationTranslate.js new file mode 100644 index 0000000000..5349034487 --- /dev/null +++ b/packages/site/examples/net/dagreFlow/demo/dagreConfigurationTranslate.js @@ -0,0 +1,273 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + ], +}; +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Dagre layout, rank seperation: 1, node seperation in same level: 1, layout direction: Top->Bottom, alignment of nodes: DL'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height, + controlPoints: false, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'dagre', + nodeSize: [40, 20], + nodesep: 1, + ranksep: 1, + }, + animate: true, + defaultNode: { + size: [40, 20], + type: 'rect', + style: { + lineWidth: 2, + stroke: '#5B8FF9', + fill: '#C6E5FF', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; + +layoutConfigTranslation(); + +function layoutConfigTranslation() { + setTimeout(function () { + descriptionDiv.innerHTML = + 'Dagre layout, rank seperation: 10, node seperation in same level: 1, layout direction: Top->Bottom, alignment of nodes: DL'; + graph.updateLayout({ + ranksep: 10, + }); + }, 1000); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Dagre layout, rank seperation: 10, node seperation in same level: 5, layout direction: Left->Right, alignment of nodes: DL'; + graph.updateLayout({ + nodesep: 5, + }); + }, 2500); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Dagre layout, rank seperation: 10, node seperation in same level: 5, layout direction: Left->Right, alignment of nodes: UL'; + graph.updateLayout({ + align: 'UL', + }); + }, 4000); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Dagre layout, rank seperation: 10, node seperation in same level: 5, layout direction: Left->Right, alignment of nodes: UR'; + graph.updateLayout({ + align: 'UR', + }); + }, 5500); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Dagre layout, rank seperation: 10, node seperation in same level: 5, layout direction: Left->Right, alignment of nodes: Down Right, alignment of nodes: DL'; + graph.updateLayout({ + rankdir: 'LR', + align: 'DL', + }); + }, 7000); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Dagre layout, rank seperation: 30, node seperation in same level: 5, layout direction: Left->Right, alignment of nodes: Down Right, alignment of nodes: DL'; + graph.updateLayout({ + ranksep: 30, + }); + }, 8500); +} diff --git a/packages/site/examples/net/dagreFlow/demo/lrDagre.js b/packages/site/examples/net/dagreFlow/demo/lrDagre.js new file mode 100644 index 0000000000..4e9d60a902 --- /dev/null +++ b/packages/site/examples/net/dagreFlow/demo/lrDagre.js @@ -0,0 +1,215 @@ +import G6 from '@antv/g6'; +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'dagre', + rankdir: 'LR', + align: 'DL', + nodesepFunc: () => 1, + ranksepFunc: () => 1, + }, + defaultNode: { + size: [30, 20], + type: 'rect', + style: { + lineWidth: 2, + stroke: '#5B8FF9', + fill: '#C6E5FF', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/dagreFlow/demo/lrDagreUL.js b/packages/site/examples/net/dagreFlow/demo/lrDagreUL.js new file mode 100644 index 0000000000..e9dd94a808 --- /dev/null +++ b/packages/site/examples/net/dagreFlow/demo/lrDagreUL.js @@ -0,0 +1,146 @@ +import G6 from '@antv/g6'; +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '1', + target: '4', + }, + { + source: '0', + target: '3', + }, + { + source: '3', + target: '4', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '7', + }, + { + source: '5', + target: '8', + }, + { + source: '8', + target: '9', + }, + { + source: '2', + target: '9', + }, + { + source: '3', + target: '9', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'dagre', + rankdir: 'LR', + align: 'UL', + controlPoints: true, + nodesepFunc: () => 1, + ranksepFunc: () => 1, + }, + defaultNode: { + size: [30, 20], + type: 'rect', + style: { + lineWidth: 2, + stroke: '#5B8FF9', + fill: '#C6E5FF', + }, + }, + defaultEdge: { + type: 'polyline', + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + radius: 20, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/dagreFlow/demo/meta.json b/packages/site/examples/net/dagreFlow/demo/meta.json new file mode 100644 index 0000000000..bc73013d48 --- /dev/null +++ b/packages/site/examples/net/dagreFlow/demo/meta.json @@ -0,0 +1,48 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicDagre.js", + "title": { + "zh": "Dagre 流程图", + "en": "Basic Dagre Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*2uMmRo5wYPUAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "dagreCombo.js", + "title": { + "zh": "带有 Combo 的流程图", + "en": "Dagre with Combos" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*94EUTJ8l2QAAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "lrDagreUL.js", + "title": { + "zh": "自左向右的 Dagre 上对齐", + "en": "Dagre from Left to Right" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*LJpmR4G7J3YAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "lrDagre.js", + "title": { + "zh": "自左向右的 Dagre", + "en": "Dagre from Left to Right" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*pdaxTpV1R0EAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "dagreConfigurationTranslate.js", + "title": { + "zh": "Dagre 布局参数动态变化", + "en": "Update Configurations for Dagre Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*VkwOTL2UyN0AAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/dagreFlow/index.en.md b/packages/site/examples/net/dagreFlow/index.en.md new file mode 100644 index 0000000000..3cb004977b --- /dev/null +++ b/packages/site/examples/net/dagreFlow/index.en.md @@ -0,0 +1,16 @@ +--- +title: Dagre Layout +order: 1 +--- + +Dagre Layout is an appropriate layout method for directed flow graph. It will calculate the levels and positions of nodes automatically according to the edge directions in the data. + +## Usage + +As the demo below, you can deploy it in `layout` while instantiating Graph. it can also be used for [Subgraph Layout](/en/docs/manual/middle/layout/sub-layout). By tuning the parameters, you can adjust the layout direction, node alignment, node separation, level separation, and so on. + +- Example 1 : Simple dagre layout. +- Example 2 : Dagre Layout with combos. +- Example 3 : Dagre layout from left to right and align top. +- Example 4 : Dagre layout from left to right. +- Example 5 : Translate the layout parameters in dynamic. diff --git a/packages/site/examples/net/dagreFlow/index.zh.md b/packages/site/examples/net/dagreFlow/index.zh.md new file mode 100644 index 0000000000..cffa9de450 --- /dev/null +++ b/packages/site/examples/net/dagreFlow/index.zh.md @@ -0,0 +1,16 @@ +--- +title: Dagre 流程图布局 +order: 1 +--- + +Dagre 是适合有向流程图的布局算法。其根据图数据中边的方向,自动计算节点的层级及位置。 + +## 使用指南 + +G6 内置的 Dagre 布局可以实现有向图的自动分层布局。如下面代码所示,可在实例化 Graph 时使用该布局。除此之外,还可以如[子图布局](/zh/docs/manual/middle/layout/sub-layout)所示单独使用布局。该布局可以通过配置调整布局方向、节点对齐方式、节点间距、层高等。 + +- 代码演示 1 :简单的 Dagre 布局。 +- 代码演示 2 :带有 Combo 的 Dagre 布局(目前只能处理好同层节点的 Combo 问题)。 +- 代码演示 3 :自左向右向上对齐的 Dagre 布局。 +- 代码演示 4 :自左向右的 Dagre 布局。 +- 代码演示 5 :Dagre 布局参数动态变化。 diff --git a/packages/site/examples/net/forceDirected/API.en.md b/packages/site/examples/net/forceDirected/API.en.md new file mode 100644 index 0000000000..7e4bb41a45 --- /dev/null +++ b/packages/site/examples/net/forceDirected/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/forceDirected/API.zh.md b/packages/site/examples/net/forceDirected/API.zh.md new file mode 100644 index 0000000000..8075f30ecb --- /dev/null +++ b/packages/site/examples/net/forceDirected/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# Force Layout + + + +# Force Atlas 2 Layout + + diff --git a/packages/site/examples/net/forceDirected/demo/basicFA2.js b/packages/site/examples/net/forceDirected/demo/basicFA2.js new file mode 100644 index 0000000000..cb786fe04b --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/basicFA2.js @@ -0,0 +1,42 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['zoom-canvas', 'drag-canvas', 'drag-node'], + }, + layout: { + type: 'forceAtlas2', + preventOverlap: true, + kr: 10, + center: [250, 250], + }, + defaultNode: { + size: 20, + }, +}); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + data.nodes.forEach(node => { + node.x = Math.random() * 1; + }); + graph.on('afterlayout', e => { + graph.fitView() + }) + graph.data(data); + graph.render(); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/forceDirected/demo/basicForce2.js b/packages/site/examples/net/forceDirected/demo/basicForce2.js new file mode 100644 index 0000000000..53b2303bf1 --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/basicForce2.js @@ -0,0 +1,505 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + cluster: 'a', + }, + { + id: '1', + label: '1', + cluster: 'a', + }, + { + id: '2', + label: '2', + cluster: 'a', + }, + { + id: '3', + label: '3', + cluster: 'a', + }, + { + id: '4', + label: '4', + cluster: 'a', + }, + { + id: '5', + label: '5', + cluster: 'a', + }, + { + id: '6', + label: '6', + cluster: 'a', + }, + { + id: '7', + label: '7', + cluster: 'a', + }, + { + id: '8', + label: '8', + cluster: 'a', + }, + { + id: '9', + label: '9', + cluster: 'a', + }, + { + id: '10', + label: '10', + cluster: 'a', + }, + { + id: '11', + label: '11', + cluster: 'a', + }, + { + id: '12', + label: '12', + cluster: 'a', + }, + { + id: '13', + label: '13', + cluster: 'b', + }, + { + id: '14', + label: '14', + cluster: 'b', + }, + { + id: '15', + label: '15', + cluster: 'b', + }, + { + id: '16', + label: '16', + cluster: 'b', + }, + { + id: '17', + label: '17', + cluster: 'b', + }, + { + id: '18', + label: '18', + cluster: 'c', + }, + { + id: '19', + label: '19', + cluster: 'c', + }, + { + id: '20', + label: '20', + cluster: 'c', + }, + { + id: '21', + label: '21', + cluster: 'c', + }, + { + id: '22', + label: '22', + cluster: 'c', + }, + { + id: '23', + label: '23', + cluster: 'c', + }, + { + id: '24', + label: '24', + cluster: 'c', + }, + { + id: '25', + label: '25', + cluster: 'c', + }, + { + id: '26', + label: '26', + cluster: 'c', + }, + { + id: '27', + label: '27', + cluster: 'c', + }, + { + id: '28', + label: '28', + cluster: 'c', + }, + { + id: '29', + label: '29', + cluster: 'c', + }, + { + id: '30', + label: '30', + cluster: 'c', + }, + { + id: '31', + label: '31', + cluster: 'd', + }, + { + id: '32', + label: '32', + cluster: 'd', + }, + { + id: '33', + label: '33', + cluster: 'd', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const colors = [ + '#BDD2FD', + '#BDEFDB', + '#C2C8D5', + '#FBE5A2', + '#F6C3B7', + '#B6E3F5', + '#D3C6EA', + '#FFD8B8', + '#AAD8D8', + '#FFD6E7', +]; +const strokes = [ + '#5B8FF9', + '#5AD8A6', + '#5D7092', + '#F6BD16', + '#E8684A', + '#6DC8EC', + '#9270CA', + '#FF9D4D', + '#269A99', + '#FF99C3', +]; +const nodes = data.nodes; +const clusterMap = new Map(); +let clusterId = 0; +nodes.forEach(function (node) { + // cluster + if (node.cluster && clusterMap.get(node.cluster) === undefined) { + clusterMap.set(node.cluster, clusterId); + clusterId++; + } + const cid = clusterMap.get(node.cluster); + if (!node.style) { + node.style = {}; + } + node.style.fill = colors[cid % colors.length]; + node.style.stroke = strokes[cid % strokes.length]; +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'force2', + animate: true, // 设置为 false 可关闭布局动画 + maxSpeed: 100, + linkDistance: 50, + clustering: true, + nodeClusterBy: 'cluster', + clusterNodeStrength: 300, + }, +}); + +graph.data(data); +graph.render(); + +/******** 拖拽固定节点的逻辑 *********/ +graph.on('node:dragstart', function (e) { + const forceLayout = graph.get('layoutController').layoutMethods[0]; + forceLayout.stop() +}); +graph.on('node:drag', function (e) { + refreshDragedNodePosition(e); + graph.layout() +}); +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; + model.x = e.x; + model.y = e.y; +} +/*********************************/ + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; \ No newline at end of file diff --git a/packages/site/examples/net/forceDirected/demo/basicForceDirected.js b/packages/site/examples/net/forceDirected/demo/basicForceDirected.js new file mode 100644 index 0000000000..f7bd16cbb4 --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/basicForceDirected.js @@ -0,0 +1,56 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'force', + }, + defaultNode: { + size: 15, + }, +}); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + graph.data({ + nodes: data.nodes, + edges: data.edges.map(function (edge, i) { + edge.id = 'edge' + i; + return Object.assign({}, edge); + }), + }); + graph.render(); + + graph.on('node:dragstart', function (e) { + graph.layout(); + refreshDragedNodePosition(e); + }); + graph.on('node:drag', function (e) { + const forceLayout = graph.get('layoutController').layoutMethods[0]; + forceLayout.execute(); + refreshDragedNodePosition(e); + }); + graph.on('node:dragend', function (e) { + e.item.get('model').fx = null; + e.item.get('model').fy = null; + }); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; +} diff --git a/packages/site/examples/net/forceDirected/demo/basicForceDirectedDragFix.js b/packages/site/examples/net/forceDirected/demo/basicForceDirectedDragFix.js new file mode 100644 index 0000000000..5372b9e35e --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/basicForceDirectedDragFix.js @@ -0,0 +1,136 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + ], +}; +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'force', + preventOverlap: true, + nodeSize: 20, + }, + modes: { + default: ['drag-node'], + }, + defaultNode: { + size: 20, + }, +}); +graph.data(data); +graph.render(); + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; +} +graph.on('node:dragstart', (e) => { + graph.layout(); + refreshDragedNodePosition(e); +}); +graph.on('node:drag', (e) => { + refreshDragedNodePosition(e); +}); +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/forceDirected/demo/forceBubbles.js b/packages/site/examples/net/forceDirected/demo/forceBubbles.js new file mode 100644 index 0000000000..cfbc20321d --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/forceBubbles.js @@ -0,0 +1,255 @@ +import G6 from '@antv/g6'; + +function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value, + enumerable: true, + configurable: true, + writable: true, + }); + } else { + obj[key] = value; + } + return obj; +} + +const colors = [ + '#BDD2FD', + '#BDEFDB', + '#C2C8D5', + '#FBE5A2', + '#F6C3B7', + '#B6E3F5', + '#D3C6EA', + '#FFD8B8', + '#AAD8D8', + '#FFD6E7', +]; +const strokes = [ + '#5B8FF9', + '#5AD8A6', + '#5D7092', + '#F6BD16', + '#E8684A', + '#6DC8EC', + '#9270CA', + '#FF9D4D', + '#269A99', + '#FF99C3', +]; + +const data = { + nodes: [ + { + id: '0', + label: '0', + value: 10, + cluster: 'a', + description: 'this is node 0, \nand the value of it is 10', + }, + { + id: '1', + label: '1', + value: 20, + cluster: 'b', + description: 'this is node 1, \nand the value of it is 20', + }, + { + id: '2', + label: '2', + value: 5, + cluster: 'a', + description: 'this is node 2, \nand the value of it is 5', + }, + { + id: '3', + label: '3', + value: 10, + cluster: 'a', + description: 'this is node 3, \nand the value of it is 10', + }, + { + id: '4', + label: '4', + value: 12, + cluster: 'c', + subCluster: 'sb', + description: 'this is node 4, \nand the value of it is 12', + }, + { + id: '5', + label: '5', + value: 18, + cluster: 'c', + subCluster: 'sa', + description: 'this is node 5, \nand the value of it is 18', + }, + { + id: '6', + label: '6', + value: 3, + cluster: 'c', + subCluster: 'sa', + description: 'this is node 6, \nand the value of it is 3', + }, + { + id: '7', + label: '7', + value: 7, + cluster: 'b', + subCluster: 'sa', + description: 'this is node 7, \nand the value of it is 7', + }, + { + id: '8', + label: '8', + value: 21, + cluster: 'd', + subCluster: 'sb', + description: 'this is node 8, \nand the value of it is 21', + }, + { + id: '9', + label: '9', + value: 9, + cluster: 'd', + subCluster: 'sb', + description: 'this is node 9, \nand the value of it is 9', + }, + ], + edges: [], +}; + +const tipDiv = document.createElement('div'); +tipDiv.innerHTML = 'Try to click or drag a bubble!'; +const graphDiv = document.getElementById('container'); +graphDiv.appendChild(tipDiv); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'force', + nodeStrength: 30, + collideStrength: 0.7, + alphaDecay: 0.01, + preventOverlap: true, + }, + modes: { + default: ['drag-node'], + }, + defaultNode: { + size: [10, 10], + }, +}); + +// mapping +const nodes = data.nodes; +const nodeMap = new Map(); +const clusterMap = new Map(); +let clusterId = 0; +nodes.forEach((node) => { + nodeMap.set(node.id, node); + // cluster + if (node.cluster && clusterMap.get(node.cluster) === undefined) { + clusterMap.set(node.cluster, clusterId); + clusterId++; + } + const cid = clusterMap.get(node.cluster); + if (!node.style) node.style = {}; + node.style.fill = colors[cid % colors.length]; + node.style.stroke = strokes[cid % strokes.length]; + node.x = width / 2 + 200 * (Math.random() - 0.5); + node.y = height / 2 + 200 * (Math.random() - 0.5); +}); + +// map the value to node size +let maxNodeValue = -9999, + minNodeValue = 9999; +nodes.forEach(function (n) { + if (maxNodeValue < n.value) maxNodeValue = n.value; + if (minNodeValue > n.value) minNodeValue = n.value; +}); +const nodeSizeRange = [10, 30]; +const nodeSizeDataRange = [minNodeValue, maxNodeValue]; +scaleNodeProp(nodes, 'size', 'value', nodeSizeDataRange, nodeSizeRange); + +nodes.forEach(function (node) { + node.oriSize = node.size; + node.oriLabel = node.label; +}); + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; +} +graph.on('node:dragstart', function (e) { + graph.layout(); + refreshDragedNodePosition(e); +}); +graph.on('node:drag', function (e) { + refreshDragedNodePosition(e); +}); +graph.on('node:dragend', function (e) { + e.item.get('model').fx = null; + e.item.get('model').fy = null; +}); +graph.on('node:click', function (e) { + const node = e.item; + const states = node.getStates(); + let clicked = false; + const model = node.getModel(); + let size = 200; + let labelText = 'NODE: ' + model.id + '\n' + model.description; + states.forEach(function (state) { + if (state === 'click') { + clicked = true; + size = model.oriSize; + labelText = model.oriLabel; + } + }); + graph.setItemState(node, 'click', !clicked); + graph.updateItem(node, { + size, + label: labelText, + }); + graph.layout(); +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; + +function scaleNodeProp(elements, propName, refPropName, dataRange, outRange) { + const outLength = outRange[1] - outRange[0]; + const dataLength = dataRange[1] - dataRange[0]; + elements.forEach(function (n) { + if (propName.split('.')[0] === 'style') { + if (n.style) { + n.style[propName.split('.')[1]] = + ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0]; + } else { + n.style = _defineProperty( + {}, + propName.split('.')[1], + ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0], + ); + } + } else { + n[propName] = ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0]; + } + }); +} diff --git a/packages/site/examples/net/forceDirected/demo/forceClustering.js b/packages/site/examples/net/forceDirected/demo/forceClustering.js new file mode 100644 index 0000000000..7f7f9f9ee2 --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/forceClustering.js @@ -0,0 +1,54 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'force', + clustering: true, + clusterNodeStrength: -5, + clusterEdgeDistance: 200, + clusterNodeSize: 20, + clusterFociStrength: 1.2, + nodeSpacing: 5, + preventOverlap: true, + }, + defaultNode: { + size: 15, + }, + modes: { + default: ['zoom-canvas', 'drag-canvas', 'drag-node'], + }, +}); + +let colorMap = { + 2012: '#BDD2FD', + 2013: '#BDEFDB', + 2014: '#F6C3B7', + 2015: '#FFD8B8', + 2016: '#D3C6EA', +}; + +fetch('https://gw.alipayobjects.com/os/basement_prod/7bacd7d1-4119-4ac1-8be3-4c4b9bcbc25f.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + data.nodes.forEach((i) => { + i.cluster = i.year; + i.style = Object.assign(i.style || {}, { + fill: colorMap[i.year], + }); + }); + graph.render(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/net/forceDirected/demo/forceConstrainedInRect.js b/packages/site/examples/net/forceDirected/demo/forceConstrainedInRect.js new file mode 100644 index 0000000000..380905f1ee --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/forceConstrainedInRect.js @@ -0,0 +1,90 @@ +import G6 from '@antv/g6'; + +const graphDiv = document.getElementById('container'); +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Constrians the nodes to be layed in the gray area with force-directed layout'; +graphDiv.appendChild(descriptionDiv); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + const nodes = data.nodes; + + // 灰色区域 + const constrainBox = { x: 60, y: 50, width: 500, height: 150 }; + + const backrect = document.createElement('div'); + backrect.style.backgroundColor = '#666'; + backrect.style.opacity = 0.1; + backrect.style.marginLeft = `${constrainBox.x}px`; + backrect.style.marginTop = `${constrainBox.y}px`; + backrect.style.width = `${constrainBox.width}px`; + backrect.style.height = `${constrainBox.height}px`; + backrect.style.position = 'absolute'; + graphDiv.appendChild(backrect); + + const onTick = () => { + let minx = 99999999; + let maxx = -99999999; + let miny = 99999999; + let maxy = -99999999; + let maxsize = -9999999; + nodes.forEach((node) => { + if (minx > node.x) { + minx = node.x; + } + if (maxx < node.x) { + maxx = node.x; + } + if (miny > node.y) { + miny = node.y; + } + if (maxy < node.y) { + maxy = node.y; + } + if (maxsize < node.size) { + maxsize = node.size; + } + }); + const scalex = (constrainBox.width - maxsize) / (maxx - minx); + const scaley = (constrainBox.height - maxsize) / (maxy - miny); + nodes.forEach((node) => { + node.x = (node.x - minx) * scalex + constrainBox.x; + node.y = (node.y - miny) * scaley + constrainBox.y; + }); + }; + + const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'force', + onTick, + }, + defaultNode: { + size: 15, + }, + }); + + graph.data({ + nodes: data.nodes, + edges: data.edges.map(function (edge, i) { + edge.id = 'edge' + i; + return Object.assign({}, edge); + }), + }); + graph.render(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/net/forceDirected/demo/forceDirectedConfigurationTranslate.js b/packages/site/examples/net/forceDirected/demo/forceDirectedConfigurationTranslate.js new file mode 100644 index 0000000000..18a3f0dd82 --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/forceDirectedConfigurationTranslate.js @@ -0,0 +1,442 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Force layout, linkDistance = 50, preventOverlap: false'; +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'force', + linkDistance: 50, + center: [width / 2, height / 2], + }, + animate: true, + defaultNode: { + size: 20, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; + +layoutConfigTranslation(); + +setInterval(() => { + layoutConfigTranslation(); +}, 5000); + +function layoutConfigTranslation() { + setTimeout(() => { + descriptionDiv.innerHTML = 'Force layout, linkDistance = 100, preventOverlap: true'; + graph.updateLayout({ + linkDistance: 100, + preventOverlap: true, + nodeSize: 20, + }); + }, 2500); + setTimeout(() => { + descriptionDiv.innerHTML = 'Force layout, linkDistance = 50, preventOverlap: false'; + graph.updateLayout({ + linkDistance: 50, + preventOverlap: false, + }); + }, 5000); +} diff --git a/packages/site/examples/net/forceDirected/demo/forceDirectedFunctionalParams.js b/packages/site/examples/net/forceDirected/demo/forceDirectedFunctionalParams.js new file mode 100644 index 0000000000..89fa8e172f --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/forceDirectedFunctionalParams.js @@ -0,0 +1,112 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'force', + preventOverlap: true, + linkDistance: (d) => { + if (d.source.id === 'node0') { + return 100; + } + return 30; + }, + nodeStrength: (d) => { + if (d.isLeaf) { + return -50; + } + return -10; + }, + edgeStrength: (d) => { + if (d.source.id === 'node1' || d.source.id === 'node2' || d.source.id === 'node3') { + return 0.7; + } + return 0.1; + }, + }, + defaultNode: { + color: '#5B8FF9', + }, + modes: { + default: ['drag-canvas'], + }, +}); + +const data = { + nodes: [ + { id: 'node0', size: 50 }, + { id: 'node1', size: 30 }, + { id: 'node2', size: 30 }, + { id: 'node3', size: 30 }, + { id: 'node4', size: 30, isLeaf: true }, + { id: 'node5', size: 30, isLeaf: true }, + { id: 'node6', size: 15, isLeaf: true }, + { id: 'node7', size: 15, isLeaf: true }, + { id: 'node8', size: 15, isLeaf: true }, + { id: 'node9', size: 15, isLeaf: true }, + { id: 'node10', size: 15, isLeaf: true }, + { id: 'node11', size: 15, isLeaf: true }, + { id: 'node12', size: 15, isLeaf: true }, + { id: 'node13', size: 15, isLeaf: true }, + { id: 'node14', size: 15, isLeaf: true }, + { id: 'node15', size: 15, isLeaf: true }, + { id: 'node16', size: 15, isLeaf: true }, + ], + edges: [ + { source: 'node0', target: 'node1' }, + { source: 'node0', target: 'node2' }, + { source: 'node0', target: 'node3' }, + { source: 'node0', target: 'node4' }, + { source: 'node0', target: 'node5' }, + { source: 'node1', target: 'node6' }, + { source: 'node1', target: 'node7' }, + { source: 'node2', target: 'node8' }, + { source: 'node2', target: 'node9' }, + { source: 'node2', target: 'node10' }, + { source: 'node2', target: 'node11' }, + { source: 'node2', target: 'node12' }, + { source: 'node2', target: 'node13' }, + { source: 'node3', target: 'node14' }, + { source: 'node3', target: 'node15' }, + { source: 'node3', target: 'node16' }, + ], +}; +const nodes = data.nodes; +graph.data({ + nodes, + edges: data.edges.map(function (edge, i) { + edge.id = 'edge' + i; + return Object.assign({}, edge); + }), +}); +graph.render(); + +graph.on('node:dragstart', function (e) { + graph.layout(); + refreshDragedNodePosition(e); +}); +graph.on('node:drag', function (e) { + refreshDragedNodePosition(e); +}); +graph.on('node:dragend', function (e) { + e.item.get('model').fx = null; + e.item.get('model').fy = null; +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; +} diff --git a/packages/site/examples/net/forceDirected/demo/forceDirectedPreventOverlap.js b/packages/site/examples/net/forceDirected/demo/forceDirectedPreventOverlap.js new file mode 100644 index 0000000000..8759a309a8 --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/forceDirectedPreventOverlap.js @@ -0,0 +1,62 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'force', + preventOverlap: true, + }, + modes: { + default: ['drag-canvas'], + }, +}); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + const nodes = data.nodes; + // randomize the node size + nodes.forEach((node) => { + node.size = Math.random() * 30 + 5; + }); + graph.data({ + nodes, + edges: data.edges.map(function (edge, i) { + edge.id = 'edge' + i; + return Object.assign({}, edge); + }), + }); + graph.render(); + + graph.on('node:dragstart', function (e) { + graph.layout(); + refreshDragedNodePosition(e); + }); + graph.on('node:drag', function (e) { + const forceLayout = graph.get('layoutController').layoutMethods[0]; + forceLayout.execute(); + refreshDragedNodePosition(e); + }); + graph.on('node:dragend', function (e) { + e.item.get('model').fx = null; + e.item.get('model').fy = null; + }); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; +} diff --git a/packages/site/examples/net/forceDirected/demo/gForceFix.js b/packages/site/examples/net/forceDirected/demo/gForceFix.js new file mode 100644 index 0000000000..f4a51ff482 --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/gForceFix.js @@ -0,0 +1,61 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'gForce', + gravity: 10, + edgeStrength: 100, + nodeStrength: 100, + }, + defaultNode: { + size: 15, + }, + fitView: true, + modes: { + default: ['drag-canvas', 'zoom-canvas'] + } +}); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + graph.data({ + nodes: data.nodes, + edges: data.edges.map(function (edge, i) { + edge.id = 'edge' + i; + return Object.assign({}, edge); + }), + }); + graph.render(); + + graph.on('node:dragstart', function (e) { + const forceLayout = graph.get('layoutController').layoutMethods[0]; + forceLayout.stop() + }); + + graph.on('node:drag', function (e) { + refreshDragedNodePosition(e); + graph.layout() + }); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; + model.x = e.x; + model.y = e.y; +} diff --git a/packages/site/examples/net/forceDirected/demo/meta.json b/packages/site/examples/net/forceDirected/demo/meta.json new file mode 100644 index 0000000000..2dfc099189 --- /dev/null +++ b/packages/site/examples/net/forceDirected/demo/meta.json @@ -0,0 +1,96 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicForce2.js", + "title": { + "zh": "Force2 聚类及固定拖拽节点", + "en": "Basic Force2 Layout with dragging" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Ce2WSIlo_fcAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "basicForceDirected.js", + "title": { + "zh": "基本力导向布局及节点拖拽", + "en": "Basic Force-directed Layout with dragging" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Ce2WSIlo_fcAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "basicFA2.js", + "title": { + "zh": "基本 Force Atlas 2", + "en": "Basic Force-Atlas 2 Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*MqwAQZLIVPwAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "forceDirectedPreventOverlap.js", + "title": { + "zh": "力导向布局防止节点重叠", + "en": "Prevent Node Overlappings" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*9VXcQLLyzHgAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "forceClustering.js", + "title": { + "zh": "力导向布局聚类", + "en": "Force Layout with Clustering" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*xonLSopPjKUAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "forceDirectedFunctionalParams.js", + "title": { + "zh": "定制不同节点的参数", + "en": "Different Configurations for Nodes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*SnVUQbXv7JAAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "basicForceDirectedDragFix.js", + "title": { + "zh": "固定被拖拽节点", + "en": "Fix the Dragged Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*0qLJQaRFg90AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "forceDirectedConfigurationTranslate.js", + "title": { + "zh": "力导向布局参数动态变化", + "en": "Update Configurations for Force-directed Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IyK7T5MkVqUAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "forceBubbles.js", + "title": { + "zh": "力导向气泡图", + "en": "Force-directed Bubbles" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*prZhSI341hUAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "forceConstrainedInRect.js", + "title": { + "zh": "约束在范围内布局", + "en": "Constrain the Nodes in a region" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-4H8SaV8QKwAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "gForceFix.js", + "title": { + "zh": "GForce 固定被拖拽的节点", + "en": "GForce with Fixing Dragged Nodes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*YMwnTbT9NnQAAAAAAAAAAAAAARQnAQ" + } + ] +} \ No newline at end of file diff --git a/packages/site/examples/net/forceDirected/index.en.md b/packages/site/examples/net/forceDirected/index.en.md new file mode 100644 index 0000000000..26cde8d333 --- /dev/null +++ b/packages/site/examples/net/forceDirected/index.en.md @@ -0,0 +1,18 @@ +--- +title: Force-directed Layout +order: 0 +--- + +Force-directed layout is a set of algorithms which are imporved and extended by lots of researchers based on the earliest classical force-directed algorithm. They simulate the nodes and edges in the graph as the physical objects. There are attractive forces and repulsive forces between nodes to iteratively move them to reach a reasonable layout. + +## Usage + +The classical force-directed layout in G6 comes from d3.js. As the demo below, you can deploy it in `layout` while instantiating Graph. it can also be used for [Subgraph Layout](/en/docs/manual/middle/layout/sub-layout). + +- Example 1 : Basic force-directed layout and dragging interactions. +- Example 2 : Prevent node overlappings. +- Example 3 : Adjust the link distances and forces for different nodes. +- Example 4 : Fix the dragged node. +- Example 5 : Translate the layout parameters in dynamic. +- Example 6 : The bubbles layout and interactions. +- Example 7 : Constrain the layout in a certain area. diff --git a/packages/site/examples/net/forceDirected/index.zh.md b/packages/site/examples/net/forceDirected/index.zh.md new file mode 100644 index 0000000000..01a067fe06 --- /dev/null +++ b/packages/site/examples/net/forceDirected/index.zh.md @@ -0,0 +1,18 @@ +--- +title: 力导向图布局 +order: 0 +--- + +力导向图布局作为较早被发明的一种实际应用布局算法,经过研究者多年改进、扩展,已发展成为一类算法的集合。该类算法的特点是模拟物理世界中的作用力,施加在节点上,并迭代计算以达到合理放置节点、美观布局的一类算法。 + +## 使用指南 + +G6 内置的经典力导向算法引用了 d3.js 的力导向算法。如下面代码所示,可在实例化 Graph 时使用该布局。除此之外,还可以如[子图布局](/zh/docs/manual/middle/layout/sub-layout)所示单独使用布局。 + +- 代码演示 1 :基础的经典力导向布局及节点的拖拽。 +- 代码演示 2 :节点不重叠。 +- 代码演示 3 :为不同节点调整边长和力。 +- 代码演示 4 :固定被拖拽的节点。 +- 代码演示 5 :支持布局参数的动态切换。 +- 代码演示 6 :使用力导向算法实现气泡效果及交互。 +- 代码演示 7 :约束在一定范围内进行力导向布局。 diff --git a/packages/site/examples/net/furchtermanLayout/API.en.md b/packages/site/examples/net/furchtermanLayout/API.en.md new file mode 100644 index 0000000000..fb3b1b21ce --- /dev/null +++ b/packages/site/examples/net/furchtermanLayout/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/furchtermanLayout/API.zh.md b/packages/site/examples/net/furchtermanLayout/API.zh.md new file mode 100644 index 0000000000..5a5fd2e2a8 --- /dev/null +++ b/packages/site/examples/net/furchtermanLayout/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/furchtermanLayout/demo/basicFruchterman.js b/packages/site/examples/net/furchtermanLayout/demo/basicFruchterman.js new file mode 100644 index 0000000000..21d2b1010b --- /dev/null +++ b/packages/site/examples/net/furchtermanLayout/demo/basicFruchterman.js @@ -0,0 +1,452 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + cluster: 'a', + }, + { + id: '1', + label: '1', + cluster: 'a', + }, + { + id: '2', + label: '2', + cluster: 'a', + }, + { + id: '3', + label: '3', + cluster: 'a', + }, + { + id: '4', + label: '4', + cluster: 'a', + }, + { + id: '5', + label: '5', + cluster: 'a', + }, + { + id: '6', + label: '6', + cluster: 'a', + }, + { + id: '7', + label: '7', + cluster: 'a', + }, + { + id: '8', + label: '8', + cluster: 'a', + }, + { + id: '9', + label: '9', + cluster: 'a', + }, + { + id: '10', + label: '10', + cluster: 'a', + }, + { + id: '11', + label: '11', + cluster: 'a', + }, + { + id: '12', + label: '12', + cluster: 'a', + }, + { + id: '13', + label: '13', + cluster: 'b', + }, + { + id: '14', + label: '14', + cluster: 'b', + }, + { + id: '15', + label: '15', + cluster: 'b', + }, + { + id: '16', + label: '16', + cluster: 'b', + }, + { + id: '17', + label: '17', + cluster: 'b', + }, + { + id: '18', + label: '18', + cluster: 'c', + }, + { + id: '19', + label: '19', + cluster: 'c', + }, + { + id: '20', + label: '20', + cluster: 'c', + }, + { + id: '21', + label: '21', + cluster: 'c', + }, + { + id: '22', + label: '22', + cluster: 'c', + }, + { + id: '23', + label: '23', + cluster: 'c', + }, + { + id: '24', + label: '24', + cluster: 'c', + }, + { + id: '25', + label: '25', + cluster: 'c', + }, + { + id: '26', + label: '26', + cluster: 'c', + }, + { + id: '27', + label: '27', + cluster: 'c', + }, + { + id: '28', + label: '28', + cluster: 'c', + }, + { + id: '29', + label: '29', + cluster: 'c', + }, + { + id: '30', + label: '30', + cluster: 'c', + }, + { + id: '31', + label: '31', + cluster: 'd', + }, + { + id: '32', + label: '32', + cluster: 'd', + }, + { + id: '33', + label: '33', + cluster: 'd', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'fruchterman', + gravity: 5, + speed: 5, + // for rendering after each iteration + tick: () => { + graph.refreshPositions() + } + }, + animate: true, + defaultNode: { + size: 30, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/furchtermanLayout/demo/fruchtermanClustering.js b/packages/site/examples/net/furchtermanLayout/demo/fruchtermanClustering.js new file mode 100644 index 0000000000..5e6ca8621c --- /dev/null +++ b/packages/site/examples/net/furchtermanLayout/demo/fruchtermanClustering.js @@ -0,0 +1,499 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + cluster: 'a', + }, + { + id: '1', + label: '1', + cluster: 'a', + }, + { + id: '2', + label: '2', + cluster: 'a', + }, + { + id: '3', + label: '3', + cluster: 'a', + }, + { + id: '4', + label: '4', + cluster: 'a', + }, + { + id: '5', + label: '5', + cluster: 'a', + }, + { + id: '6', + label: '6', + cluster: 'a', + }, + { + id: '7', + label: '7', + cluster: 'a', + }, + { + id: '8', + label: '8', + cluster: 'a', + }, + { + id: '9', + label: '9', + cluster: 'a', + }, + { + id: '10', + label: '10', + cluster: 'a', + }, + { + id: '11', + label: '11', + cluster: 'a', + }, + { + id: '12', + label: '12', + cluster: 'a', + }, + { + id: '13', + label: '13', + cluster: 'b', + }, + { + id: '14', + label: '14', + cluster: 'b', + }, + { + id: '15', + label: '15', + cluster: 'b', + }, + { + id: '16', + label: '16', + cluster: 'b', + }, + { + id: '17', + label: '17', + cluster: 'b', + }, + { + id: '18', + label: '18', + cluster: 'c', + }, + { + id: '19', + label: '19', + cluster: 'c', + }, + { + id: '20', + label: '20', + cluster: 'c', + }, + { + id: '21', + label: '21', + cluster: 'c', + }, + { + id: '22', + label: '22', + cluster: 'c', + }, + { + id: '23', + label: '23', + cluster: 'c', + }, + { + id: '24', + label: '24', + cluster: 'c', + }, + { + id: '25', + label: '25', + cluster: 'c', + }, + { + id: '26', + label: '26', + cluster: 'c', + }, + { + id: '27', + label: '27', + cluster: 'c', + }, + { + id: '28', + label: '28', + cluster: 'c', + }, + { + id: '29', + label: '29', + cluster: 'c', + }, + { + id: '30', + label: '30', + cluster: 'c', + }, + { + id: '31', + label: '31', + cluster: 'd', + }, + { + id: '32', + label: '32', + cluster: 'd', + }, + { + id: '33', + label: '33', + cluster: 'd', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const colors = [ + '#BDD2FD', + '#BDEFDB', + '#C2C8D5', + '#FBE5A2', + '#F6C3B7', + '#B6E3F5', + '#D3C6EA', + '#FFD8B8', + '#AAD8D8', + '#FFD6E7', +]; +const strokes = [ + '#5B8FF9', + '#5AD8A6', + '#5D7092', + '#F6BD16', + '#E8684A', + '#6DC8EC', + '#9270CA', + '#FF9D4D', + '#269A99', + '#FF99C3', +]; + +const nodes = data.nodes; +const clusterMap = new Map(); +let clusterId = 0; +nodes.forEach(function (node) { + // cluster + if (node.cluster && clusterMap.get(node.cluster) === undefined) { + clusterMap.set(node.cluster, clusterId); + clusterId++; + } + const cid = clusterMap.get(node.cluster); + if (!node.style) { + node.style = {}; + } + node.style.fill = colors[cid % colors.length]; + node.style.stroke = strokes[cid % strokes.length]; +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'fruchterman', + gravity: 10, + speed: 5, + clustering: true, + }, + animate: true, + defaultNode: { + size: 20, + }, + defaultEdge: { + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/furchtermanLayout/demo/fruchtermanConfigurationTranslate.js b/packages/site/examples/net/furchtermanLayout/demo/fruchtermanConfigurationTranslate.js new file mode 100644 index 0000000000..41fa60f881 --- /dev/null +++ b/packages/site/examples/net/furchtermanLayout/demo/fruchtermanConfigurationTranslate.js @@ -0,0 +1,542 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + cluster: 'a', + }, + { + id: '1', + label: '1', + cluster: 'a', + }, + { + id: '2', + label: '2', + cluster: 'a', + }, + { + id: '3', + label: '3', + cluster: 'a', + }, + { + id: '4', + label: '4', + cluster: 'a', + }, + { + id: '5', + label: '5', + cluster: 'a', + }, + { + id: '6', + label: '6', + cluster: 'a', + }, + { + id: '7', + label: '7', + cluster: 'a', + }, + { + id: '8', + label: '8', + cluster: 'a', + }, + { + id: '9', + label: '9', + cluster: 'a', + }, + { + id: '10', + label: '10', + cluster: 'a', + }, + { + id: '11', + label: '11', + cluster: 'a', + }, + { + id: '12', + label: '12', + cluster: 'a', + }, + { + id: '13', + label: '13', + cluster: 'b', + }, + { + id: '14', + label: '14', + cluster: 'b', + }, + { + id: '15', + label: '15', + cluster: 'b', + }, + { + id: '16', + label: '16', + cluster: 'b', + }, + { + id: '17', + label: '17', + cluster: 'b', + }, + { + id: '18', + label: '18', + cluster: 'c', + }, + { + id: '19', + label: '19', + cluster: 'c', + }, + { + id: '20', + label: '20', + cluster: 'c', + }, + { + id: '21', + label: '21', + cluster: 'c', + }, + { + id: '22', + label: '22', + cluster: 'c', + }, + { + id: '23', + label: '23', + cluster: 'c', + }, + { + id: '24', + label: '24', + cluster: 'c', + }, + { + id: '25', + label: '25', + cluster: 'c', + }, + { + id: '26', + label: '26', + cluster: 'c', + }, + { + id: '27', + label: '27', + cluster: 'c', + }, + { + id: '28', + label: '28', + cluster: 'c', + }, + { + id: '29', + label: '29', + cluster: 'c', + }, + { + id: '30', + label: '30', + cluster: 'c', + }, + { + id: '31', + label: '31', + cluster: 'd', + }, + { + id: '32', + label: '32', + cluster: 'd', + }, + { + id: '33', + label: '33', + cluster: 'd', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const colors = [ + '#BDD2FD', + '#BDEFDB', + '#C2C8D5', + '#FBE5A2', + '#F6C3B7', + '#B6E3F5', + '#D3C6EA', + '#FFD8B8', + '#AAD8D8', + '#FFD6E7', +]; +const strokes = [ + '#5B8FF9', + '#5AD8A6', + '#5D7092', + '#F6BD16', + '#E8684A', + '#6DC8EC', + '#9270CA', + '#FF9D4D', + '#269A99', + '#FF99C3', +]; + +const nodes = data.nodes; +const clusterMap = new Map(); +let clusterId = 0; +nodes.forEach(function (node) { + // cluster + if (node.cluster && clusterMap.get(node.cluster) === undefined) { + clusterMap.set(node.cluster, clusterId); + clusterId++; + } + const cid = clusterMap.get(node.cluster); + if (!node.style) { + node.style = {}; + } + node.style.fill = colors[cid % colors.length]; + node.style.stroke = strokes[cid % strokes.length]; +}); +const container = document.getElementById('container'); +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Fruchterman layout, gravity = 1'; +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'fruchterman', + gravity: 1, + speed: 5, + }, + animate: true, + defaultNode: { + size: 20, + }, + defaultEdge: { + size: 1, + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; + +layoutConfigTranslation(); + +function layoutConfigTranslation() { + setTimeout(function () { + descriptionDiv.innerHTML = 'Fructherman layout, gravity = 5'; + graph.updateLayout({ + gravity: 5, + }); + }, 1000); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Fructherman layout, gravity = 10, layout by cluster'; + graph.updateLayout({ + gravity: 10, + clustering: true, + }); + }, 2500); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Fructherman layout, gravity = 20, layout by cluster'; + graph.updateLayout({ + gravity: 20, + }); + }, 4000); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Fructherman layout, gravity = 50, layout by cluster'; + graph.updateLayout({ + gravity: 50, + }); + }, 5500); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Fructherman layout, gravity = 80, layout by cluster'; + graph.updateLayout({ + gravity: 80, + }); + }, 7000); +} diff --git a/packages/site/examples/net/furchtermanLayout/demo/fruchtermanFix.js b/packages/site/examples/net/furchtermanLayout/demo/fruchtermanFix.js new file mode 100644 index 0000000000..82a08c00e0 --- /dev/null +++ b/packages/site/examples/net/furchtermanLayout/demo/fruchtermanFix.js @@ -0,0 +1,61 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'fruchterman', + speed: 10, + maxIteration: 500, + // for rendering after each iteration + tick: () => { + graph.refreshPositions() + } + }, + defaultNode: { + size: 15, + }, + fitView: true +}); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + graph.data({ + nodes: data.nodes, + edges: data.edges.map(function (edge, i) { + edge.id = 'edge' + i; + return Object.assign({}, edge); + }), + }); + graph.render(); + + graph.on('node:dragstart', function (e) { + const forceLayout = graph.get('layoutController').layoutMethods[0]; + forceLayout.stop() + }); + + graph.on('node:drag', function (e) { + refreshDragedNodePosition(e); + graph.layout() + }); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); + +function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; + model.x = e.x; + model.y = e.y; +} diff --git a/packages/site/examples/net/furchtermanLayout/demo/fruchtermanWebWorker.js b/packages/site/examples/net/furchtermanLayout/demo/fruchtermanWebWorker.js new file mode 100644 index 0000000000..7fdfe692bf --- /dev/null +++ b/packages/site/examples/net/furchtermanLayout/demo/fruchtermanWebWorker.js @@ -0,0 +1,50 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Doing layout... web-worker is enabled in this demo, so the layout will not block the page.'; +container.appendChild(descriptionDiv); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'fruchterman', + maxIteration: 8000, + gravity: 1, + workerEnabled: true, + }, + animate: true, + defaultNode: { + size: 10, + }, + defaultEdge: { + size: 1, + }, +}); + +graph.on('afterlayout', () => { + descriptionDiv.innerHTML = 'Done!'; +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; + +fetch('https://gw.alipayobjects.com/os/basement_prod/7bacd7d1-4119-4ac1-8be3-4c4b9bcbc25f.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + }); diff --git a/packages/site/examples/net/furchtermanLayout/demo/meta.json b/packages/site/examples/net/furchtermanLayout/demo/meta.json new file mode 100644 index 0000000000..b5b0e750be --- /dev/null +++ b/packages/site/examples/net/furchtermanLayout/demo/meta.json @@ -0,0 +1,48 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicFruchterman.js", + "title": { + "zh": "基本 Fruchterman 布局", + "en": "Basic Fruchterman Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*1KY7SLEXxqMAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "fruchtermanClustering.js", + "title": { + "zh": "Fruchterman 聚类布局", + "en": "Fruchterman with Clustering" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*WO1OTbNE_ugAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "fruchtermanConfigurationTranslate.js", + "title": { + "zh": "Fruchterman 布局参数动态变化", + "en": "Update the Configurations for Fruchterman" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*W9MoQoKaQbYAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "fruchtermanWebWorker.js", + "title": { + "zh": "Fruchterman 使用 Web-worker", + "en": "Fruchterman with Web-worker" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*0sl9RZ7Cp28AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "fruchtermanFix.js", + "title": { + "zh": "Fruchterman 固定被拖拽的节点", + "en": "Fruchterman with Fixing Dragged Nodes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*fQNPRKHPJRMAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/furchtermanLayout/index.en.md b/packages/site/examples/net/furchtermanLayout/index.en.md new file mode 100644 index 0000000000..726384e5cf --- /dev/null +++ b/packages/site/examples/net/furchtermanLayout/index.en.md @@ -0,0 +1,15 @@ +--- +title: Fruchterman Layout +order: 2 +--- + +Fruchterman Reingold Layout is a kind of force-directed layout in theory. The differences are the definitions of attracitve force and repulsive force. + +## Usage + +As the demo below, you can deploy it in `layout` while instantiating Graph. it can also be used for [Subgraph Layout](/en/docs/manual/middle/layout/sub-layout). By tuning the parameters, you can adjust the iteration number, layout compactness, layout by clusters, and so on. + +- Example 1 : Basic Fruchterman layout. +- Example 2 : Fruchterman clustering layout. +- Example 3 : Translate the layout parameters in dynamic. +- Example 4 : Fruchterman layout with web-worker in case layout calculation takes too long to block page interaction. diff --git a/packages/site/examples/net/furchtermanLayout/index.zh.md b/packages/site/examples/net/furchtermanLayout/index.zh.md new file mode 100644 index 0000000000..c0c14a23f6 --- /dev/null +++ b/packages/site/examples/net/furchtermanLayout/index.zh.md @@ -0,0 +1,15 @@ +--- +title: Fruchterman 图布局 +order: 2 +--- + +Fruchterman Reingold 布局算法在原理上而言属于力导向布局算法。其引力与斥力的定义方式与经典的 Force Diected 力导向图布局有少许不同。 + +## 使用指南 + +G6 内置的 Fruchterman 布局可在实例化 Graph 时使用该布局。除此之外,还可以如[子图布局](/zh/docs/manual/middle/layout/sub-layout)所示单独使用布局。该布局可以通过配置调整迭代次数、紧凑程度、是否按照聚类布局等。 + +- 代码演示 1 :基本的 Fruchterman 布局。 +- 代码演示 2 :Fruchterman 的聚类布局。 +- 代码演示 3 :Fruchterman 布局参数动态变化。 +- 代码演示 4 :Fruchterman 使用 web-worker 以避免阻塞页面。 diff --git a/packages/site/examples/net/gpuLayout/API.en.md b/packages/site/examples/net/gpuLayout/API.en.md new file mode 100644 index 0000000000..a2253244c4 --- /dev/null +++ b/packages/site/examples/net/gpuLayout/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/gpuLayout/API.zh.md b/packages/site/examples/net/gpuLayout/API.zh.md new file mode 100644 index 0000000000..811034462b --- /dev/null +++ b/packages/site/examples/net/gpuLayout/API.zh.md @@ -0,0 +1,12 @@ +--- +title: API +--- + +G6 4.0 推出两种支持 GPU 布局: + +- gforce:全新内置布局,实现了经典的力导向算法; +- fruchterman:另一种力导向的变种算法,CPU 版本在之前的版本就已经存在,G6 4.0 支持使用 GPU 并行计算该算法。 + +由于 GPU 与 CPU 通信的拷贝效率原因,在小规模图上 GPU 版本的布局提升不大,甚至可能更慢。但在较大、大规模图上,计算性能大大提升,升至超过 CPU 的百倍。下面两张表格对比了两个算法在不同数据规模、不同算法下 GPU 与 CPU 的计算时间: + + diff --git a/packages/site/examples/net/gpuLayout/demo/basicFruchterman.js b/packages/site/examples/net/gpuLayout/demo/basicFruchterman.js new file mode 100644 index 0000000000..20e1d6c2d3 --- /dev/null +++ b/packages/site/examples/net/gpuLayout/demo/basicFruchterman.js @@ -0,0 +1,464 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + cluster: 'a', + }, + { + id: '1', + label: '1', + cluster: 'a', + }, + { + id: '2', + label: '2', + cluster: 'a', + }, + { + id: '3', + label: '3', + cluster: 'a', + }, + { + id: '4', + label: '4', + cluster: 'a', + }, + { + id: '5', + label: '5', + cluster: 'a', + }, + { + id: '6', + label: '6', + cluster: 'a', + }, + { + id: '7', + label: '7', + cluster: 'a', + }, + { + id: '8', + label: '8', + cluster: 'a', + }, + { + id: '9', + label: '9', + cluster: 'a', + }, + { + id: '10', + label: '10', + cluster: 'a', + }, + { + id: '11', + label: '11', + cluster: 'a', + }, + { + id: '12', + label: '12', + cluster: 'a', + }, + { + id: '13', + label: '13', + cluster: 'b', + }, + { + id: '14', + label: '14', + cluster: 'b', + }, + { + id: '15', + label: '15', + cluster: 'b', + }, + { + id: '16', + label: '16', + cluster: 'b', + }, + { + id: '17', + label: '17', + cluster: 'b', + }, + { + id: '18', + label: '18', + cluster: 'c', + }, + { + id: '19', + label: '19', + cluster: 'c', + }, + { + id: '20', + label: '20', + cluster: 'c', + }, + { + id: '21', + label: '21', + cluster: 'c', + }, + { + id: '22', + label: '22', + cluster: 'c', + }, + { + id: '23', + label: '23', + cluster: 'c', + }, + { + id: '24', + label: '24', + cluster: 'c', + }, + { + id: '25', + label: '25', + cluster: 'c', + }, + { + id: '26', + label: '26', + cluster: 'c', + }, + { + id: '27', + label: '27', + cluster: 'c', + }, + { + id: '28', + label: '28', + cluster: 'c', + }, + { + id: '29', + label: '29', + cluster: 'c', + }, + { + id: '30', + label: '30', + cluster: 'c', + }, + { + id: '31', + label: '31', + cluster: 'd', + }, + { + id: '32', + label: '32', + cluster: 'd', + }, + { + id: '33', + label: '33', + cluster: 'd', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + animate: true, + defaultNode: { + size: 30, + style: { + lineWidth: 2, + stroke: '#5B8FF9', + fill: '#C6E5FF', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, + layout: { + type: 'fruchterman', + gpuEnabled: true, + maxIteration: 300, + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/gpuLayout/demo/basicGForce.js b/packages/site/examples/net/gpuLayout/demo/basicGForce.js new file mode 100644 index 0000000000..3de59801a4 --- /dev/null +++ b/packages/site/examples/net/gpuLayout/demo/basicGForce.js @@ -0,0 +1,179 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { x: 72.4593956315343, y: 62.500580986434755, id: '0', label: '0', cluster: 'a' }, + { x: 175.1600873958553, y: 86.05370537028077, id: '1', label: '1', cluster: 'a' }, + { x: 344.9769062055303, y: 346.0034481446168, id: '2', label: '2', cluster: 'a' }, + { x: 81.6566555038367, y: 449.54521190792946, id: '3', label: '3', cluster: 'a' }, + { x: 216.88282150662286, y: 154.6366915677718, id: '4', label: '4', cluster: 'a' }, + { x: 607.4192016090308, y: 29.68981871724924, id: '5', label: '5', cluster: 'a' }, + { x: 608.1952294163268, y: 346.0504788285849, id: '6', label: '6', cluster: 'a' }, + { x: 452.20735149164864, y: 44.41509530420839, id: '7', label: '7', cluster: 'a' }, + { x: 385.8029314535722, y: 345.77605366469146, id: '8', label: '8', cluster: 'a' }, + { x: 221.9577525643851, y: 314.49923076828685, id: '9', label: '9', cluster: 'a' }, + { x: 476.3993667720199, y: 324.90015915221954, id: '10', label: '10', cluster: 'a' }, + { x: 285.856260625802, y: 478.7175936401358, id: '11', label: '11', cluster: 'a' }, + { x: 454.0091937006662, y: 244.40185112543793, id: '12', label: '12', cluster: 'a' }, + { x: 85.85356888467777, y: 202.4891680926948, id: '13', label: '13', cluster: 'b' }, + { x: 178.43553862412497, y: 248.0194739727555, id: '14', label: '14', cluster: 'b' }, + { x: 83.56438018013918, y: 354.8762429461217, id: '15', label: '15', cluster: 'b' }, + { x: 282.627196085794, y: 455.5720951259284, id: '16', label: '16', cluster: 'b' }, + { x: 242.3547537166361, y: 98.66129908712844, id: '17', label: '17', cluster: 'b' }, + { x: 602.1167288184395, y: 269.168861245467, id: '18', label: '18', cluster: 'c' }, + { x: 483.79272921167114, y: 450.6376205219211, id: '19', label: '19', cluster: 'c' }, + { x: 297.187609794337, y: 457.588179255913, id: '20', label: '20', cluster: 'c' }, + { x: 460.9705509891003, y: 40.428685184792954, id: '21', label: '21', cluster: 'c' }, + { x: 160.6332922414413, y: 120.21023271187795, id: '22', label: '22', cluster: 'c' }, + { x: 512.008587934917, y: 418.6500912111135, id: '23', label: '23', cluster: 'c' }, + { x: 520.0165179629071, y: 27.93522012918939, id: '24', label: '24', cluster: 'c' }, + { x: 86.3930775510143, y: 151.71498826445242, id: '25', label: '25', cluster: 'c' }, + { x: 69.5835581684608, y: 51.79679194816802, id: '26', label: '26', cluster: 'c' }, + { x: 290.3934620883672, y: 33.385661601435174, id: '27', label: '27', cluster: 'c' }, + { x: 162.64554141115855, y: 210.26903302812224, id: '28', label: '28', cluster: 'c' }, + { x: 360.8317809241701, y: 91.48175269950366, id: '29', label: '29', cluster: 'c' }, + { x: 47.240097508368194, y: 145.50344892493422, id: '30', label: '30', cluster: 'c' }, + { x: 327.2099811985623, y: 92.12781674932864, id: '31', label: '31', cluster: 'd' }, + { x: 241.12704982623336, y: 337.86999370236475, id: '32', label: '32', cluster: 'd' }, + { x: 205.79508596449074, y: 102.31474420745676, id: '33', label: '33', cluster: 'd' }, + ], + edges: [ + { source: '0', target: '1' }, + { source: '0', target: '2' }, + { source: '0', target: '3' }, + { source: '0', target: '4' }, + { source: '0', target: '5' }, + { source: '0', target: '7' }, + { source: '0', target: '8' }, + { source: '0', target: '9' }, + { source: '0', target: '10' }, + { source: '0', target: '11' }, + { source: '0', target: '13' }, + { source: '0', target: '14' }, + { source: '0', target: '15' }, + { source: '0', target: '16' }, + { source: '2', target: '3' }, + { source: '4', target: '5' }, + { source: '4', target: '6' }, + { source: '5', target: '6' }, + { source: '7', target: '13' }, + { source: '8', target: '14' }, + { source: '9', target: '10' }, + { source: '10', target: '22' }, + { source: '10', target: '14' }, + { source: '10', target: '12' }, + { source: '10', target: '24' }, + { source: '10', target: '21' }, + { source: '10', target: '20' }, + { source: '11', target: '24' }, + { source: '11', target: '22' }, + { source: '11', target: '14' }, + { source: '12', target: '13' }, + { source: '16', target: '17' }, + { source: '16', target: '18' }, + { source: '16', target: '21' }, + { source: '16', target: '22' }, + { source: '17', target: '18' }, + { source: '17', target: '20' }, + { source: '18', target: '19' }, + { source: '19', target: '20' }, + { source: '19', target: '33' }, + { source: '19', target: '22' }, + { source: '19', target: '23' }, + { source: '20', target: '21' }, + { source: '21', target: '22' }, + { source: '22', target: '24' }, + { source: '22', target: '25' }, + { source: '22', target: '26' }, + { source: '22', target: '23' }, + { source: '22', target: '28' }, + { source: '22', target: '30' }, + { source: '22', target: '31' }, + { source: '22', target: '32' }, + { source: '22', target: '33' }, + { source: '23', target: '28' }, + { source: '23', target: '27' }, + { source: '23', target: '29' }, + { source: '23', target: '30' }, + { source: '23', target: '31' }, + { source: '23', target: '33' }, + { source: '32', target: '33' }, + ], +}; + +const data2 = { + nodes: [ + { x: 72.4593956315343, y: 62.500580986434755, id: '0', label: '0', cluster: 'a' }, + { x: 175.1600873958553, y: 86.05370537028077, id: '1', label: '1', cluster: 'a' }, + { x: 344.9769062055303, y: 346.0034481446168, id: '2', label: '2', cluster: 'a' }, + { x: 81.6566555038367, y: 449.54521190792946, id: '3', label: '3', cluster: 'a' }, + { x: 216.88282150662286, y: 154.6366915677718, id: '4', label: '4', cluster: 'a' }, + { x: 607.4192016090308, y: 29.68981871724924, id: '5', label: '5', cluster: 'a' }, + ], + edges: [ + { source: '0', target: '1' }, + { source: '0', target: '2' }, + { source: '0', target: '3' }, + { source: '4', target: '3' }, + { source: '4', target: '5' }, + ], +}; +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node', 'zoom-canvas'], + }, + animate: true, + defaultNode: { + size: 30, + style: { + lineWidth: 2, + stroke: '#5B8FF9', + fill: '#C6E5FF', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, + layout: { + type: 'gForce', + gpuEnabled: true, + maxIteration: 500, + linkDistance: (e) => { + if (e.source === '0') return 100; + return 1; + }, + }, +}); + +graph.data(data); +graph.render(); + +graph.on('canvas:click', (e) => { + setTimeout(() => { + // graph.updateLayout({ + // gravity: 100, + // maxIteration: 1 + // }); + graph.changeData(data2); + }, 1000); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/gpuLayout/demo/frComplexDataWorker.js b/packages/site/examples/net/gpuLayout/demo/frComplexDataWorker.js new file mode 100644 index 0000000000..0c0c144fa3 --- /dev/null +++ b/packages/site/examples/net/gpuLayout/demo/frComplexDataWorker.js @@ -0,0 +1,61 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Doing layout... web-worker is enabled in this demo, so the layout will not block the page.'; +container.appendChild(descriptionDiv); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + animate: true, + defaultNode: { + size: 10, + style: { + lineWidth: 2, + stroke: '#5B8FF9', + fill: '#C6E5FF', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, + layout: { + type: 'fruchterman', + gpuEnabled: true, + workerEnabled: true, + maxIteration: 2000, + }, +}); + +graph.on('afterlayout', () => { + descriptionDiv.innerHTML = 'Done!'; +}); + +fetch('https://gw.alipayobjects.com/os/basement_prod/7bacd7d1-4119-4ac1-8be3-4c4b9bcbc25f.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/gpuLayout/demo/fruchtermanClustering.js b/packages/site/examples/net/gpuLayout/demo/fruchtermanClustering.js new file mode 100644 index 0000000000..cc3981bfc3 --- /dev/null +++ b/packages/site/examples/net/gpuLayout/demo/fruchtermanClustering.js @@ -0,0 +1,503 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + cluster: 'a', + }, + { + id: '1', + label: '1', + cluster: 'a', + }, + { + id: '2', + label: '2', + cluster: 'a', + }, + { + id: '3', + label: '3', + cluster: 'a', + }, + { + id: '4', + label: '4', + cluster: 'a', + }, + { + id: '5', + label: '5', + cluster: 'a', + }, + { + id: '6', + label: '6', + cluster: 'a', + }, + { + id: '7', + label: '7', + cluster: 'a', + }, + { + id: '8', + label: '8', + cluster: 'a', + }, + { + id: '9', + label: '9', + cluster: 'a', + }, + { + id: '10', + label: '10', + cluster: 'a', + }, + { + id: '11', + label: '11', + cluster: 'a', + }, + { + id: '12', + label: '12', + cluster: 'a', + }, + { + id: '13', + label: '13', + cluster: 'b', + }, + { + id: '14', + label: '14', + cluster: 'b', + }, + { + id: '15', + label: '15', + cluster: 'b', + }, + { + id: '16', + label: '16', + cluster: 'b', + }, + { + id: '17', + label: '17', + cluster: 'b', + }, + { + id: '18', + label: '18', + cluster: 'c', + }, + { + id: '19', + label: '19', + cluster: 'c', + }, + { + id: '20', + label: '20', + cluster: 'c', + }, + { + id: '21', + label: '21', + cluster: 'c', + }, + { + id: '22', + label: '22', + cluster: 'c', + }, + { + id: '23', + label: '23', + cluster: 'c', + }, + { + id: '24', + label: '24', + cluster: 'c', + }, + { + id: '25', + label: '25', + cluster: 'c', + }, + { + id: '26', + label: '26', + cluster: 'c', + }, + { + id: '27', + label: '27', + cluster: 'c', + }, + { + id: '28', + label: '28', + cluster: 'c', + }, + { + id: '29', + label: '29', + cluster: 'c', + }, + { + id: '30', + label: '30', + cluster: 'c', + }, + { + id: '31', + label: '31', + cluster: 'd', + }, + { + id: '32', + label: '32', + cluster: 'd', + }, + { + id: '33', + label: '33', + cluster: 'd', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const colors = [ + '#BDD2FD', + '#BDEFDB', + '#C2C8D5', + '#FBE5A2', + '#F6C3B7', + '#B6E3F5', + '#D3C6EA', + '#FFD8B8', + '#AAD8D8', + '#FFD6E7', +]; +const strokes = [ + '#5B8FF9', + '#5AD8A6', + '#5D7092', + '#F6BD16', + '#E8684A', + '#6DC8EC', + '#9270CA', + '#FF9D4D', + '#269A99', + '#FF99C3', +]; + +const nodes = data.nodes; +const clusterMap = new Map(); +let clusterId = 0; +nodes.forEach(function (node) { + // cluster + if (node.cluster && clusterMap.get(node.cluster) === undefined) { + clusterMap.set(node.cluster, clusterId); + clusterId++; + } + const cid = clusterMap.get(node.cluster); + if (!node.style) { + node.style = {}; + } + node.style.fill = colors[cid % colors.length]; + node.style.stroke = strokes[cid % strokes.length]; +}); +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + animate: true, + defaultNode: { + size: 20, + style: { + lineWidth: 2, + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, + layout: { + type: 'fruchterman', + gpuEnabled: true, + clustering: true, + maxIteration: 300, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/gpuLayout/demo/fruchtermanComplexData.js b/packages/site/examples/net/gpuLayout/demo/fruchtermanComplexData.js new file mode 100644 index 0000000000..80605f4186 --- /dev/null +++ b/packages/site/examples/net/gpuLayout/demo/fruchtermanComplexData.js @@ -0,0 +1,50 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + animate: true, + defaultNode: { + size: 10, + style: { + lineWidth: 2, + stroke: '#5B8FF9', + fill: '#C6E5FF', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, + layout: { + type: 'fruchterman', + gpuEnabled: true, + }, +}); + +fetch('https://gw.alipayobjects.com/os/basement_prod/7bacd7d1-4119-4ac1-8be3-4c4b9bcbc25f.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/gpuLayout/demo/fruchtermanConfigurationTranslate.js b/packages/site/examples/net/gpuLayout/demo/fruchtermanConfigurationTranslate.js new file mode 100644 index 0000000000..11d1564deb --- /dev/null +++ b/packages/site/examples/net/gpuLayout/demo/fruchtermanConfigurationTranslate.js @@ -0,0 +1,546 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + cluster: 'a', + }, + { + id: '1', + label: '1', + cluster: 'a', + }, + { + id: '2', + label: '2', + cluster: 'a', + }, + { + id: '3', + label: '3', + cluster: 'a', + }, + { + id: '4', + label: '4', + cluster: 'a', + }, + { + id: '5', + label: '5', + cluster: 'a', + }, + { + id: '6', + label: '6', + cluster: 'a', + }, + { + id: '7', + label: '7', + cluster: 'a', + }, + { + id: '8', + label: '8', + cluster: 'a', + }, + { + id: '9', + label: '9', + cluster: 'a', + }, + { + id: '10', + label: '10', + cluster: 'a', + }, + { + id: '11', + label: '11', + cluster: 'a', + }, + { + id: '12', + label: '12', + cluster: 'a', + }, + { + id: '13', + label: '13', + cluster: 'b', + }, + { + id: '14', + label: '14', + cluster: 'b', + }, + { + id: '15', + label: '15', + cluster: 'b', + }, + { + id: '16', + label: '16', + cluster: 'b', + }, + { + id: '17', + label: '17', + cluster: 'b', + }, + { + id: '18', + label: '18', + cluster: 'c', + }, + { + id: '19', + label: '19', + cluster: 'c', + }, + { + id: '20', + label: '20', + cluster: 'c', + }, + { + id: '21', + label: '21', + cluster: 'c', + }, + { + id: '22', + label: '22', + cluster: 'c', + }, + { + id: '23', + label: '23', + cluster: 'c', + }, + { + id: '24', + label: '24', + cluster: 'c', + }, + { + id: '25', + label: '25', + cluster: 'c', + }, + { + id: '26', + label: '26', + cluster: 'c', + }, + { + id: '27', + label: '27', + cluster: 'c', + }, + { + id: '28', + label: '28', + cluster: 'c', + }, + { + id: '29', + label: '29', + cluster: 'c', + }, + { + id: '30', + label: '30', + cluster: 'c', + }, + { + id: '31', + label: '31', + cluster: 'd', + }, + { + id: '32', + label: '32', + cluster: 'd', + }, + { + id: '33', + label: '33', + cluster: 'd', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const colors = [ + '#BDD2FD', + '#BDEFDB', + '#C2C8D5', + '#FBE5A2', + '#F6C3B7', + '#B6E3F5', + '#D3C6EA', + '#FFD8B8', + '#AAD8D8', + '#FFD6E7', +]; +const strokes = [ + '#5B8FF9', + '#5AD8A6', + '#5D7092', + '#F6BD16', + '#E8684A', + '#6DC8EC', + '#9270CA', + '#FF9D4D', + '#269A99', + '#FF99C3', +]; + +const nodes = data.nodes; +const clusterMap = new Map(); +let clusterId = 0; +nodes.forEach(function (node) { + // cluster + if (node.cluster && clusterMap.get(node.cluster) === undefined) { + clusterMap.set(node.cluster, clusterId); + clusterId++; + } + const cid = clusterMap.get(node.cluster); + if (!node.style) { + node.style = {}; + } + node.style.fill = colors[cid % colors.length]; + node.style.stroke = strokes[cid % strokes.length]; +}); +const graphDiv = document.getElementById('container'); +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Fruchterman layout, gravity = 1'; +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'fruchterman', + gravity: 1, + speed: 5, + enableGPU: true, + }, + animate: true, + defaultNode: { + size: 20, + style: { + lineWidth: 2, + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); +}; + +layoutConfigTranslation(); + +function layoutConfigTranslation() { + setTimeout(function () { + descriptionDiv.innerHTML = 'Fructherman layout, gravity = 5'; + graph.updateLayout({ + gravity: 5, + }); + }, 1000); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Fructherman layout, gravity = 10, layout by cluster'; + graph.updateLayout({ + gravity: 10, + clustering: true, + }); + }, 2500); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Fructherman layout, gravity = 20, layout by cluster'; + graph.updateLayout({ + gravity: 20, + }); + }, 4000); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Fructherman layout, gravity = 50, layout by cluster'; + graph.updateLayout({ + gravity: 50, + }); + }, 5500); + + setTimeout(function () { + descriptionDiv.innerHTML = 'Fructherman layout, gravity = 80, layout by cluster'; + graph.updateLayout({ + gravity: 80, + }); + }, 7000); +} diff --git a/packages/site/examples/net/gpuLayout/demo/gForceCPU.js b/packages/site/examples/net/gpuLayout/demo/gForceCPU.js new file mode 100644 index 0000000000..54e57d9807 --- /dev/null +++ b/packages/site/examples/net/gpuLayout/demo/gForceCPU.js @@ -0,0 +1,166 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'des1' }, + { id: 'des2' }, + { id: 'des3' }, + { id: 'des4' }, + { x: 72.4593956315343, y: 62.500580986434755, id: '0', label: '0', cluster: 'a' }, + { x: 175.1600873958553, y: 86.05370537028077, id: '1', label: '1', cluster: 'a' }, + { x: 344.9769062055303, y: 346.0034481446168, id: '2', label: '2', cluster: 'a' }, + { x: 81.6566555038367, y: 449.54521190792946, id: '3', label: '3', cluster: 'a' }, + { x: 216.88282150662286, y: 154.6366915677718, id: '4', label: '4', cluster: 'a' }, + { x: 607.4192016090308, y: 29.68981871724924, id: '5', label: '5', cluster: 'a' }, + { x: 608.1952294163268, y: 346.0504788285849, id: '6', label: '6', cluster: 'a' }, + { x: 452.20735149164864, y: 44.41509530420839, id: '7', label: '7', cluster: 'a' }, + { x: 385.8029314535722, y: 345.77605366469146, id: '8', label: '8', cluster: 'a' }, + { x: 221.9577525643851, y: 314.49923076828685, id: '9', label: '9', cluster: 'a' }, + { x: 476.3993667720199, y: 324.90015915221954, id: '10', label: '10', cluster: 'a' }, + { x: 285.856260625802, y: 478.7175936401358, id: '11', label: '11', cluster: 'a' }, + { x: 454.0091937006662, y: 244.40185112543793, id: '12', label: '12', cluster: 'a' }, + { x: 85.85356888467777, y: 202.4891680926948, id: '13', label: '13', cluster: 'b' }, + { x: 178.43553862412497, y: 248.0194739727555, id: '14', label: '14', cluster: 'b' }, + { x: 83.56438018013918, y: 354.8762429461217, id: '15', label: '15', cluster: 'b' }, + { x: 282.627196085794, y: 455.5720951259284, id: '16', label: '16', cluster: 'b' }, + { x: 242.3547537166361, y: 98.66129908712844, id: '17', label: '17', cluster: 'b' }, + { x: 602.1167288184395, y: 269.168861245467, id: '18', label: '18', cluster: 'c' }, + { x: 483.79272921167114, y: 450.6376205219211, id: '19', label: '19', cluster: 'c' }, + { x: 297.187609794337, y: 457.588179255913, id: '20', label: '20', cluster: 'c' }, + { x: 460.9705509891003, y: 40.428685184792954, id: '21', label: '21', cluster: 'c' }, + { x: 160.6332922414413, y: 120.21023271187795, id: '22', label: '22', cluster: 'c' }, + { x: 512.008587934917, y: 418.6500912111135, id: '23', label: '23', cluster: 'c' }, + { x: 520.0165179629071, y: 27.93522012918939, id: '24', label: '24', cluster: 'c' }, + { x: 86.3930775510143, y: 151.71498826445242, id: '25', label: '25', cluster: 'c' }, + { x: 69.5835581684608, y: 51.79679194816802, id: '26', label: '26', cluster: 'c' }, + { x: 290.3934620883672, y: 33.385661601435174, id: '27', label: '27', cluster: 'c' }, + { x: 162.64554141115855, y: 210.26903302812224, id: '28', label: '28', cluster: 'c' }, + { x: 360.8317809241701, y: 91.48175269950366, id: '29', label: '29', cluster: 'c' }, + { x: 47.240097508368194, y: 145.50344892493422, id: '30', label: '30', cluster: 'c' }, + { x: 327.2099811985623, y: 92.12781674932864, id: '31', label: '31', cluster: 'd' }, + { x: 241.12704982623336, y: 337.86999370236475, id: '32', label: '32', cluster: 'd' }, + { x: 205.79508596449074, y: 102.31474420745676, id: '33', label: '33', cluster: 'd' }, + ], + edges: [ + { source: '0', target: '1' }, + { source: '0', target: '2' }, + { source: '0', target: '3' }, + { source: '0', target: '4' }, + { source: '0', target: '5' }, + { source: '0', target: '7' }, + { source: '0', target: '8' }, + { source: '0', target: '9' }, + { source: '0', target: '10' }, + { source: '0', target: '11' }, + { source: '0', target: '13' }, + { source: '0', target: '14' }, + { source: '0', target: '15' }, + { source: '0', target: '16' }, + { source: '2', target: '3' }, + { source: '4', target: '5' }, + { source: '4', target: '6' }, + { source: '5', target: '6' }, + { source: '7', target: '13' }, + { source: '8', target: '14' }, + { source: '9', target: '10' }, + { source: '10', target: '22' }, + { source: '10', target: '14' }, + { source: '10', target: '12' }, + { source: '10', target: '24' }, + { source: '10', target: '21' }, + { source: '10', target: '20' }, + { source: '11', target: '24' }, + { source: '11', target: '22' }, + { source: '11', target: '14' }, + { source: '12', target: '13' }, + { source: '16', target: '17' }, + { source: '16', target: '18' }, + { source: '16', target: '21' }, + { source: '16', target: '22' }, + { source: '17', target: '18' }, + { source: '17', target: '20' }, + { source: '18', target: '19' }, + { source: '19', target: '20' }, + { source: '19', target: '33' }, + { source: '19', target: '22' }, + { source: '19', target: '23' }, + { source: '20', target: '21' }, + { source: '21', target: '22' }, + { source: '22', target: '24' }, + { source: '22', target: '25' }, + { source: '22', target: '26' }, + { source: '22', target: '23' }, + { source: '22', target: '28' }, + { source: '22', target: '30' }, + { source: '22', target: '31' }, + { source: '22', target: '32' }, + { source: '22', target: '33' }, + { source: '23', target: '28' }, + { source: '23', target: '27' }, + { source: '23', target: '29' }, + { source: '23', target: '30' }, + { source: '23', target: '31' }, + { source: '23', target: '33' }, + { source: '32', target: '33' }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node', 'zoom-canvas'], + }, + animate: true, + defaultNode: { + size: 30, + style: { + lineWidth: 2, + stroke: '#5B8FF9', + fill: '#C6E5FF', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, + layout: { + type: 'gForce', + // gravity: 500, + //preventOverlap: true, + maxIteration: 500, + gatherDiscrete: true, + //nodeSize: 100, + //nodeSpacing: 100, + //gatherDiscreteCenter: [500, 100], + descreteGravity: 200, + linkDistanceFunc: (e) => { + if (e.source === '0') return 10; + return 1; + }, + getMass: (d) => { + if (d.id === '0') return 100; + return 1; + }, + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/gpuLayout/demo/gForceComplexData.js b/packages/site/examples/net/gpuLayout/demo/gForceComplexData.js new file mode 100644 index 0000000000..9512748ba7 --- /dev/null +++ b/packages/site/examples/net/gpuLayout/demo/gForceComplexData.js @@ -0,0 +1,51 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + animate: true, + defaultNode: { + size: 10, + style: { + lineWidth: 2, + stroke: '#5B8FF9', + fill: '#C6E5FF', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, + layout: { + type: 'gForce', + gpuEnabled: true, + maxIteration: 1000, + }, +}); + +fetch('https://gw.alipayobjects.com/os/basement_prod/7bacd7d1-4119-4ac1-8be3-4c4b9bcbc25f.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/gpuLayout/demo/meta.json b/packages/site/examples/net/gpuLayout/demo/meta.json new file mode 100644 index 0000000000..ee991627b1 --- /dev/null +++ b/packages/site/examples/net/gpuLayout/demo/meta.json @@ -0,0 +1,72 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicFruchterman.js", + "title": { + "zh": "GPU Fruchterman 小数据", + "en": "GPU Fruchterman Small Data" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*1KY7SLEXxqMAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "fruchtermanComplexData.js", + "title": { + "zh": "GPU Fruchterman 复杂数据", + "en": "GPU Fruchterman with Complex Data" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*0sl9RZ7Cp28AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "frComplexDataWorker.js", + "title": { + "zh": "GPU Fruchterman 复杂数据 + WebWorker", + "en": "Fruchterman Layout with GPU and WebWorker" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*0sl9RZ7Cp28AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "fruchtermanClustering.js", + "title": { + "zh": "GPU Fruchterman 聚类布局", + "en": "GPU Fruchterman with Clustering" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*WO1OTbNE_ugAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "fruchtermanConfigurationTranslate.js", + "title": { + "zh": "GPU Fruchterman 布局参数动态变化", + "en": "GPU Update the Configurations for Fruchterman" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*W9MoQoKaQbYAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "basicGForce.js", + "title": { + "zh": "GPU gForce 小数据", + "en": "GPU gForce" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*W9MoQoKaQbYAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "gForceComplexData.js", + "title": { + "zh": "GPU gForce 复杂数据", + "en": "GPU gForce with Complex Data" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*2lg0SYvEiusAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "gForceCPU.js", + "title": { + "zh": "CPU gForce 小数据", + "en": "CPU gForce Small Data" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*lX-qSqDECrIAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/gpuLayout/index.en.md b/packages/site/examples/net/gpuLayout/index.en.md new file mode 100644 index 0000000000..ca618417af --- /dev/null +++ b/packages/site/examples/net/gpuLayout/index.en.md @@ -0,0 +1,13 @@ +--- +title: GPU Layout +order: 12 +--- + +G6 4.0 provides two kinds of GPU layout: + +- gforce: a new built-in layout that support CPU and GPU calculation, it implements the classical force-directed algorithm; +- fruchterman: anoher force-directed algorithm whose CPU version is supported in previous version, and G6 4.0 support GPU version fruchterman. + +Since the mechanism of the memory copy between GPU and CPU, the improvement is not obviouse in small graph, sometimes GPU version even performs worse than CPU. But in middle and large graph, the calculation speed of GPU might be hundreds times of CPU. The two table below shows the comparison on different scale datasets: + + diff --git a/packages/site/examples/net/gpuLayout/index.zh.md b/packages/site/examples/net/gpuLayout/index.zh.md new file mode 100644 index 0000000000..d7a8748f05 --- /dev/null +++ b/packages/site/examples/net/gpuLayout/index.zh.md @@ -0,0 +1,13 @@ +--- +title: GPU 图布局 +order: 12 +--- + +G6 4.0 推出两种支持 GPU 布局: + +- gforce:全新内置布局,实现了经典的力导向算法; +- fruchterman:另一种力导向的变种算法,CPU 版本在之前的版本就已经存在,G6 4.0 支持使用 GPU 并行计算该算法。 + +由于 GPU 与 CPU 通信的拷贝效率原因,在小规模图上 GPU 版本的布局提升不大,甚至可能更慢。但在较大、大规模图上,计算性能大大提升,升至超过 CPU 的百倍。下面两张表格对比了两个算法在不同数据规模、不同算法下 GPU 与 CPU 的计算时间: + + diff --git a/packages/site/examples/net/gridLayout/API.en.md b/packages/site/examples/net/gridLayout/API.en.md new file mode 100644 index 0000000000..2ca44ea11a --- /dev/null +++ b/packages/site/examples/net/gridLayout/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/gridLayout/API.zh.md b/packages/site/examples/net/gridLayout/API.zh.md new file mode 100644 index 0000000000..7f040391c3 --- /dev/null +++ b/packages/site/examples/net/gridLayout/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/gridLayout/demo/basicGrid.js b/packages/site/examples/net/gridLayout/demo/basicGrid.js new file mode 100644 index 0000000000..50bfcd8e6f --- /dev/null +++ b/packages/site/examples/net/gridLayout/demo/basicGrid.js @@ -0,0 +1,415 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['zoom-canvas', 'drag-canvas', 'drag-node'], + }, + layout: { + type: 'grid', + begin: [20, 20], + width: width - 20, + height: height - 20, + }, + animate: true, + defaultNode: { + size: 20, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/gridLayout/demo/clusterGrid.js b/packages/site/examples/net/gridLayout/demo/clusterGrid.js new file mode 100644 index 0000000000..74e9174c43 --- /dev/null +++ b/packages/site/examples/net/gridLayout/demo/clusterGrid.js @@ -0,0 +1,490 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + cluster: '0', + }, + { + id: '1', + label: '1', + cluster: '0', + }, + { + id: '2', + label: '2', + cluster: '0', + }, + { + id: '3', + label: '3', + cluster: '0', + }, + { + id: '4', + label: '4', + cluster: '0', + }, + { + id: '5', + label: '5', + cluster: '3', + }, + { + id: '6', + label: '6', + cluster: '0', + }, + { + id: '7', + label: '7', + cluster: '0', + }, + { + id: '8', + label: '8', + cluster: '0', + }, + { + id: '9', + label: '9', + cluster: '3', + }, + { + id: '10', + label: '10', + cluster: '3', + }, + { + id: '11', + label: '11', + cluster: '2', + }, + { + id: '12', + label: '12', + cluster: '2', + }, + { + id: '13', + label: '13', + cluster: '4', + }, + { + id: '14', + label: '14', + cluster: '2', + }, + { + id: '15', + label: '15', + cluster: '2', + }, + { + id: '16', + label: '16', + cluster: '2', + }, + { + id: '17', + label: '17', + cluster: '1', + }, + { + id: '18', + label: '18', + cluster: '4', + }, + { + id: '19', + label: '19', + cluster: '4', + }, + { + id: '20', + label: '20', + cluster: '4', + }, + { + id: '21', + label: '21', + cluster: '0', + }, + { + id: '22', + label: '22', + cluster: '2', + }, + { + id: '23', + label: '23', + cluster: '2', + }, + { + id: '24', + label: '24', + cluster: '2', + }, + { + id: '25', + label: '25', + cluster: '3', + }, + { + id: '26', + label: '26', + cluster: '4', + }, + { + id: '27', + label: '27', + cluster: '4', + }, + { + id: '28', + label: '28', + cluster: '1', + }, + { + id: '29', + label: '29', + cluster: '1', + }, + { + id: '30', + label: '30', + cluster: '4', + }, + { + id: '31', + label: '31', + cluster: '4', + }, + { + id: '32', + label: '32', + cluster: '1', + }, + { + id: '33', + label: '33', + cluster: '2', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const colors = [ + '#BDD2FD', + '#BDEFDB', + '#C2C8D5', + '#FBE5A2', + '#F6C3B7', + '#B6E3F5', + '#D3C6EA', + '#FFD8B8', + '#AAD8D8', + '#FFD6E7', +]; +const strokes = [ + '#5B8FF9', + '#5AD8A6', + '#5D7092', + '#F6BD16', + '#E8684A', + '#6DC8EC', + '#9270CA', + '#FF9D4D', + '#269A99', + '#FF99C3', +]; + +const nodes = data.nodes; +const clusterMap = new Map(); +let clusterId = 0; +nodes.forEach(function (node) { + // cluster + if (node.cluster && clusterMap.get(node.cluster) === undefined) { + clusterMap.set(node.cluster, clusterId); + clusterId++; + } + const cid = clusterMap.get(node.cluster); + if (!node.style) node.style = {}; + node.style.fill = colors[cid % colors.length]; + node.style.stroke = strokes[cid % strokes.length]; +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['zoom-canvas', 'drag-canvas', 'drag-node'], + }, + layout: { + type: 'grid', + begin: [20, 20], + width: width - 20, + height: height - 20, + sortBy: 'cluster', + }, + animate: true, + defaultNode: { + size: 20, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/gridLayout/demo/meta.json b/packages/site/examples/net/gridLayout/demo/meta.json new file mode 100644 index 0000000000..bf920c700e --- /dev/null +++ b/packages/site/examples/net/gridLayout/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicGrid.js", + "title": { + "zh": "基本 Grid 网格布局", + "en": "Basic Grid Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Oh6mRLVEBBIAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "clusterGrid.js", + "title": { + "zh": "指定排序方式的网格布局", + "en": "Ordered Grid Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*_ABKTptHoKoAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/gridLayout/index.en.md b/packages/site/examples/net/gridLayout/index.en.md new file mode 100644 index 0000000000..5f8c9dd468 --- /dev/null +++ b/packages/site/examples/net/gridLayout/index.en.md @@ -0,0 +1,13 @@ +--- +title: Grid +order: 5 +--- + +Grid Layout will order the nodes according to the parameters, and then place the nodes on the grids. + +## Usage + +As the demo below, you can deploy it in `layout` while instantiating Graph. it can also be used for [Subgraph Layout](/en/docs/manual/middle/layout/sub-layout). By tuning the parameters, you can adjust the iteration number, compact degree, layout buy cluster, and so on. By tuning the parameters, you can adjust the ordering method, rows, cols, prevent node overlappings, and so on. + +- Example 1 : Basic Grid Layout, the nodes are ordered according to the data. +- Example 2 : Order the nodes according to the property `cluster`. diff --git a/packages/site/examples/net/gridLayout/index.zh.md b/packages/site/examples/net/gridLayout/index.zh.md new file mode 100644 index 0000000000..0fdf4cd6a2 --- /dev/null +++ b/packages/site/examples/net/gridLayout/index.zh.md @@ -0,0 +1,13 @@ +--- +title: Grid 网格布局 +order: 5 +--- + +Grid 网格布局根据参数指定的排序方式对节点进行排序后,将节点排列在网格上。 + +## 使用指南 + +G6 内置的 Grid 网格布局可在实例化 Graph 时使用该布局。除此之外,还可以如[子图布局](/zh/docs/manual/middle/layout/sub-layout)所示单独使用布局。该布局可以通过配置调整节点排序方式、行数、列数、节点不重叠等。 + +- 代码演示 1 :基本的网格布局,节点根据在数据中的顺序排列。 +- 代码演示 2 :节点根据数据中的 cluster 属性排序。 diff --git a/packages/site/examples/net/layoutMechanism/API.en.md b/packages/site/examples/net/layoutMechanism/API.en.md new file mode 100644 index 0000000000..ff913a6bd1 --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/layoutMechanism/API.zh.md b/packages/site/examples/net/layoutMechanism/API.zh.md new file mode 100644 index 0000000000..a3512c1184 --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/layoutMechanism/demo/customBigraph.js b/packages/site/examples/net/layoutMechanism/demo/customBigraph.js new file mode 100644 index 0000000000..f38bcd2e4b --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/demo/customBigraph.js @@ -0,0 +1,249 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: 'A', + cluster: 'part1', + }, + { + id: '1', + label: 'B', + cluster: 'part1', + }, + { + id: '2', + label: 'C', + cluster: 'part1', + }, + { + id: '3', + label: 'D', + cluster: 'part1', + }, + { + id: '4', + label: 'E', + cluster: 'part1', + }, + { + id: '5', + label: 'F', + cluster: 'part1', + }, + { + id: '6', + label: 'a', + cluster: 'part2', + }, + { + id: '7', + label: 'b', + cluster: 'part2', + }, + { + id: '8', + label: 'c', + cluster: 'part2', + }, + { + id: '9', + label: 'd', + cluster: 'part2', + }, + ], + edges: [ + { + source: '0', + target: '6', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '9', + }, + { + source: '1', + target: '6', + }, + { + source: '1', + target: '9', + }, + { + source: '1', + target: '7', + }, + { + source: '2', + target: '8', + }, + { + source: '2', + target: '9', + }, + { + source: '2', + target: '6', + }, + { + source: '3', + target: '8', + }, + { + source: '4', + target: '6', + }, + { + source: '4', + target: '7', + }, + { + source: '5', + target: '9', + }, + ], +}; + +G6.registerLayout('bigraph-layout', { + execute() { + const self = this; + const center = self.center || [0, 0]; + const biSep = self.biSep || 100; + const nodeSep = self.nodeSep || 20; + const nodeSize = self.nodeSize || 20; + const direction = self.direction || 'horizontal'; + let part1Pos = 0; + let part2Pos = 0; + if (direction === 'horizontal') { + part1Pos = center[0] - biSep / 2; + part2Pos = center[0] + biSep / 2; + } + const { nodes, edges } = self; + const part1Nodes = []; + const part2Nodes = []; + const part1NodeMap = new Map(); + const part2NodeMap = new Map(); + // separate the nodes and init the positions + nodes.forEach(function (node, i) { + if (node.cluster === 'part1') { + part1Nodes.push(node); + part1NodeMap.set(node.id, i); + } else { + part2Nodes.push(node); + part2NodeMap.set(node.id, i); + } + }); + + // order the part1 node + part1Nodes.forEach(function (p1n) { + let index = 0; + let adjCount = 0; + edges.forEach(function (edge) { + const sourceId = edge.source; + const targetId = edge.target; + if (sourceId === p1n.id) { + index += part2NodeMap.get(targetId); + adjCount += 1; + } else if (targetId === p1n.id) { + index += part2NodeMap.get(sourceId); + adjCount += 1; + } + }); + index /= adjCount; + p1n.index = index; + }); + part1Nodes.sort(function (a, b) { + return a.index - b.index; + }); + part2Nodes.forEach(function (p2n) { + let index = 0; + let adjCount = 0; + edges.forEach(function (edge) { + const sourceId = edge.source; + const targetId = edge.target; + if (sourceId === p2n.id) { + index += part1NodeMap.get(targetId); + adjCount += 1; + } else if (targetId === p2n.id) { + index += part1NodeMap.get(sourceId); + adjCount += 1; + } + }); + index /= adjCount; + p2n.index = index; + }); + part2Nodes.sort(function (a, b) { + return a.index - b.index; + }); + + // place the nodes + const hLength = part1Nodes.length > part2Nodes.length ? part1Nodes.length : part2Nodes.length; + const height = hLength * (nodeSep + nodeSize); + let begin = center[1] - height / 2; + if (direction === 'vertical') { + begin = center[0] - height / 2; + } + part1Nodes.forEach(function (p1n, i) { + if (direction === 'horizontal') { + p1n.x = part1Pos; + p1n.y = begin + i * (nodeSep + nodeSize); + } else { + p1n.x = begin + i * (nodeSep + nodeSize); + p1n.y = part1Pos; + } + }); + part2Nodes.forEach(function (p2n, i) { + if (direction === 'horizontal') { + p2n.x = part2Pos; + p2n.y = begin + i * (nodeSep + nodeSize); + } else { + p2n.x = begin + i * (nodeSep + nodeSize); + p2n.y = part2Pos; + } + }); + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'bigraph-layout', + biSep: 300, + nodeSep: 20, + nodeSize: 20, + }, + animate: true, + defaultNode: { + size: 20, + style: { + fill: '#C6E5FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + }, + modes: { + default: ['drag-canvas'], + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/layoutMechanism/demo/dataChange.js b/packages/site/examples/net/layoutMechanism/demo/dataChange.js new file mode 100644 index 0000000000..09094bdff0 --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/demo/dataChange.js @@ -0,0 +1,482 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const data2 = { + nodes: [ + { + id: 'b0', + label: '0', + }, + { + id: 'b1', + label: '1', + }, + { + id: 'b2', + label: '2', + }, + { + id: 'b3', + label: '3', + }, + { + id: 'b4', + label: '4', + }, + { + id: 'b5', + label: '5', + }, + ], + edges: [ + { + id: 'be1', + source: 'b0', + target: 'b1', + }, + { + id: 'be2', + source: 'b0', + target: 'b2', + }, + { + id: 'be3', + source: 'b0', + target: 'b3', + }, + { + id: 'be4', + source: 'b0', + target: 'b4', + }, + { + id: 'be5', + source: 'b0', + target: 'b5', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'force', + }, + animate: true, + defaultNode: { + size: 20, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + +setTimeout(() => { + graph.changeData(data2); +}, 2500); diff --git a/packages/site/examples/net/layoutMechanism/demo/layoutTiming.js b/packages/site/examples/net/layoutMechanism/demo/layoutTiming.js new file mode 100644 index 0000000000..dd5f16fc59 --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/demo/layoutTiming.js @@ -0,0 +1,133 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + ], +}; + +const tipDiv = document.createElement('div'); +const container = document.getElementById('container'); +container.appendChild(tipDiv); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'force', + preventOverlap: true, + nodeSize: 20, + }, + modes: { + default: ['drag-node'], + }, +}); + +graph.on('beforelayout', function () { + tipDiv.innerHTML = + 'Doing force-directed layout... the text will be changed after the layout being done.'; +}); +graph.on('afterlayout', function () { + tipDiv.innerHTML = 'Done!'; +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/layoutMechanism/demo/layoutTranslate.js b/packages/site/examples/net/layoutMechanism/demo/layoutTranslate.js new file mode 100644 index 0000000000..abaa1fc538 --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/demo/layoutTranslate.js @@ -0,0 +1,78 @@ +import G6 from '@antv/g6'; + +const tipDiv = document.createElement('div'); +tipDiv.innerHTML = 'Random Layout'; +const container = document.getElementById('container'); +container.appendChild(tipDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'random', + }, + modes: { + default: ['drag-node'], + }, + animate: true, +}); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + setTimeout(() => { + tipDiv.innerHTML = 'Circular Layout'; + graph.updateLayout({ + type: 'circular', + radius: 200, + }); + }, 3000); + setTimeout(() => { + tipDiv.innerHTML = 'Grid Layout'; + graph.updateLayout({ + type: 'grid', + }); + }, 6000); + setTimeout(() => { + tipDiv.innerHTML = 'Force Layout'; + graph.updateLayout({ + type: 'force', + preventOverlap: true, + nodeSize: 20, + }); + }, 9000); + setTimeout(() => { + tipDiv.innerHTML = 'Radial Layout'; + graph.updateLayout({ + type: 'radial', + preventOverlap: true, + nodeSize: 15, + }); + }, 12000); + setTimeout(() => { + tipDiv.innerHTML = 'Concentric Layout'; + graph.updateLayout({ + type: 'concentric', + minNodeSpacing: 30, + }); + }, 15000); + setTimeout(() => { + tipDiv.innerHTML = 'MDS Layout'; + graph.updateLayout({ + type: 'mds', + linkDistance: 100, + }); + }, 18000); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; diff --git a/packages/site/examples/net/layoutMechanism/demo/meta.json b/packages/site/examples/net/layoutMechanism/demo/meta.json new file mode 100644 index 0000000000..fdb1e07f25 --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/demo/meta.json @@ -0,0 +1,56 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "layoutTranslate.js", + "title": { + "zh": "布局切换", + "en": "Layout Translate" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*eZRNQ4tpkecAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "subgraphLayout.js", + "title": { + "zh": "子图布局", + "en": "Subgraph Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*dsOISrH6tREAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "sublayoutPipes.js", + "title": { + "zh": "流水线子图布局", + "en": "Sublayout Pipeline" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*SQijSogATDAAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "dataChange.js", + "title": { + "zh": "数据动态切换", + "en": "Change Data" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*VIXvQagCETkAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "customBigraph.js", + "title": { + "zh": "自定义二分图布局", + "en": "Custom Bigraph Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Vod0Q6u5dSoAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "layoutTiming.js", + "title": { + "zh": "布局时机监听", + "en": "Layout Listener" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*tdRuSbgJAZ8AAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/layoutMechanism/demo/subgraphLayout.js b/packages/site/examples/net/layoutMechanism/demo/subgraphLayout.js new file mode 100644 index 0000000000..991124247e --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/demo/subgraphLayout.js @@ -0,0 +1,475 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const nodes = data.nodes; + +nodes.forEach(function (node, i) { + if (i <= 16 && i !== 12) { + if (!node.style) { + node.style = { + fill: '#F6C3B7', + stroke: '#E8684A', + }; + } else { + node.style.fill = 'lightsteelblue'; + } + } +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + fitViewPadding: 40, + modes: { + default: ['drag-node'], + }, + defaultNode: { + size: 20, + style: { + fill: '#C6E5FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; + +setTimeout(function () { + const nodes = data.nodes; + const edges = data.edges; + const newNodes = []; + const newEdges = []; + const newNodeMap = new Map(); + // fix the nodes[0] + nodes[0].fx = nodes[0].x; + nodes[0].fy = nodes[0].y; + // add the nodes which should be re-layout + nodes.forEach(function (node, i) { + if (i <= 16 && i !== 12) { + newNodes.push(node); + newNodeMap.set(node.id, i); + } + }); + // add related edges + edges.forEach(function (edge) { + const sourceId = edge.source; + const targetId = edge.target; + if (newNodeMap.get(sourceId) !== undefined && newNodeMap.get(targetId) !== undefined) { + newEdges.push(edge); + } + }); + + const subForceLayout = new G6.Layout.force({ + center: [nodes[0].x, nodes[0].y], + linkDistance: 70, + preventOverlap: true, + nodeSize: 20, + tick: function tick() { + // the tick function to show the animation of layout process + graph.refreshPositions(); + }, + }); + subForceLayout.init({ + nodes: newNodes, + edges: newEdges, + }); + subForceLayout.execute(); +}, 1000); diff --git a/packages/site/examples/net/layoutMechanism/demo/sublayoutPipes.js b/packages/site/examples/net/layoutMechanism/demo/sublayoutPipes.js new file mode 100644 index 0000000000..e7491e0fc8 --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/demo/sublayoutPipes.js @@ -0,0 +1,161 @@ +import G6 from '@antv/g6'; + +const data = { nodes: [], edges: [] }; +for (let i = 0; i < 32; i++) { + data.nodes.push({ + id: `${i}`, + label: i < 17 ? `employee-${i}` : `company-${i - 17}`, + dataType: i < 17 ? 'employee' : 'company', + style: i < 17 ? + { stroke: '#5D7092', fill: '#5D7092', fillOpacity: 0.5 } : + { stroke: '#5B8FF9', fill: '#5B8FF9', fillOpacity: 0.5 } + }); +} +data.edges = [ + { source: '0', target: '1' }, + { source: '0', target: '2' }, + { source: '0', target: '3' }, + { source: '0', target: '4' }, + { source: '0', target: '5' }, + { source: '0', target: '6' }, + { source: '1', target: '2' }, + { source: '1', target: '3' }, + { source: '1', target: '4' }, + { source: '1', target: '5' }, + { source: '1', target: '6' }, + { source: '2', target: '3' }, + { source: '2', target: '4' }, + { source: '2', target: '5' }, + { source: '2', target: '6' }, + + { source: '7', target: '8' }, + { source: '8', target: '9' }, + { source: '9', target: '10' }, + + { source: '11', target: '12' }, + { source: '12', target: '13' }, + { source: '13', target: '14' }, + { source: '14', target: '15' }, + { source: '15', target: '16' }, + { source: '11', target: '14' }, + + { source: '31', target: '11' }, + { source: '24', target: '4' }, + { source: '23', target: '7' }, +]; + +const legendData = { + nodes: [ + { id: 'employee', label: 'employee', style: { stroke: '#5D7092', fill: '#5D7092' } }, + { id: 'company', label: 'company', style: { stroke: '#5B8FF9', fill: '#5B8FF9' } }, + ], + edges: [] +} + +const legend = new G6.Legend({ + data: legendData, + align: 'center', + layout: 'horizontal', // vertical + position: 'bottom-left', + vertiSep: 12, + horiSep: 24, + offsetY: -24, + padding: [4, 16, 8, 16], + containerStyle: { + fill: '#ccc', + lineWidth: 1 + }, + title: 'Legend', + titleConfig: { + position: 'left', + offsetX: 0, + offsetY: 12, + }, + filter: { + enable: true, + multiple: true, + trigger: 'click', + graphActiveState: 'activeByLegend', + graphInactiveState: 'inactiveByLegend', + filterFunctions: { + 'a': (d) => { + if (d.cluster === 'a') return true; + return false + }, + 'b': (d) => { + if (d.cluster === 'b') return true; + return false + }, + 'c': (d) => { + if (d.cluster === 'c') return true; + return false + }, + 'd': (d) => { + if (d.cluster === 'd') return true; + return false + }, + } + } +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + plugins: [legend], + animate: true, + nodeStateStyles: { + activeByLegend: { + lineWidth: 5, + strokeOpacity: 0.5, + stroke: '#f00' + }, + inactiveByLegend: { + opacity: 0.5 + } + }, + layout: { + pipes: [ + { + type: 'circular', + nodesFilter: (node) => (+node.id) <= 6, + center: [width / 5 * 4 - 30, height / 2], + radius: width / 10 + }, + { + type: 'circular', + nodesFilter: (node) => ((+node.id) >= 7 && (+node.id) <= 10), + center: [width / 20 + 30, height / 3 * 2], + radius: width / 20 + }, + { + type: 'circular', + nodesFilter: (node) => ((+node.id) >= 11 && (+node.id) <= 16), + center: [width / 20 + 30, height / 3], + radius: width / 20 + }, + { + type: 'grid', + nodesFilter: (node) => (+node.id) > 16, + begin: [width / 10 + 50, 20], + width: width / 5 * 3 - 100, + height: height - 40, + } + ], + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/layoutMechanism/index.en.md b/packages/site/examples/net/layoutMechanism/index.en.md new file mode 100644 index 0000000000..2445b73f59 --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/index.en.md @@ -0,0 +1,14 @@ +--- +title: Layout Mechanism +order: 19 +--- + +G6 provides the mechanism for layout paramters translation, layout methods translation, data translation, subgraph layout, layout timing for listener, custom layout, and so on. + +## Usage + +- Example 1 : Subgrpah(red nodes) layout by force-directed layout. +- Example 2 : Apply different layouts for subgraphs in the same time. +- Example 3 : Tranlate the layout parameters in dynamic. +- Example 4 : Custom layout for a bigraph. +- Example 5 : Layout timing events —— `beforelayout` and `afterlayout`. diff --git a/packages/site/examples/net/layoutMechanism/index.zh.md b/packages/site/examples/net/layoutMechanism/index.zh.md new file mode 100644 index 0000000000..293c7673b3 --- /dev/null +++ b/packages/site/examples/net/layoutMechanism/index.zh.md @@ -0,0 +1,14 @@ +--- +title: 布局机制 +order: 19 +--- + +G6 为用户提供了布局参数动态切换、布局方法动态切换、数据动态切换、子图布局、布局时机监听、自定义布局等布局相关等机制。 + +## 使用指南 + +- 代码演示 1 :控制子图(红色节点)使用经典力导向算法进行子图布局。 +- 代码演示 2 :同时设置不同的子图布局。 +- 代码演示 3 :布局数据可以动态切换。 +- 代码演示 4 :G6 提供了自定义布局机制,可以随心所欲定制自己的布局。 +- 代码演示 5 :G6 开放了布局的监听时机 —— `beforelayout` 与 `afterlayout`。 diff --git a/packages/site/examples/net/mdsLayout/API.en.md b/packages/site/examples/net/mdsLayout/API.en.md new file mode 100644 index 0000000000..6286006102 --- /dev/null +++ b/packages/site/examples/net/mdsLayout/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/mdsLayout/API.zh.md b/packages/site/examples/net/mdsLayout/API.zh.md new file mode 100644 index 0000000000..8e6f406246 --- /dev/null +++ b/packages/site/examples/net/mdsLayout/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/mdsLayout/demo/basicMDS.js b/packages/site/examples/net/mdsLayout/demo/basicMDS.js new file mode 100644 index 0000000000..7d7e5b58c2 --- /dev/null +++ b/packages/site/examples/net/mdsLayout/demo/basicMDS.js @@ -0,0 +1,413 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + fitView: true, + layout: { + type: 'mds', + linkDistance: 100, + }, + modes: { + default: ['drag-node'], + }, + defaultNode: { + size: 20, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/mdsLayout/demo/meta.json b/packages/site/examples/net/mdsLayout/demo/meta.json new file mode 100644 index 0000000000..c70714b0d3 --- /dev/null +++ b/packages/site/examples/net/mdsLayout/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicMDS.js", + "title": { + "zh": "基本 MDS 布局", + "en": "Basic MDS Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*aUS7TJR2NHcAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/mdsLayout/index.en.md b/packages/site/examples/net/mdsLayout/index.en.md new file mode 100644 index 0000000000..57d3f79b99 --- /dev/null +++ b/packages/site/examples/net/mdsLayout/index.en.md @@ -0,0 +1,10 @@ +--- +title: MDS Layout +order: 7 +--- + +Classical MDS(Multidemensional Scaling)is an algorithm for high dimensional data projection. Relational data layout problem can be regarded as projecting high dimensional data onto a 2D visual space. + +## Usage + +As the demo below, you can deploy it in `layout` while instantiating Graph. it can also be used for [Subgraph Layout](/en/docs/manual/middle/layout/sub-layout). By tuning the parameters, you can adjust the edge length. diff --git a/packages/site/examples/net/mdsLayout/index.zh.md b/packages/site/examples/net/mdsLayout/index.zh.md new file mode 100644 index 0000000000..f170903cac --- /dev/null +++ b/packages/site/examples/net/mdsLayout/index.zh.md @@ -0,0 +1,10 @@ +--- +title: MDS 布局 +order: 7 +--- + +MDS(Multidemensional Scaling)本是用于高维数据降维的算法。关系图数据可视化中的布局可以看作是高维数据降维到二维屏幕空间中。 + +## 使用指南 + +G6 内置的 MDS 布局可在实例化 Graph 时使用该布局。除此之外,还可以如[子图布局](/zh/docs/manual/middle/layout/sub-layout)所示单独使用布局。该算法可配置边的长度。 diff --git a/packages/site/examples/net/radialLayout/API.en.md b/packages/site/examples/net/radialLayout/API.en.md new file mode 100644 index 0000000000..5c7b3e8778 --- /dev/null +++ b/packages/site/examples/net/radialLayout/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/radialLayout/API.zh.md b/packages/site/examples/net/radialLayout/API.zh.md new file mode 100644 index 0000000000..53137c02be --- /dev/null +++ b/packages/site/examples/net/radialLayout/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/net/radialLayout/demo/basicRadial.js b/packages/site/examples/net/radialLayout/demo/basicRadial.js new file mode 100644 index 0000000000..e4d3e9a967 --- /dev/null +++ b/packages/site/examples/net/radialLayout/demo/basicRadial.js @@ -0,0 +1,412 @@ +import G6 from '@antv/g6'; +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'radial', + unitRadius: 50, + }, + animate: true, + defaultNode: { + size: 20, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/radialLayout/demo/interactRadial.js b/packages/site/examples/net/radialLayout/demo/interactRadial.js new file mode 100644 index 0000000000..e2343aa86a --- /dev/null +++ b/packages/site/examples/net/radialLayout/demo/interactRadial.js @@ -0,0 +1,791 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const data2_m = { + nodes: [ + { + id: '2', + label: '2', + }, + { + id: '1001', + label: '1001', + }, + { + id: '1002', + label: '1002', + }, + { + id: '1003', + label: '1003', + }, + { + id: '1004', + label: '1004', + }, + { + id: '1005', + label: '1005', + }, + { + id: '1006', + label: '1006', + }, + { + id: '1007', + label: '1007', + }, + { + id: '1008', + label: '1008', + }, + { + id: '1009', + label: '1009', + }, + { + id: '1010', + label: '1010', + }, + { + id: '1011', + label: '1011', + }, + { + id: '1012', + label: '1012', + }, + { + id: '1013', + label: '1013', + }, + { + id: '1014', + label: '1014', + }, + { + id: '1015', + label: '1015', + }, + { + id: '1016', + label: '1016', + }, + { + id: '1017', + label: '1017', + }, + { + id: '1018', + label: '1018', + }, + { + id: '1019', + label: '1019', + }, + { + id: '1020', + label: '1020', + }, + { + id: '5', + label: '5', + }, + { + id: '41', + label: '41', + }, + ], + edges: [ + { + source: '2', + target: '1001', + }, + { + source: '2', + target: '1002', + }, + { + source: '2', + target: '1003', + }, + { + source: '2', + target: '1004', + }, + { + source: '2', + target: '1005', + }, + { + source: '1001', + target: '1006', + }, + { + source: '1001', + target: '1007', + }, + { + source: '1001', + target: '1008', + }, + { + source: '1001', + target: '1009', + }, + { + source: '1001', + target: '1010', + }, + { + source: '1002', + target: '1006', + }, + { + source: '1002', + target: '1007', + }, + { + source: '1002', + target: '1008', + }, + { + source: '1002', + target: '1009', + }, + { + source: '1002', + target: '1010', + }, + { + source: '1003', + target: '1006', + }, + { + source: '1003', + target: '1007', + }, + { + source: '1003', + target: '1008', + }, + { + source: '1003', + target: '1009', + }, + { + source: '1003', + target: '1010', + }, + { + source: '1010', + target: '1011', + }, + { + source: '1010', + target: '1012', + }, + { + source: '1010', + target: '1013', + }, + { + source: '1010', + target: '1014', + }, + { + source: '1010', + target: '1015', + }, + { + source: '1010', + target: '1016', + }, + { + source: '1010', + target: '1017', + }, + { + source: '1008', + target: '1014', + }, + { + source: '1008', + target: '1015', + }, + { + source: '1008', + target: '1016', + }, + { + source: '1008', + target: '1017', + }, + { + source: '1017', + target: '1018', + }, + { + source: '1017', + target: '1019', + }, + { + source: '1016', + target: '1020', + }, + { + source: '1016', + target: '1020', + }, + { + source: '5', + target: '1020', + }, + { + source: '41', + target: '1020', + }, + { + source: '5', + target: '1009', + }, + { + source: '41', + target: '1009', + }, + ], +}; + +const container = document.getElementById('container'); +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Try to click node 2!'; +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const mainUnitRadius = 80; +const focusNode = data.nodes[22]; +focusNode.style = { + stroke: '#00419F', + fill: '#729FFC', + lineWidth: 2, +}; +data.nodes[2].style = { + stroke: '#00419F', + fill: '#729FFC', + lineWidth: 2, +}; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + layout: { + type: 'radial', + maxIteration: 200, + focusNode, + unitRadius: mainUnitRadius, + linkDistance: 100, + preventOverlap: true, + nodeSize: 20, + }, + animate: true, + modes: { + default: ['drag-node', 'click-select', 'click-add-node', 'drag-canvas'], + }, + defaultNode: { + size: 20, + }, +}); + +graph.data({ + nodes: data.nodes, + edges: data.edges.map((edge, i) => { + edge.id = `edge${i}`; + return Object.assign({}, edge); + }), +}); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; + +graph.on('node:click', (ev) => { + const itemModel = ev.item.getModel(); + const nodes = graph.getNodes(); + const edges = graph.getEdges(); + let newData; + if (itemModel.id === '2') newData = data2_m; + else return; + const newNodeModels = newData.nodes; + const newEdgeModels = []; + // deduplication the items in newEdgeModels + newData.edges.forEach(function (e) { + let exist = false; + newEdgeModels.forEach(function (ne) { + if (ne.source === e.source && ne.target === e.target) exist = true; + }); + if (!exist) { + newEdgeModels.push(e); + } + }); + + // for graph.changeData() + const allNodeModels = [], + allEdgeModels = []; + + // add new nodes to graph + const nodeMap = new Map(); + nodes.forEach((n) => { + const nModel = n.getModel(); + nodeMap.set(nModel.id, n); + }); + newNodeModels.forEach((nodeModel) => { + if (nodeMap.get(nodeModel.id) === undefined) { + // set the initial positions of the new nodes to the focus(clicked) node + nodeModel.x = itemModel.x; + nodeModel.y = itemModel.y; + graph.addItem('node', nodeModel); + } + }); + + // add new edges to graph + const edgeMap = new Map(); + edges.forEach(function (e, i) { + const eModel = e.getModel(); + edgeMap.set(eModel.source + ',' + eModel.target, i); + }); + const oldEdgeNum = edges.length; + newEdgeModels.forEach(function (em, i) { + const exist = edgeMap.get(em.source + ',' + em.target); + if (exist === undefined) { + graph.addItem('edge', em); + edgeMap.set(em.source + ',' + em.target, oldEdgeNum + i); + } + }); + + edges.forEach((e) => { + allEdgeModels.push(e.getModel()); + }); + nodes.forEach((n) => { + allNodeModels.push(n.getModel()); + }); + // the max degree about foces(clicked) node in the newly added data + const maxDegree = 4; + // the max degree about foces(clicked) node in the original data + const oMaxDegree = 3; + const unitRadius = 40; + // re-place the clicked node far away the exisiting items + // along the radius from center node to it + const vx = itemModel.x - focusNode.x; + const vy = itemModel.y - focusNode.y; + const vlength = Math.sqrt(vx * vx + vy * vy); + const ideallength = unitRadius * maxDegree + mainUnitRadius * oMaxDegree; + itemModel.x = (ideallength * vx) / vlength + focusNode.x; + itemModel.y = (ideallength * vy) / vlength + focusNode.y; + + const subRadialLayout = new G6.Layout.radial({ + center: [itemModel.x, itemModel.y], + maxIteration: 200, + focusNode: '2', + unitRadius, + linkDistance: 180, + preventOverlap: true, + }); + subRadialLayout.init({ + nodes: newNodeModels, + edges: newEdgeModels, + }); + subRadialLayout.execute(); + graph.positionsAnimate(); + graph.data({ + nodes: allNodeModels, + edges: allEdgeModels, + }); +}); diff --git a/packages/site/examples/net/radialLayout/demo/meta.json b/packages/site/examples/net/radialLayout/demo/meta.json new file mode 100644 index 0000000000..883ae544ff --- /dev/null +++ b/packages/site/examples/net/radialLayout/demo/meta.json @@ -0,0 +1,56 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicRadial.js", + "title": { + "zh": "基本 Radial 辐射布局", + "en": "Basic Radial Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Ec_WQ5hqVsgAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "preventOverlapRadial.js", + "title": { + "zh": "防止节点重叠的严格辐射布局", + "en": "Prevent Node Overlappings and Restrict Radial" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GAFjRJeAoAsAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "preventOverlapUnstrictRadial.js", + "title": { + "zh": "防止节点重叠的非严格辐射布局", + "en": "Prevent Node Overlappings and Unrestrict Radial" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*X8rzTZyWwUcAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "interactRadial.js", + "title": { + "zh": "交互扩展节点的辐射布局", + "en": "Interactively Expand a Node" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*F4OQQKc8l2IAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "radialConfigurationTranslate.js", + "title": { + "zh": "Radial 布局参数动态变化", + "en": "Update Configurations for Radial" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ikctQb9KECAAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "sortRadial.js", + "title": { + "zh": "同层节点按照指定字段聚类", + "en": "Order the Level Nodes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*gzfTQKBB62IAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/net/radialLayout/demo/preventOverlapRadial.js b/packages/site/examples/net/radialLayout/demo/preventOverlapRadial.js new file mode 100644 index 0000000000..db28976289 --- /dev/null +++ b/packages/site/examples/net/radialLayout/demo/preventOverlapRadial.js @@ -0,0 +1,425 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'radial', + unitRadius: 50, + preventOverlap: true, + maxPreventOverlapIteration: 100, + }, + animate: true, + defaultEdge: { + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); + +const nodes = data.nodes; +nodes.forEach((node) => { + node.size = Math.random() * 20 + 10; +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/radialLayout/demo/preventOverlapUnstrictRadial.js b/packages/site/examples/net/radialLayout/demo/preventOverlapUnstrictRadial.js new file mode 100644 index 0000000000..cc614087d5 --- /dev/null +++ b/packages/site/examples/net/radialLayout/demo/preventOverlapUnstrictRadial.js @@ -0,0 +1,424 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'radial', + unitRadius: 70, + preventOverlap: true, + strictRadial: false, + }, + animate: true, + defaultNode: { + size: 20, + }, + defaultEdge: { + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/radialLayout/demo/radialConfigurationTranslate.js b/packages/site/examples/net/radialLayout/demo/radialConfigurationTranslate.js new file mode 100644 index 0000000000..6b76a85ede --- /dev/null +++ b/packages/site/examples/net/radialLayout/demo/radialConfigurationTranslate.js @@ -0,0 +1,478 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + 'Radial layout, focusNode = = 0, unitRadius = 50, preventOverlap: false'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 20; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'radial', + unitRadius: 50, + }, + animate: true, + defaultNode: { + size: 20, + }, + defaultEdge: { + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 20); + }; + +layoutConfigTranslation(); + +setInterval(function () { + layoutConfigTranslation(); +}, 7000); + +function layoutConfigTranslation() { + setTimeout(function () { + descriptionDiv.innerHTML = + 'Radial layout, focusNode = = 0, unitRadius = 50, preventOverlap: true'; + graph.updateLayout({ + preventOverlap: true, + nodeSize: 20, + }); + }, 1000); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Radial layout, focusNode = = 0, unitRadius = 80, preventOverlap: true'; + graph.updateLayout({ + unitRadius: 80, + }); + }, 2500); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Radial layout, focusNode = = 10, unitRadius = 80, preventOverlap: true'; + graph.updateLayout({ + focusNode: '10', + }); + }, 4000); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Radial layout, focusNode = = 20, unitRadius = 80, preventOverlap: true'; + graph.updateLayout({ + focusNode: '20', + }); + }, 5500); + + setTimeout(function () { + descriptionDiv.innerHTML = + 'Radial layout, focusNode = = 0, unitRadius = 50, preventOverlap: false'; + graph.updateLayout({ + focusNode: '0', + preventOverlap: false, + unitRadius: 50, + }); + }, 5500); +} diff --git a/packages/site/examples/net/radialLayout/demo/sortRadial.js b/packages/site/examples/net/radialLayout/demo/sortRadial.js new file mode 100644 index 0000000000..440e43eb31 --- /dev/null +++ b/packages/site/examples/net/radialLayout/demo/sortRadial.js @@ -0,0 +1,469 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + sortAttr: 0, + sortAttr2: 'a', + }, + { + id: '1', + sortAttr: 0, + sortAttr2: 'a', + }, + { + id: '2', + sortAttr: 0, + sortAttr2: 'a', + }, + { + id: '3', + sortAttr: 0, + sortAttr2: 'a', + }, + { + id: '4', + sortAttr: 2, + sortAttr2: 'c', + }, + { + id: '5', + sortAttr: 0, + sortAttr2: 'a', + }, + { + id: '6', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '7', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '8', + sortAttr: 2, + sortAttr2: 'c', + }, + { + id: '9', + sortAttr: 3, + sortAttr2: 'd', + }, + { + id: '10', + sortAttr: 3, + sortAttr2: 'd', + }, + { + id: '11', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '12', + sortAttr: 2, + sortAttr2: 'c', + }, + { + id: '13', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '14', + sortAttr: 3, + sortAttr2: 'd', + }, + { + id: '15', + sortAttr: 3, + sortAttr2: 'd', + }, + { + id: '16', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '17', + sortAttr: 2, + sortAttr2: 'c', + }, + { + id: '18', + sortAttr: 2, + sortAttr2: 'c', + }, + { + id: '19', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '20', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '21', + sortAttr: 3, + sortAttr2: 'd', + }, + { + id: '22', + sortAttr: 3, + sortAttr2: 'd', + }, + { + id: '23', + sortAttr: 3, + sortAttr2: 'd', + }, + { + id: '24', + sortAttr: 0, + sortAttr2: 'a', + }, + { + id: '25', + sortAttr: 0, + sortAttr2: 'a', + }, + { + id: '26', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '27', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '28', + sortAttr: 3, + sortAttr2: 'd', + }, + { + id: '29', + sortAttr: 2, + sortAttr2: 'c', + }, + { + id: '30', + sortAttr: 2, + sortAttr2: 'c', + }, + { + id: '31', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '32', + sortAttr: 1, + sortAttr2: 'b', + }, + { + id: '33', + sortAttr: 0, + sortAttr2: 'a', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + layout: { + type: 'radial', + unitRadius: 70, + maxIteration: 1000, + linkDistance: 10, + preventOverlap: true, + nodeSize: 30, + sortBy: 'sortAttr2', + sortStrength: 50, + }, + animate: true, + defaultEdge: { + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, +}); + +const colors = ['steelblue', 'green', 'pink', 'grey']; +const colorsObj = { a: 'steelblue', b: 'green', c: 'pink', d: 'grey' }; +data.nodes.forEach((node) => { + node.size = 20; + node.style = { + lineWidth: 4, + fill: '#fff', + stroke: colors[node.sortAttr2] || colorsObj[node.sortAttr2], + }; +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/net/radialLayout/index.en.md b/packages/site/examples/net/radialLayout/index.en.md new file mode 100644 index 0000000000..d3cc469f06 --- /dev/null +++ b/packages/site/examples/net/radialLayout/index.en.md @@ -0,0 +1,17 @@ +--- +title: Radial Layout +order: 3 +--- + +Radial Layout will place the nodes to the concentric circles around the `focusNode` according to the shortest path length to `focusNode`. + +## Usage + +As the demo below, you can deploy it in `layout` while instantiating Graph. it can also be used for [Subgraph Layout](/en/docs/manual/middle/layout/sub-layout). By tuning the parameters, you can adjust the iteration number, compact degree, layout buy cluster, and so on. By tuning the parameters, you can adjust the radial radius, preven node overlappings, relaxed radial, and so on. + +- Example 1 : Basic Radial Layout. +- Example 2 : Prevent node overlappings according each node's size. +- Example 3 : Relaxed radial layout allows offsets between nodes on the same level to prevent node overlappings. +- Example 4 : By using the subgraph layout mechanism, we extend nodes by interaction. Try to click node 2. +- Example 5 : Translate the parameters of Radial Layout. +- Example 6 : Cluster the nodes in the same level according to some attribute. diff --git a/packages/site/examples/net/radialLayout/index.zh.md b/packages/site/examples/net/radialLayout/index.zh.md new file mode 100644 index 0000000000..fcbfb1d52c --- /dev/null +++ b/packages/site/examples/net/radialLayout/index.zh.md @@ -0,0 +1,17 @@ +--- +title: Radial 辐射布局 +order: 3 +--- + +Radial 辐射布局根据参数中指定的 focusNode 为中心点,根据其他节点与中心点的拓扑距离(最短路径长度)将其余节点放置在以中心点为圆心的同心圆上。 + +## 使用指南 + +G6 内置的 Radial 布局可在实例化 Graph 时使用该布局。除此之外,还可以如[子图布局](/zh/docs/manual/middle/layout/sub-layout)所示单独使用布局。该布局可以通过配置调整辐射半径、节点不重叠、不严格的辐射布局等。 + +- 代码演示 1 :基本的 Radial 布局。 +- 代码演示 2 :根据不同的节点大小计算节点不重叠。 +- 代码演示 3 :不严格的辐射布局允许同层节点有前后交错以避免节点重叠。 +- 代码演示 4 :利用子图布局机制可以实现点击节点进行扩展的功能,请尝试点击 2 号节点。 +- 代码演示 5 :Radial 布局参数动态变化。 +- 代码演示 6 :同层节点按照指定字段聚类。 diff --git a/packages/site/examples/performance/perf/demo/eva.js b/packages/site/examples/performance/perf/demo/eva.js new file mode 100644 index 0000000000..61aaa4d527 --- /dev/null +++ b/packages/site/examples/performance/perf/demo/eva.js @@ -0,0 +1,120 @@ +import G6 from '@antv/g6'; + +const mapNodeSize = (nodes, propertyName, visualRange) => { + let minp = 9999999999; + let maxp = -9999999999; + nodes.forEach((node) => { + node[propertyName] = Math.pow(node[propertyName], 1 / 3); + minp = node[propertyName] < minp ? node[propertyName] : minp; + maxp = node[propertyName] > maxp ? node[propertyName] : maxp; + }); + const rangepLength = maxp - minp; + const rangevLength = visualRange[1] - visualRange[0]; + nodes.forEach((node) => { + node.size = ((node[propertyName] - minp) / rangepLength) * rangevLength + visualRange[0]; + }); +}; + +const container = document.getElementById('container'); +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = `正在渲染大规模数据,请稍等……`; +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultNode: { + size: 2, + style: { + fill: '#C6E5FF', + stroke: '#5B8FF9', + lineWidth: 0.3, + }, + labelCfg: { + style: { + fontSize: 3, + }, + position: 'right', + offset: 1, + }, + }, + defaultEdge: { + size: 0.1, + color: '#333', + type: 'line', + }, + nodeStateStyles: { + selected: { + fill: 'steelblue', + stroke: '#000', + lineWidth: 1, + }, + hover: { + fill: 'red', + stroke: '#000', + lineWidth: 1, + }, + }, + modes: { + default: [ + { + type: 'zoom-canvas', + enableOptimize: true, + optimizeZoom: 0.9, + }, + { + type: 'drag-canvas', + enableOptimize: true, + }, + 'drag-node', + 'brush-select', + ], // 'drag-canvas', + }, +}); + +fetch('https://gw.alipayobjects.com/os/basement_prod/0b9730ff-0850-46ff-84d0-1d4afecd43e6.json') + .then((res) => res.json()) + .then((data) => { + data.nodes.forEach((node) => { + node.label = node.olabel; + node.labelCfg.style = { + fontSize: 1.3, + }; + node.degree = 0; + data.edges.forEach((edge) => { + if (edge.source === node.id || edge.target === node.id) { + node.degree++; + } + }); + }); + console.log('原始数据', data.nodes.length, data.edges.length); + mapNodeSize(data.nodes, 'degree', [1, 15]); + // console.log(data.nodes); + graph.data(data); + graph.render(); + graph.on('node:mouseenter', (e) => { + const { item } = e; + graph.setItemState(item, 'hover', true); + }); + graph.on('node:mouseleave', (e) => { + const { item } = e; + graph.setItemState(item, 'hover', false); + }); + + const graphData = graph.save(); + const nodeLen = graphData.nodes.length; + const edgeLen = graphData.edges.length; + descriptionDiv.innerHTML = `节点数量:${nodeLen}, 边数量:${edgeLen}, 图元数量:${ + nodeLen * 2 + edgeLen + }`; + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/performance/perf/demo/meta.json b/packages/site/examples/performance/perf/demo/meta.json new file mode 100644 index 0000000000..34936dea85 --- /dev/null +++ b/packages/site/examples/performance/perf/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "netscience.js", + "title": { + "zh": "5000+ 图元", + "en": "5000+ Graphic Shapes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q6eRT7RNwBYAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "eva.js", + "title": { + "zh": "~20000 图元", + "en": "~20000 Graphic Shapes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*mysRTLxipwMAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "moreData.js", + "title": { + "zh": "50000+ 图元", + "en": "50000+ Graphic Shapes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*6U-cSrdpBjYAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/performance/perf/demo/moreData.js b/packages/site/examples/performance/perf/demo/moreData.js new file mode 100644 index 0000000000..41ed016317 --- /dev/null +++ b/packages/site/examples/performance/perf/demo/moreData.js @@ -0,0 +1,76 @@ +import G6 from '@antv/g6'; + +const container = document.getElementById('container'); +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = `正在渲染大规模数据,请稍等……`; +container.appendChild(descriptionDiv); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultNode: { + size: 2, + style: { + fill: '#C6E5FF', + stroke: '#5B8FF9', + lineWidth: 0.3, + }, + labelCfg: { + style: { + fontSize: 3, + }, + position: 'right', + offset: 1, + }, + }, + defaultEdge: { + size: 0.1, + color: '#333', + opacity: 0.2, + }, + nodeStateStyles: { + selected: { + fill: 'steelblue', + stroke: '#000', + lineWidth: 1, + }, + }, + modes: { + default: [ + { + type: 'zoom-canvas', + enableOptimize: true, + optimizeZoom: 0.9, + }, + { + type: 'drag-canvas', + enableOptimize: true, + }, + 'drag-node', + 'brush-select', + ], + }, +}); + +fetch('https://gw.alipayobjects.com/os/bmw-prod/f1565312-d537-4231-adf5-81cb1cd3a0e8.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + + const graphData = graph.save(); + const nodeLen = graphData.nodes.length; + const edgeLen = graphData.edges.length; + descriptionDiv.innerHTML = `节点数量:${nodeLen}, 边数量:${edgeLen}, 图元数量:${ + nodeLen + edgeLen + }`; + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/performance/perf/demo/netscience.js b/packages/site/examples/performance/perf/demo/netscience.js new file mode 100644 index 0000000000..366d7711c2 --- /dev/null +++ b/packages/site/examples/performance/perf/demo/netscience.js @@ -0,0 +1,101 @@ +import G6 from '@antv/g6'; + +const mapNodeSize = (nodes, propertyName, visualRange) => { + let minp = 9999999999; + let maxp = -9999999999; + nodes.forEach((node) => { + node[propertyName] = Math.pow(node[propertyName], 1 / 3); + minp = node[propertyName] < minp ? node[propertyName] : minp; + maxp = node[propertyName] > maxp ? node[propertyName] : maxp; + }); + const rangepLength = maxp - minp; + const rangevLength = visualRange[1] - visualRange[0]; + nodes.forEach((node) => { + node.size = ((node[propertyName] - minp) / rangepLength) * rangevLength + visualRange[0]; + }); +}; + +const container = document.getElementById('container'); +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = `正在渲染大规模数据,请稍等……`; +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultNode: { + size: 2, + style: { + fill: '#C6E5FF', + stroke: '#5B8FF9', + lineWidth: 0.3, + }, + labelCfg: { + style: { + fontSize: 3, + }, + position: 'right', + offset: 1, + }, + }, + defaultEdge: { + size: 0.1, + color: '#333', + }, + nodeStateStyles: { + selected: { + fill: 'steelblue', + stroke: '#000', + lineWidth: 1, + }, + }, + modes: { + default: [ + { + type: 'zoom-canvas', + enableOptimize: true, + optimizeZoom: 0.9, + }, + { + type: 'drag-canvas', + enableOptimize: true, + }, + 'drag-node', + 'brush-select', + ], + }, +}); + +fetch('https://gw.alipayobjects.com/os/basement_prod/da5a1b47-37d6-44d7-8d10-f3e046dabf82.json') + .then((res) => res.json()) + .then((data) => { + data.nodes.forEach((node) => { + node.label = node.olabel; + node.degree = 0; + data.edges.forEach((edge) => { + if (edge.source === node.id || edge.target === node.id) { + node.degree++; + } + }); + }); + mapNodeSize(data.nodes, 'degree', [1, 10]); + graph.data(data); + graph.render(); + + const graphData = graph.save(); + const nodeLen = graphData.nodes.length; + const edgeLen = graphData.edges.length; + descriptionDiv.innerHTML = `节点数量:${nodeLen}, 边数量:${edgeLen}, 图元数量:${ + nodeLen * 2 + edgeLen + }`; + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/performance/perf/index.en.md b/packages/site/examples/performance/perf/index.en.md new file mode 100644 index 0000000000..0a4b223d69 --- /dev/null +++ b/packages/site/examples/performance/perf/index.en.md @@ -0,0 +1,6 @@ +--- +title: Performance Case +order: 0 +--- + +G6 Performance test, we provide three data volume cases: 5k+ graphic shapes、amlost 2w graphic shapes and 5W+ graphic shapes. diff --git a/packages/site/examples/performance/perf/index.zh.md b/packages/site/examples/performance/perf/index.zh.md new file mode 100644 index 0000000000..01535d2a5c --- /dev/null +++ b/packages/site/examples/performance/perf/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 性能案例 +order: 0 +--- + +对 G6 的性能测试,用来验证 G6 能够承载的数据量,分别使用 5000+ 图元、将近 20000 图元及 50000+ 图元的示例进行了测试,从结果来看,20000 左右图元时,G6 是可以正常交互的,当数据量达到 50000+ 时,交互就会出现一定的卡顿,但对于绝大部分业务来说,都不建议在画布上展示如此多的数据,具体的做法可以参考 AntV G6 团队的大图可视化方案,预计 1122 发布。 diff --git a/packages/site/examples/scatter/changePosition/demo/default.js b/packages/site/examples/scatter/changePosition/demo/default.js new file mode 100644 index 0000000000..c4f8db480b --- /dev/null +++ b/packages/site/examples/scatter/changePosition/demo/default.js @@ -0,0 +1,64 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: 'a', + x: 200, + y: 100, + style: { fill: '#5B8FF9', stroke: null }, + }, + { + id: 'b', + x: 100, + y: 200, + style: { fill: '#5AD8A6', stroke: null }, + }, + { + id: 'c', + x: 300, + y: 200, + style: { fill: '#5D7092', stroke: null }, + }, + ], + edges: [ + { + id: 'a2b', + source: 'a', + target: 'b', + }, + { + id: 'a2c', + source: 'a', + target: 'c', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + animate: true, +}); + +graph.data(data); +graph.render(); + +setInterval(() => { + data.nodes.forEach((node) => { + node.x += Math.random() * 50 - 25; + node.y += Math.random() * 50 - 25; + }); + graph.changeData(data); +}, 600); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/scatter/changePosition/demo/meta.json b/packages/site/examples/scatter/changePosition/demo/meta.json new file mode 100644 index 0000000000..a6ff2466e2 --- /dev/null +++ b/packages/site/examples/scatter/changePosition/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "default.js", + "title": { + "zh": "节点移动动画", + "en": "Node Move Animation" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*M-EGS5v6RnUAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/scatter/changePosition/index.en.md b/packages/site/examples/scatter/changePosition/index.en.md new file mode 100644 index 0000000000..062d6bcee1 --- /dev/null +++ b/packages/site/examples/scatter/changePosition/index.en.md @@ -0,0 +1,10 @@ +--- +title: Node Move Animation +order: 5 +--- + +Change the node positions by `changeData` method is a simple way to realize the node animation. + +### Usage + +For simple animation, change positions by `changeData` is a straightforward way. But it is not a good solution for complex scenario. Please refer to the document [Animation](/en/docs/manual/middle/animation). diff --git a/packages/site/examples/scatter/changePosition/index.zh.md b/packages/site/examples/scatter/changePosition/index.zh.md new file mode 100644 index 0000000000..6457dcbc4f --- /dev/null +++ b/packages/site/examples/scatter/changePosition/index.zh.md @@ -0,0 +1,12 @@ +--- +title: 节点移动动画 +order: 5 +--- + +通过 `changeData` 方法不断地改变节点位置,以达到动画的效果。 + +### 使用指南 + +对于一些简单的动画场景,可以通过这种方式来实现。稍复杂的场景,不建议使用这种方式。 + +G6 中更多关于动画的内容,请参考文档 [动画](/zh/docs/manual/middle/animation)。 diff --git a/packages/site/examples/scatter/customAnimate/demo/meta.json b/packages/site/examples/scatter/customAnimate/demo/meta.json new file mode 100644 index 0000000000..5289eec77c --- /dev/null +++ b/packages/site/examples/scatter/customAnimate/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "position.js", + "title": { + "zh": "自定义动画", + "en": "Custom Animation" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*tN3KQ59_XO8AAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/scatter/customAnimate/demo/position.js b/packages/site/examples/scatter/customAnimate/demo/position.js new file mode 100644 index 0000000000..e1dbd11852 --- /dev/null +++ b/packages/site/examples/scatter/customAnimate/demo/position.js @@ -0,0 +1,67 @@ +import G6 from '@antv/g6'; + +const r = 50; +const radius = Math.PI; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + animate: true, + animateCfg: { + duration: 1000, + onFrame(node, ratio, toAttrs, fromAttrs) { + const current = radius * ratio; + let x = fromAttrs.x; + let y = fromAttrs.y; + if (fromAttrs.x > toAttrs.x) { + x = x - r + r * Math.cos(current); + y += r * Math.sin(current); + } else { + x = x + r - r * Math.cos(current); + y -= r * Math.sin(current); + } + return { x, y }; + }, + }, +}); + +// 加入两个节点 +const node1 = graph.addItem('node', { + id: 'node1', + x: 100, + y: 100, + type: 'circle', + style: { fill: '#5B8FF9', lineWidth: 0 }, +}); +const node2 = graph.addItem('node', { + id: 'node2', + x: 200, + y: 100, + type: 'circle', + style: { fill: '#5AD8A6', lineWidth: 0 }, +}); + +// 循环动画 +let count = 0; +setInterval(() => { + if (count % 2 === 0) { + node1.get('model').x = 200; + node2.get('model').x = 100; + } else { + node1.get('model').x = 100; + node2.get('model').x = 200; + } + count++; + graph.refresh(); +}, 1000); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/scatter/customAnimate/index.en.md b/packages/site/examples/scatter/customAnimate/index.en.md new file mode 100644 index 0000000000..575deb7286 --- /dev/null +++ b/packages/site/examples/scatter/customAnimate/index.en.md @@ -0,0 +1,12 @@ +--- +title: Custom Animation +order: 6 +--- + +Configuration `animateCfg` of `Graph` is designed for global animation. + +### Usage + +The demo shows how to set a global uniform animation. + +For more information, please refer to document [Animation](/en/docs/manual/middle/animation). diff --git a/packages/site/examples/scatter/customAnimate/index.zh.md b/packages/site/examples/scatter/customAnimate/index.zh.md new file mode 100644 index 0000000000..b59c634c2e --- /dev/null +++ b/packages/site/examples/scatter/customAnimate/index.zh.md @@ -0,0 +1,12 @@ +--- +title: 自定义动画 +order: 6 +--- + +在实例化 `Graph` 时,可以通过 `animateCfg` 配置项设置全局动画。 + +### 使用指南 + +当需要对已有的节点,做统一的自定义动画效果时,可以使用这种方式来实现。 + +G6 中更多关于动画的内容,请参考[动画文档](/zh/docs/manual/middle/animation)。 diff --git a/packages/site/examples/scatter/edge/API.en.md b/packages/site/examples/scatter/edge/API.en.md new file mode 100644 index 0000000000..46d455c169 --- /dev/null +++ b/packages/site/examples/scatter/edge/API.en.md @@ -0,0 +1,7 @@ +--- +title: API +--- + +# Custom Behavior + +Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). diff --git a/packages/site/examples/scatter/edge/API.zh.md b/packages/site/examples/scatter/edge/API.zh.md new file mode 100644 index 0000000000..d83a9f5deb --- /dev/null +++ b/packages/site/examples/scatter/edge/API.zh.md @@ -0,0 +1,7 @@ +--- +title: API +--- + +# 自定义复合交互 + +请参考教程 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)。 diff --git a/packages/site/examples/scatter/edge/demo/arrowAnimate.js b/packages/site/examples/scatter/edge/demo/arrowAnimate.js new file mode 100644 index 0000000000..4ccabeb422 --- /dev/null +++ b/packages/site/examples/scatter/edge/demo/arrowAnimate.js @@ -0,0 +1,116 @@ +import G6 from "@antv/g6"; +import { ext } from "@antv/matrix-util"; + +const { getLabelPosition, transform } = G6.Util; + +G6.registerEdge( + "arrow-running", + { + afterDraw(cfg, group) { + // get the first shape in the group, it is the edge's path here= + const shape = group.get("children")[0]; + + const arrow = group.addShape("marker", { + attrs: { + x: 16, + y: 0, + r: 8, + lineWidth: 2, + stroke: "#3370ff", + fill: "#fff", + symbol: (x, y, r) => { + return [ + ["M", x - 6, y - 4], + ["L", x - 2, y], + ["L", x - 6, y + 4] + ]; + } + } + }); + + // animation for the red circle + arrow.animate( + (ratio) => { + // the operations in each frame. Ratio ranges from 0 to 1 indicating the prograss of the animation. Returns the modified configurations + // get the position on the edge according to the ratio + const tmpPoint = shape.getPoint(ratio); + const pos = getLabelPosition(shape, ratio); + let matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; + matrix = transform(matrix, [ + ["t", -tmpPoint.x, -tmpPoint.y], + ["r", pos.angle], + ["t", tmpPoint.x, tmpPoint.y] + ]); + + // returns the modified configurations here, x and y here + return { + x: tmpPoint.x, + y: tmpPoint.y, + matrix + }; + }, + { + // repeat: true, // Whether executes the animation repeatly + duration: 3000 // the duration for executing once + } + ); + } + }, + "cubic" // extend the built-in edge 'cubic' +); + +const data = { + nodes: [ + { + id: "node1", + x: 100, + y: 100, + label: "Node 1", + labelCfg: { + position: "top" + } + }, + { + id: "node2", + x: 300, + y: 200, + color: "#40a9ff", + label: "Node 2", + labelCfg: { + position: "left", + offset: 10 + } + } + ], + edges: [ + { + source: "node1", + target: "node2" + } + ] +}; + +const container = document.getElementById("container"); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: "container", + width, + height, + defaultEdge: { + type: "arrow-running", + style: { + lineWidth: 2, + stroke: "#bae7ff" + } + } +}); +graph.data(data); +graph.render(); + +if (typeof window !== "undefined") + window.onresize = () => { + if (!graph || graph.get("destroyed")) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/scatter/edge/demo/edge.js b/packages/site/examples/scatter/edge/demo/edge.js new file mode 100644 index 0000000000..a3800beb46 --- /dev/null +++ b/packages/site/examples/scatter/edge/demo/edge.js @@ -0,0 +1,89 @@ +import G6 from '@antv/g6'; + +const lineDash = [4, 2, 1, 2]; +G6.registerEdge( + 'line-dash', + { + afterDraw(cfg, group) { + // get the first shape in the group, it is the edge's path here= + const shape = group.get('children')[0]; + let index = 0; + // Define the animation + shape.animate( + () => { + index++; + if (index > 9) { + index = 0; + } + const res = { + lineDash, + lineDashOffset: -index, + }; + // returns the modified configurations here, lineDash and lineDashOffset here + return res; + }, + { + repeat: true, // whether executes the animation repeatly + duration: 3000, // the duration for executing once + }, + ); + }, + }, + 'cubic', // extend the built-in edge 'cubic' +); + +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 100, + label: 'Node 1', + labelCfg: { + position: 'top', + }, + }, + { + id: 'node2', + x: 300, + y: 200, + color: '#40a9ff', + label: 'Node 2', + labelCfg: { + position: 'left', + offset: 10, + }, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultEdge: { + type: 'line-dash', + style: { + lineWidth: 2, + stroke: '#bae7ff', + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/scatter/edge/demo/lineGrowth.js b/packages/site/examples/scatter/edge/demo/lineGrowth.js new file mode 100644 index 0000000000..090a6d0408 --- /dev/null +++ b/packages/site/examples/scatter/edge/demo/lineGrowth.js @@ -0,0 +1,83 @@ +import G6 from '@antv/g6'; + +G6.registerEdge( + 'line-growth', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + const length = shape.getTotalLength(); + shape.animate( + (ratio) => { + // the operations in each frame. Ratio ranges from 0 to 1 indicating the prograss of the animation. Returns the modified configurations + const startLen = ratio * length; + // Calculate the lineDash + const cfg = { + lineDash: [startLen, length - startLen], + }; + return cfg; + }, + { + repeat: true, // Whether executes the animation repeatly + duration: 2000, // the duration for executing once + }, + ); + }, + }, + 'cubic', // extend the built-in edge 'cubic' +); + +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 100, + label: 'Node 1', + labelCfg: { + position: 'top', + }, + }, + { + id: 'node2', + x: 300, + y: 200, + color: '#40a9ff', + label: 'Node 2', + labelCfg: { + position: 'left', + offset: 10, + }, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultEdge: { + type: 'line-growth', + style: { + lineWidth: 2, + stroke: '#bae7ff', + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/scatter/edge/demo/meta.json b/packages/site/examples/scatter/edge/demo/meta.json new file mode 100644 index 0000000000..2ff0fbde54 --- /dev/null +++ b/packages/site/examples/scatter/edge/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "arrowAnimate.js", + "title": { + "zh": "箭头沿边运动", + "en": "Running Arrow" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*KZNLQrnz3o0AAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "pointInLine.js", + "title": { + "zh": "圆点沿边运动", + "en": "Running Circle" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*HqX_Q7lljpsAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "edge.js", + "title": { + "zh": "虚线运动", + "en": "Running Dashed Edge" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*70lpSLPAxb8AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "lineGrowth.js", + "title": { + "zh": "无到有的边", + "en": "Growing Edge" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*W40_RKyCrDsAAAAAAAAAAABkARQnAQ" + } + ] +} \ No newline at end of file diff --git a/packages/site/examples/scatter/edge/demo/pointInLine.js b/packages/site/examples/scatter/edge/demo/pointInLine.js new file mode 100644 index 0000000000..6cf312d8a4 --- /dev/null +++ b/packages/site/examples/scatter/edge/demo/pointInLine.js @@ -0,0 +1,99 @@ +import G6 from '@antv/g6'; +G6.registerEdge( + 'circle-running', + { + afterDraw(cfg, group) { + // get the first shape in the group, it is the edge's path here= + const shape = group.get('children')[0]; + // the start position of the edge's path + const startPoint = shape.getPoint(0); + + // add red circle shape + const circle = group.addShape('circle', { + attrs: { + x: startPoint.x, + y: startPoint.y, + fill: '#1890ff', + r: 3, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + + // animation for the red circle + circle.animate( + (ratio) => { + // the operations in each frame. Ratio ranges from 0 to 1 indicating the prograss of the animation. Returns the modified configurations + // get the position on the edge according to the ratio + const tmpPoint = shape.getPoint(ratio); + // returns the modified configurations here, x and y here + return { + x: tmpPoint.x, + y: tmpPoint.y, + }; + }, + { + repeat: true, // Whether executes the animation repeatly + duration: 3000, // the duration for executing once + }, + ); + }, + }, + 'cubic', // extend the built-in edge 'cubic' +); + +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 100, + label: 'Node 1', + labelCfg: { + position: 'top', + }, + }, + { + id: 'node2', + x: 300, + y: 200, + color: '#40a9ff', + label: 'Node 2', + labelCfg: { + position: 'left', + offset: 10, + }, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultEdge: { + type: 'circle-running', + style: { + lineWidth: 2, + stroke: '#bae7ff', + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/scatter/edge/index.en.md b/packages/site/examples/scatter/edge/index.en.md new file mode 100644 index 0000000000..e1437f773d --- /dev/null +++ b/packages/site/examples/scatter/edge/index.en.md @@ -0,0 +1,16 @@ +--- +title: Edge Animation +order: 2 +--- + +Edge animation can be realized by customing an edge. + +### Usage + +The three demos below show the edge animation realized by customing edge: + +- Example 1 : A dot runs alone the edge; +- Example 2 : A running dashed edge; +- Example 3 : A edge grow from source to target. + +For more information, please refer to document [Animation](/en/docs/manual/middle/animation). diff --git a/packages/site/examples/scatter/edge/index.zh.md b/packages/site/examples/scatter/edge/index.zh.md new file mode 100644 index 0000000000..8c2ea84647 --- /dev/null +++ b/packages/site/examples/scatter/edge/index.zh.md @@ -0,0 +1,16 @@ +--- +title: 边动画 +order: 2 +--- + +通过自定义边,可以实现边的动画效果。 + +### 使用指南 + +下面代码演示了三种通过自定义边实现的边动画: + +- 圆点在沿着线运动; +- 虚线运动的效果; +- 线从无到有的效果。 + +G6 中更多关于动画的内容,请参考[动画文档](/zh/docs/manual/middle/animation)。 diff --git a/packages/site/examples/scatter/node/API.en.md b/packages/site/examples/scatter/node/API.en.md new file mode 100644 index 0000000000..46d455c169 --- /dev/null +++ b/packages/site/examples/scatter/node/API.en.md @@ -0,0 +1,7 @@ +--- +title: API +--- + +# Custom Behavior + +Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). diff --git a/packages/site/examples/scatter/node/API.zh.md b/packages/site/examples/scatter/node/API.zh.md new file mode 100644 index 0000000000..d83a9f5deb --- /dev/null +++ b/packages/site/examples/scatter/node/API.zh.md @@ -0,0 +1,7 @@ +--- +title: API +--- + +# 自定义复合交互 + +请参考教程 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)。 diff --git a/packages/site/examples/scatter/node/demo/meta.json b/packages/site/examples/scatter/node/demo/meta.json new file mode 100644 index 0000000000..4f2fafd635 --- /dev/null +++ b/packages/site/examples/scatter/node/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "node.js", + "title": { + "zh": "节点动画", + "en": "Node Animation" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*MDDjSov7gMkAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/scatter/node/demo/node.js b/packages/site/examples/scatter/node/demo/node.js new file mode 100644 index 0000000000..1f1b66b52c --- /dev/null +++ b/packages/site/examples/scatter/node/demo/node.js @@ -0,0 +1,249 @@ +import G6 from '@antv/g6'; + +const Util = G6.Util; +const data = { + nodes: [ + { + id: 'node1', + x: 100, + y: 100, + type: 'circle-animate', + size: 20, + label: 'Scale Animation', + labelCfg: { + position: 'top', + }, + }, + { + id: 'node2', + x: 300, + y: 200, + type: 'background-animate', + color: '#40a9ff', + size: 20, + label: 'Background Animation', + labelCfg: { + position: 'left', + offset: 10, + }, + }, + { + id: 'node3', + x: 400, + y: 100, + size: [40, 40], + type: 'inner-animate', + img: + 'data:image/webp;base64,UklGRq4FAABXRUJQVlA4IKIFAABwHwCdASo8ADwAPiEMhEGhhv6rQAYAgS2NHsdCq/4D8AOoA64OEUAj/XPxVwyvRGvyO/gGxN/t3oK/1X6zesX3L/p/RP/2HCgKAB9AGeAbCB+AGwAbQBtA/8c/m/4PYHTonm+SzRH6B9sv2i/rOZC+G/ln9l/ML/GdoD7APcA/TD+09QDzAfrX+vHYM9AD+Uf0zrAPQA/bH0vv2t+CH9qf2R+A39e6X098/I7IAcLcs8Gjmc/9T7gPbX9H+wJ+rPVV9En9ZmBI5oUiYhkYHIVjRr9hzCTPcV5Rs/wjjIHkxPgtr/3ALZSuUm146HHwqQVA23hnnqH/4aJ/k4v4hU6RBZ0AAAD+//8ARPyL9yWIlAbWBAD0oKSqlYWreuRa3Oj02u+TvSQS8iwMYewUYTWLDNp9wOlFJaWnqE+za35UwUXuDAT6T0I4fwY+u+qrRVhl+S1ir4X7BQiNswug5AX+MjQcXEeUwfSIEUT+DFPCr+BUiwTbFxLni7fv61vRbmXoauLz4tiqOFTzEGP8tNXP5+H7mZVGfNjIxapT3FGUtqBdp/SD5cTOYOkn2fawkpqpCSqf2+CfiGWtIF673fEzlk/hIbWDhQ81C/ddxLn609d/5efckbdZ8HZbhhVmM82/Uat7CFmw1SH5xCxRxEEhjpf1EP1Xn5q9VZfm1+OFTab/MN67Xha8K//5oVBlMgZALE653X0fas/+2xMqiyCu5Wa7PHsCwbBwqROfNmzi4LPOTjkFPHVKDD1Nfj4/sul6cANdF68rf2jszlyZsUUoLTP7H3swSroc3ssNXSRVAcYd7+iBZpfoAYWvKgnr+Hv62fHZX5ZbjYbYzVjq6fsXkubto858NuUx4+ILb5y7dP6W3/IYVeUSF0yZseKIZhOMs9BBf5uB2Y3Ott//+1OG7hYINzcqigrzWAOJbSmVw3G0ULywkobx+rvfk8VmZFzQGgP/+4T74mp/vsZyM1NLguiTO2gNO05tcpXwveq5mrcweXrJ/bRZDmU2KBrnXhkXq+735c+UHTFq4h4jMOPm5shKioB6XaqhUf3DJlMg8937g/SKjrgD0H5sNm0/k4FfilBbcrsjc3dd6cwEYJo3CNhe3SpNJ2geNXyV4/hq/BZXZ1kiVknjmf5cUx7Tv/9Sb+fQ/DgAsRNSz1wiVodiLjP7aVrkxbWx5gJ8U/j0o1Ipm/nyDZRPrQXmbPAcy1eDDejxTDBKe42ElHpC2QlFdhOedsp4i9QVjt8EqWGy1YzPaGqZhCVg/LWt8/+4BmiCzNGtpR21MGJf4kI/n/1sbe36e1QBCBAx4EVfTM82ZM2lh0P189e7eY0A3NzXWVrUek8SEn+DYYCQeEaC5hDHGreFHT1baY6KyrFx3G9oMm3fLrCqmNjFRnZa3LB/5m8FgCpq9B/1OCLRE5GzVTZnVzj/4V38PgCIpX16Kznijf01+MkDeS9oCF2hEXQ9tr+mPLrjGy4Cg5fyLgyCj1fUq33nMf79Svli2h83m3gqkoxJcXvBetFQP8V/gRjBNGmFXK5TfwLhbolWEjDqUGK3n+hxzQLif9zreYO88EIRTUNbzE1/Sn7rBEtjB0uawNje5OubWsB62SOlMZoZpxrDbMb4UvQrODPhSafmhcYe9zm/dHxssMfUthhDKjyMhoRhngPjbzfGXmIV2Omgrn/zbefK/PawUGSH6x4Qk4HCN4/X8S+XCf51JJtOQeHST/yfwg69uMkE07SONnhGUrL6j5oQn6JI+zkaH/H/P/Ti/pfOTfAWxQNiMvWX08mqbuUweFSQ/G5YUP/uCvZAXutf1+Nhl2jj/n4/fPOihPjwvfFnnjOaQvs9PSpF33d+396LASZ3IID/4UP4pf9eOMXw82ccoUUUHX6MfBWyBDvARCrdPmerUwKwW+lBIAe1dsAAAA==', + label: 'Image Rotate', + labelCfg: { + position: 'right', + }, + }, + { + id: 'node4', + x: 300, + y: 300, + type: 'rect', + label: 'No Animation', + labelCfg: { + position: 'bottom', + }, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + { + source: 'node3', + target: 'node2', + }, + { + source: 'node2', + target: 'node4', + }, + ], +}; + +// Scale Animation +G6.registerNode( + 'circle-animate', + { + afterDraw(cfg, group) { + const shape = group.get('children')[0]; + shape.animate( + (ratio) => { + const diff = ratio <= 0.5 ? ratio * 10 : (1 - ratio) * 10; + return { + r: cfg.size / 2 + diff, + }; + }, + { + repeat: true, + duration: 3000, + easing: 'easeCubic', + }, + ); + }, + }, + 'circle', +); + +// Background Animation +G6.registerNode( + 'background-animate', + { + afterDraw(cfg, group) { + const r = cfg.size / 2; + const back1 = group.addShape('circle', { + zIndex: -3, + attrs: { + x: 0, + y: 0, + r, + fill: cfg.color, + opacity: 0.6, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'back1-shape', + }); + const back2 = group.addShape('circle', { + zIndex: -2, + attrs: { + x: 0, + y: 0, + r, + fill: cfg.color, + opacity: 0.6, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'back2-shape', + }); + const back3 = group.addShape('circle', { + zIndex: -1, + attrs: { + x: 0, + y: 0, + r, + fill: cfg.color, + opacity: 0.6, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'back3-shape', + }); + group.sort(); // Sort according to the zIndex + back1.animate( + { + // Magnifying and disappearing + r: r + 10, + opacity: 0.1, + }, + { + duration: 3000, + easing: 'easeCubic', + delay: 0, + repeat: true, // repeat + }, + ); // no delay + back2.animate( + { + // Magnifying and disappearing + r: r + 10, + opacity: 0.1, + }, + { + duration: 3000, + easing: 'easeCubic', + delay: 1000, + repeat: true, // repeat + }, + ); // 1s delay + back3.animate( + { + // Magnifying and disappearing + r: r + 10, + opacity: 0.1, + }, + { + duration: 3000, + easing: 'easeCubic', + delay: 2000, + repeat: true, // repeat + }, + ); // 3s delay + }, + }, + 'circle', +); + +// Image animation +G6.registerNode( + 'inner-animate', + { + afterDraw(cfg, group) { + const size = cfg.size; + const width = size[0] - 12; + const height = size[1] - 12; + const image = group.addShape('image', { + attrs: { + x: -width / 2, + y: -height / 2, + width, + height, + img: cfg.img, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'image-shape', + }); + image.animate( + (ratio) => { + const toMatrix = Util.transform( + [1, 0, 0, 0, 1, 0, 0, 0, 1], + [['r', ratio * Math.PI * 2]], + ); + return { + matrix: toMatrix, + }; + }, + { + repeat: true, + duration: 3000, + easing: 'easeCubic', + }, + ); + }, + }, + 'rect', +); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultNode: { + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + lineWidth: 1, + stroke: '#b5b5b5', + }, + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/scatter/node/index.en.md b/packages/site/examples/scatter/node/index.en.md new file mode 100644 index 0000000000..40cd8f3d11 --- /dev/null +++ b/packages/site/examples/scatter/node/index.en.md @@ -0,0 +1,16 @@ +--- +title: Node Animation +order: 1 +--- + +Node animation can be realized by customing a node. + +### Usage + +The three demos below show the node animation realized by customing node: + +- Example 1 : Animated shape of a node; +- Example 2 : Animated background of a node; +- Example 3 : Shape rotate animation on the node. + +For more information, please refer to document [Animation](/en/docs/manual/middle/animation). diff --git a/packages/site/examples/scatter/node/index.zh.md b/packages/site/examples/scatter/node/index.zh.md new file mode 100644 index 0000000000..cdabdde3d3 --- /dev/null +++ b/packages/site/examples/scatter/node/index.zh.md @@ -0,0 +1,16 @@ +--- +title: 节点动画 +order: 1 +--- + +通过自定义节点,可以实现节点的动画效果。 + +### 使用指南 + +下面代码演示了三种通过自定义边实现的边动画: + +- 节点上图形的动画; +- 增加带有动画的背景图形; +- 节点上部分图形的旋转动画。 + +G6 中更多关于动画的内容,请参考[动画文档](/zh/docs/manual/middle/animation)。 diff --git a/packages/site/examples/scatter/stateChange/API.en.md b/packages/site/examples/scatter/stateChange/API.en.md new file mode 100644 index 0000000000..5a91c8f18c --- /dev/null +++ b/packages/site/examples/scatter/stateChange/API.en.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# Custom Behavior + +Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior). + +# 状态 + + diff --git a/packages/site/examples/scatter/stateChange/API.zh.md b/packages/site/examples/scatter/stateChange/API.zh.md new file mode 100644 index 0000000000..0816a88c23 --- /dev/null +++ b/packages/site/examples/scatter/stateChange/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +# 自定义复合交互 + +请参考教程 [自定义 Behavior](/zh/docs/manual/middle/states/custom-behavior)。 + +# 状态 + + diff --git a/packages/site/examples/scatter/stateChange/demo/hover.js b/packages/site/examples/scatter/stateChange/demo/hover.js new file mode 100644 index 0000000000..fac87a852b --- /dev/null +++ b/packages/site/examples/scatter/stateChange/demo/hover.js @@ -0,0 +1,183 @@ +import G6 from '@antv/g6'; + +const nodes = []; +const edges = []; + +// Center node +const centerNode = { + id: 'center', + x: 500, + y: 300, + type: 'center-node', + size: 20, +}; +nodes.push(centerNode); +// Add 4 nodes on the left +for (let i = 0; i < 4; i++) { + const id = 'left' + i; + nodes.push({ + id, + x: 250, + y: (i + 1) * 100 + 50, + type: 'leaf-node', + }); + edges.push({ source: id, target: 'center', type: 'can-running' }); +} +// Add 6 nodes on the right +for (let i = 0; i < 6; i++) { + const id = 'right' + i; + nodes.push({ + id, + x: 750, + y: i * 100 + 50, + type: 'leaf-node', + }); + edges.push({ source: 'center', target: id, type: 'can-running' }); +} + +G6.registerNode( + 'leaf-node', + { + afterDraw(cfg, group) { + group.addShape('circle', { + attrs: { + x: 0, + y: 0, + r: 5, + fill: cfg.color || '#5B8FF9', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + }); + }, + getAnchorPoints() { + return [ + [0, 0.5], + [1, 0.5], + ]; + }, + }, + 'circle', +); + +G6.registerNode( + 'center-node', + { + afterDraw(cfg, group) { + const r = cfg.size / 2; + group.addShape('circle', { + zIndex: -3, + attrs: { + x: 0, + y: 0, + r: r + 10, + fill: 'gray', + opacity: 0.4, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape1', + }); + group.addShape('circle', { + zIndex: -2, + attrs: { + x: 0, + y: 0, + r: r + 20, + fill: 'gray', + opacity: 0.2, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape2', + }); + group.sort(); + }, + getAnchorPoints() { + return [ + [0, 0.5], + [1, 0.5], + ]; + }, + }, + 'circle', +); + +// lineDash array +const lineDash = [4, 2, 1, 2]; + +G6.registerEdge( + 'can-running', + { + setState(name, value, item) { + const shape = item.get('keyShape'); + if (name === 'running') { + if (value) { + let index = 0; + shape.animate( + () => { + index++; + if (index > 9) { + index = 0; + } + const res = { + lineDash, + lineDashOffset: -index, + }; + // return the params for this frame + return res; + }, + { + repeat: true, + duration: 3000, + }, + ); + } else { + shape.stopAnimate(); + shape.attr('lineDash', null); + } + } + }, + }, + 'cubic-horizontal', +); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + defaultNode: { + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + }, + }, +}); +graph.data({ nodes, edges }); +graph.render(); +graph.fitView(); + +// set hover state +graph.on('node:mouseenter', (ev) => { + const node = ev.item; + const edges = node.getEdges(); + edges.forEach((edge) => graph.setItemState(edge, 'running', true)); +}); +graph.on('node:mouseleave', (ev) => { + const node = ev.item; + const edges = node.getEdges(); + edges.forEach((edge) => graph.setItemState(edge, 'running', false)); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/scatter/stateChange/demo/meta.json b/packages/site/examples/scatter/stateChange/demo/meta.json new file mode 100644 index 0000000000..02d25a996d --- /dev/null +++ b/packages/site/examples/scatter/stateChange/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "hover.js", + "title": { + "zh": "状态与动画", + "en": "State and Animation" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*WJhtR4pSJssAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/scatter/stateChange/index.en.md b/packages/site/examples/scatter/stateChange/index.en.md new file mode 100644 index 0000000000..6d4a0039c7 --- /dev/null +++ b/packages/site/examples/scatter/stateChange/index.en.md @@ -0,0 +1,12 @@ +--- +title: State Switch +order: 3 +--- + +State of G6 includes interaction states and bussiness states. + +### Usage + +Animation is an important part of visualization. Users can realized complex animation by `onFrame`. The Demo below shows how to realized edge animation with interaction by states. + +For more information, please refer to document [Animation](/zh/docs/manual/middle/animation)。 diff --git a/packages/site/examples/scatter/stateChange/index.zh.md b/packages/site/examples/scatter/stateChange/index.zh.md new file mode 100644 index 0000000000..c6cbe1740c --- /dev/null +++ b/packages/site/examples/scatter/stateChange/index.zh.md @@ -0,0 +1,12 @@ +--- +title: 状态切换 +order: 3 +--- + +G6 中的 state,指的是状态,包括交互状态和业务状态两种。 + +### 使用指南 + +动画是可视化中非常重要的内容。对于一些复杂的动画效果,可以通过 `onFrame` 来实现。下面代码演示展示通过结合状态,实现在交互过程中,展示单条边或多条边的动画。 + +G6 中更多关于动画的内容,请参考[动画文档](/zh/docs/manual/middle/animation)。 diff --git a/packages/site/examples/scatter/viewport/API.en.md b/packages/site/examples/scatter/viewport/API.en.md new file mode 100644 index 0000000000..486d5e0a9f --- /dev/null +++ b/packages/site/examples/scatter/viewport/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/scatter/viewport/API.zh.md b/packages/site/examples/scatter/viewport/API.zh.md new file mode 100644 index 0000000000..e92fbefc35 --- /dev/null +++ b/packages/site/examples/scatter/viewport/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/scatter/viewport/demo/default.js b/packages/site/examples/scatter/viewport/demo/default.js new file mode 100644 index 0000000000..96740658bf --- /dev/null +++ b/packages/site/examples/scatter/viewport/demo/default.js @@ -0,0 +1,115 @@ +import G6 from '@antv/g6'; + +const tipDiv = document.createElement('div'); +tipDiv.innerHTML = `Click node or edge to focus on it with animate. 点击节点或边可带动画地聚焦到该元素上`; +document.getElementById('container').appendChild(tipDiv); +const tipDiv2 = document.createElement('div'); +tipDiv2.innerHTML = `Click the button on toolbar to see viewport change animately. 点击 Toolbar 上的按钮可带动画地操作视图`; +document.getElementById('container').appendChild(tipDiv2); + +const data = { + nodes: [ + { + id: 'a', + x: 200, + y: 100, + style: { fill: '#5B8FF9', stroke: null }, + }, + { + id: 'b', + x: 100, + y: 200, + style: { fill: '#5AD8A6', stroke: null }, + }, + { + id: 'c', + x: 300, + y: 200, + style: { fill: '#5D7092', stroke: null }, + }, + ], + edges: [ + { + id: 'a2b', + source: 'a', + target: 'b', + }, + { + id: 'a2c', + source: 'a', + target: 'c', + }, + ], +}; + +const animateCfg = { duration: 200, easing: 'easeCubic' } + +const toolbar = new G6.ToolBar({ + position: { x: 10, y: 70 }, + getContent: () => ` +
    +
  • + + + +
  • +
  • + + + +
  • +
  • + + + +
  • +
  • + + + +
  • +
`, + handleClick: (code, graph) => { + switch (code) { + case 'zoomOut': + graph.zoom(1.2, undefined, true, animateCfg); + break; + case 'zoomIn': + graph.zoom(0.8, undefined, true, animateCfg); + break; + case 'realZoom': + graph.zoomTo(1, undefined, true, animateCfg); + break; + case 'autoZoom': + graph.fitView(20, undefined, true, animateCfg); + break; + } + } +}) + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 50; +const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [toolbar], +}); + +graph.data(data); +graph.render(); + +graph.on('node:click', e => { + graph.focusItem(e.item, true, animateCfg) +}) +graph.on('edge:click', e => { + graph.focusItem(e.item, true, animateCfg) +}) + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/scatter/viewport/demo/meta.json b/packages/site/examples/scatter/viewport/demo/meta.json new file mode 100644 index 0000000000..52c4a01efa --- /dev/null +++ b/packages/site/examples/scatter/viewport/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "default.js", + "title": { + "zh": "视口变换动画", + "en": "Viewport Animation" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*HRIpRobLTr0AAAAAAAAAAAAAARQnAQ" + } + ] +} \ No newline at end of file diff --git a/packages/site/examples/scatter/viewport/index.en.md b/packages/site/examples/scatter/viewport/index.en.md new file mode 100644 index 0000000000..747bab2f52 --- /dev/null +++ b/packages/site/examples/scatter/viewport/index.en.md @@ -0,0 +1,10 @@ +--- +title: Viewport Animation +order: 0 +--- + +Configure animtions for viewport move, zoom, fitView, fitCenter, etc. + +### Usage + +Viewport update with animations could be achieved easily by configuring `animate` and `animateCfg` to the corresponding APIs, e.g. `graph.translate`, `graph.moveTo`, `graph.zoom`, `graph.zoomTo`, `graph.fitView`, `graph.fitCenter`, `graph.focusItem`. Please refer to the document [Graph Viewport API](/en/docs/api/graphFunc/transform). diff --git a/packages/site/examples/scatter/viewport/index.zh.md b/packages/site/examples/scatter/viewport/index.zh.md new file mode 100644 index 0000000000..93768700c0 --- /dev/null +++ b/packages/site/examples/scatter/viewport/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 视口变换动画 +order: 0 +--- + +为视口/全图的平移、缩放、自适应等配置动画。 + +### 使用指南 + +通过在调用各个视口操作 API 时配置 `animate` 和 `animateCfg` 参数,可以非常快捷地为这些变换配置动画。这些 API 包括 `graph.translate`, `graph.moveTo`, `graph.zoom`, `graph.zoomTo`, `graph.fitView`, `graph.fitCenter`, `graph.focusItem` 等。详见 [图视口 API](/zh/docs/api/graphFunc/transform)。 \ No newline at end of file diff --git a/packages/site/examples/tool/contextMenu/API.en.md b/packages/site/examples/tool/contextMenu/API.en.md new file mode 100644 index 0000000000..f8397eeb9b --- /dev/null +++ b/packages/site/examples/tool/contextMenu/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/contextMenu/API.zh.md b/packages/site/examples/tool/contextMenu/API.zh.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/contextMenu/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/contextMenu/demo/contextMenu.js b/packages/site/examples/tool/contextMenu/demo/contextMenu.js new file mode 100644 index 0000000000..f74c286929 --- /dev/null +++ b/packages/site/examples/tool/contextMenu/demo/contextMenu.js @@ -0,0 +1,150 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +// define the CSS with the id of your menu + +insertCss(` + #contextMenu { + position: absolute; + list-style-type: none; + padding: 10px 8px; + left: -150px; + background-color: rgba(255, 255, 255, 0.9); + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + } + #contextMenu li { + cursor: pointer; + list-style-type:none; + list-style: none; + margin-left: 0px; + } + #contextMenu li:hover { + color: #aaa; + } +`); + +const descriptionDiv = document.createElement('div'); +descriptionDiv.id = 'discription'; +descriptionDiv.innerHTML = 'Right click a node to activate a contextMenu.'; +document.getElementById('container').appendChild(descriptionDiv); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const contextMenu = new G6.Menu({ + getContent(evt) { + let header; + if (evt.target && evt.target.isCanvas && evt.target.isCanvas()) { + header = 'Canvas ContextMenu'; + } else if (evt.item) { + const itemType = evt.item.getType(); + header = `${itemType.toUpperCase()} ContextMenu`; + } + return ` +

${header}

+
    +
  • li 1
  • +
  • li 2
  • +
  • li 3
  • +
  • li 4
  • +
  • li 5
  • +
`; + }, + handleMenuClick: (target, item) => { + console.log(target, item); + }, + // offsetX and offsetY include the padding of the parent container + // 需要加上父级容器的 padding-left 16 与自身偏移量 10 + offsetX: 16 + 10, + // 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10 + offsetY: 0, + // the types of items that allow the menu show up + // 在哪些类型的元素上响应 + itemTypes: ['node', 'edge', 'canvas'], +}); + +const graph = new G6.Graph({ + // 使用 contextMenu plugins 时,需要将 container 设置为 position: relative; + container: 'container', + width, + height, + linkCenter: true, + plugins: [contextMenu], + defaultNode: { + size: [80, 40], + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, // Enlarge the range the edge to be hitted + }, + labelCfg: { + autoRotate: true, + style: { + // A white stroke with width 5 helps show the label more clearly avoiding the interference of the overlapped edge + stroke: 'white', + lineWidth: 5, + }, + }, + }, + modes: { + default: ['drag-node'], + }, +}); + +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + x: 200, + y: 100, + type: 'rect', + }, + { + id: 'node2', + label: 'node2', + x: 250, + y: 250, + type: 'rect', + }, + { + id: 'node3', + label: 'node3', + x: 350, + y: 100, + type: 'rect', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + label: 'Test Label', + }, + { + source: 'node1', + target: 'node3', + label: 'Test Label 2', + }, + ], +}; + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/contextMenu/demo/meta.json b/packages/site/examples/tool/contextMenu/demo/meta.json new file mode 100644 index 0000000000..e2f8359f77 --- /dev/null +++ b/packages/site/examples/tool/contextMenu/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "contextMenu.js", + "title": { + "zh": "上下文菜单", + "en": "Context Menu" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*YtBHR5akE2gAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tool/contextMenu/index.en.md b/packages/site/examples/tool/contextMenu/index.en.md new file mode 100644 index 0000000000..f5d5d88bdd --- /dev/null +++ b/packages/site/examples/tool/contextMenu/index.en.md @@ -0,0 +1,27 @@ +--- +title: ContextMenu +order: 1 +--- + +Using contextMenu in G6. + +## Usage + +The demo below show how to use contextMenu on a graph. Menu's style can be defined by the CSS: + +```css +/* Define the CSS with the id of your menu */ + #contextMenu { + position: absolute; + /* ... Other styles */ + } + #contextMenu li { + cursor: pointer; + /* ... Other styles */ + } + #contextMenu li:hover { + color: #aaa; + /* ... Other styles */ + } +`); +``` diff --git a/packages/site/examples/tool/contextMenu/index.zh.md b/packages/site/examples/tool/contextMenu/index.zh.md new file mode 100644 index 0000000000..c9cf47f5ae --- /dev/null +++ b/packages/site/examples/tool/contextMenu/index.zh.md @@ -0,0 +1,26 @@ +--- +title: 上下文菜单 +order: 1 +--- + +G6 中内置的 contextMenu。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 ContextMenu。如果需要定义菜单的样式,需要定义菜单对应标签的 CSS 样式,例如: + +```css +/* 根据菜单对应标签的 id 进行设置 */ + #contextMenu { + position: absolute; + /* ... Other styles */ + } + #contextMenu li { + cursor: pointer; + /* ... Other styles */ + } + #contextMenu li:hover { + color: #aaa; + /* ... Other styles */ + } +``` diff --git a/packages/site/examples/tool/edgeBundling/API.en.md b/packages/site/examples/tool/edgeBundling/API.en.md new file mode 100644 index 0000000000..f8397eeb9b --- /dev/null +++ b/packages/site/examples/tool/edgeBundling/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/edgeBundling/API.zh.md b/packages/site/examples/tool/edgeBundling/API.zh.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/edgeBundling/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/edgeBundling/demo/edgeBundling.js b/packages/site/examples/tool/edgeBundling/demo/edgeBundling.js new file mode 100644 index 0000000000..de33e51c57 --- /dev/null +++ b/packages/site/examples/tool/edgeBundling/demo/edgeBundling.js @@ -0,0 +1,427 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + { + id: '16', + label: '16', + }, + { + id: '17', + label: '17', + }, + { + id: '18', + label: '18', + }, + { + id: '19', + label: '19', + }, + { + id: '20', + label: '20', + }, + { + id: '21', + label: '21', + }, + { + id: '22', + label: '22', + }, + { + id: '23', + label: '23', + }, + { + id: '24', + label: '24', + }, + { + id: '25', + label: '25', + }, + { + id: '26', + label: '26', + }, + { + id: '27', + label: '27', + }, + { + id: '28', + label: '28', + }, + { + id: '29', + label: '29', + }, + { + id: '30', + label: '30', + }, + { + id: '31', + label: '31', + }, + { + id: '32', + label: '32', + }, + { + id: '33', + label: '33', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const edgeBundling = new G6.Bundling({ + bundleThreshold: 0.1, +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + layout: { + type: 'circular', + center: [width / 2, height / 2], + radius: height / 2.5, + ordering: null, + }, + plugins: [edgeBundling], + defaultNode: { + size: [20, 20], + color: 'steelblue', + }, + defaultEdge: { + size: 1, + color: '#999', + }, +}); +graph.data(data); +graph.render(); + +setTimeout(() => { + edgeBundling.bundling(data); +}, 1000); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/edgeBundling/demo/meta.json b/packages/site/examples/tool/edgeBundling/demo/meta.json new file mode 100644 index 0000000000..4442f4ef1f --- /dev/null +++ b/packages/site/examples/tool/edgeBundling/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "edgeBundling.js", + "title": { + "zh": "边绑定", + "en": "Edge Bundling" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*7g4iRqVfkHQAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tool/edgeBundling/index.en.md b/packages/site/examples/tool/edgeBundling/index.en.md new file mode 100644 index 0000000000..2209d5ee3f --- /dev/null +++ b/packages/site/examples/tool/edgeBundling/index.en.md @@ -0,0 +1,10 @@ +--- +title: Edge Bundling +order: 3 +--- + +Bundling is a built-in components in G6. It is used for bundling the complex edges to reduce the visual clutter. + +## Usage + +The demo below show how to use Bundling on graph. diff --git a/packages/site/examples/tool/edgeBundling/index.zh.md b/packages/site/examples/tool/edgeBundling/index.zh.md new file mode 100644 index 0000000000..760adc6a22 --- /dev/null +++ b/packages/site/examples/tool/edgeBundling/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 边绑定 +order: 3 +--- + +G6 中内置的 Bundling 组件,用于边的绑定。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 Bundling。 diff --git a/packages/site/examples/tool/edgeFilterLens/API.en.md b/packages/site/examples/tool/edgeFilterLens/API.en.md new file mode 100644 index 0000000000..f8397eeb9b --- /dev/null +++ b/packages/site/examples/tool/edgeFilterLens/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/edgeFilterLens/API.zh.md b/packages/site/examples/tool/edgeFilterLens/API.zh.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/edgeFilterLens/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/edgeFilterLens/demo/edgeFilter.js b/packages/site/examples/tool/edgeFilterLens/demo/edgeFilter.js new file mode 100644 index 0000000000..1cfc55003d --- /dev/null +++ b/packages/site/examples/tool/edgeFilterLens/demo/edgeFilter.js @@ -0,0 +1,178 @@ +import G6 from '@antv/g6'; + +let trigger = 'mousemove'; +const filterConfigs = { + trigger, + showLabel: 'edge', + r: 60, +}; +let filterLens = new G6.EdgeFilterLens(filterConfigs); + +// ================= The DOMs for configurations =============== // +const graphDiv = document.getElementById('container'); + +const buttonContainer = document.createElement('div'); +buttonContainer.style.display = 'inline-block'; +buttonContainer.style.height = '35px'; +buttonContainer.style.width = '100%'; +buttonContainer.style.textAlign = 'center'; + +// tip +const tip = document.createElement('span'); +tip.innerHTML = '点击画布任意位置开始探索。过滤镜中显示两端节点均在过滤镜中的边。'; +buttonContainer.appendChild(tip); + +buttonContainer.appendChild(document.createElement('br')); + +// tip english +const tipEn = document.createElement('span'); +tipEn.innerHTML = + 'Click the canvas to begin. Show the edge whose both end nodes are inside the lens.'; +buttonContainer.appendChild(tipEn); + +buttonContainer.appendChild(document.createElement('br')); + +// enable/disable the fisheye lens button +const swithButton = document.createElement('input'); +swithButton.type = 'button'; +swithButton.value = 'Disable'; +swithButton.style.height = '25px'; +swithButton.style.width = '60px'; +swithButton.style.marginLeft = '16px'; +buttonContainer.appendChild(swithButton); + +// list for changing trigger +const triggerTag = document.createElement('span'); +triggerTag.innerHTML = 'Trigger:'; +triggerTag.style.marginLeft = '16px'; +buttonContainer.appendChild(triggerTag); +const configTrigger = document.createElement('select'); +configTrigger.value = 'mousemove'; +configTrigger.style.height = '25px'; +configTrigger.style.width = '100px'; +configTrigger.style.marginLeft = '8px'; +const mousemoveTrigger = document.createElement('option'); +mousemoveTrigger.value = 'mousemove'; +mousemoveTrigger.innerHTML = 'mousemove'; +configTrigger.appendChild(mousemoveTrigger); +const dragTrigger = document.createElement('option'); +dragTrigger.value = 'drag'; +dragTrigger.innerHTML = 'drag'; +configTrigger.appendChild(dragTrigger); +const clickTrigger = document.createElement('option'); +clickTrigger.value = 'click'; +clickTrigger.innerHTML = 'click'; +configTrigger.appendChild(clickTrigger); +buttonContainer.appendChild(configTrigger); + +// list for changing scaleRBy +const scaleR = document.createElement('span'); +scaleR.innerHTML = 'Scale r by:'; +scaleR.style.marginLeft = '16px'; +buttonContainer.appendChild(scaleR); +const configScaleRBy = document.createElement('select'); +configScaleRBy.value = 'wheel'; +configScaleRBy.style.height = '25px'; +configScaleRBy.style.width = '100px'; +configScaleRBy.style.marginLeft = '8px'; +const scaleRByWheel = document.createElement('option'); +scaleRByWheel.value = 'wheel'; +scaleRByWheel.innerHTML = 'wheel'; +configScaleRBy.appendChild(scaleRByWheel); +const scaleRByUnset = document.createElement('option'); +scaleRByUnset.value = undefined; +scaleRByUnset.innerHTML = 'undefined'; +configScaleRBy.appendChild(scaleRByUnset); +buttonContainer.appendChild(configScaleRBy); + +graphDiv.appendChild(buttonContainer); + +// ========================================================= // + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 60; +const graph = new G6.Graph({ + container: 'container', + width, + height: height, + plugins: [filterLens], + fitView: true, + defaultEdge: { + labelCfg: { + autoRotate: true, + style: { + stroke: '#fff', + lineWidth: 2, + }, + }, + }, + defaultNode: { + size: 15, + color: '#5B8FF9', + style: { + lineWidth: 2, + fill: '#C6E5FF', + }, + }, + modes: { + default: ['drag-canvas'], + }, +}); + +swithButton.addEventListener('click', (e) => { + if (swithButton.value === 'Disable') { + swithButton.value = 'Enable'; + graph.removePlugin(filterLens); + } else { + swithButton.value = 'Disable'; + filterLens = new G6.EdgeFilterLens({ + ...filterConfigs, + trigger, + }); + graph.addPlugin(filterLens); + } +}); +configScaleRBy.addEventListener('change', (e) => { + filterLens.updateParams({ scaleRBy: e.target.value }); +}); +configTrigger.addEventListener('change', (e) => { + const filterLensConfigs = filterLens._cfgs; + graph.removePlugin(filterLens); + trigger = e.target.value; + filterLens = new G6.EdgeFilterLens({ + ...filterLensConfigs, + trigger, + }); + graph.addPlugin(filterLens); +}); + +fetch('https://gw.alipayobjects.com/os/bmw-prod/afe8b2a6-f691-4070-aa73-46fc07fd1171.json') + .then((res) => res.json()) + .then((data) => { + data.edges.forEach((edge) => { + edge.color = '#aaa'; + edge.size = 2; + edge.style = { + opacity: 0.7, + }; + edge.label = 'a'; + }); + graph.data(data); + graph.render(); + graph.getEdges().forEach((edge) => { + edge + .getContainer() + .getChildren() + .forEach((shape) => { + if (shape.get('type') === 'text') shape.set('visible', false); + }); + }); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 60); + }; diff --git a/packages/site/examples/tool/edgeFilterLens/demo/edgeFilterConfig.js b/packages/site/examples/tool/edgeFilterLens/demo/edgeFilterConfig.js new file mode 100644 index 0000000000..cac91a095e --- /dev/null +++ b/packages/site/examples/tool/edgeFilterLens/demo/edgeFilterConfig.js @@ -0,0 +1,188 @@ +import G6 from '@antv/g6'; + +let trigger = 'drag'; +const filterConfigs = { + trigger, + showLabel: 'edge', + r: 100, + shouldShow: (e) => { + return e.size > 3; + }, + delegateStyle: { + fill: '#F8E8D9', + lineDash: [5, 5], + stroke: '#666', + }, +}; + +let filterLens = new G6.EdgeFilterLens(filterConfigs); + +// ================= The DOMs for configurations =============== // +const graphDiv = document.getElementById('container'); + +const buttonContainer = document.createElement('div'); +buttonContainer.style.display = 'inline-block'; +buttonContainer.style.height = '35px'; +buttonContainer.style.width = '100%'; +buttonContainer.style.textAlign = 'center'; + +// tip +const tip = document.createElement('span'); +tip.innerHTML = + '点击画布任意位置开始探索。过滤镜中显示两端节点均在过滤镜中,且满足 shouldShow 所定义的条件的边。'; +buttonContainer.appendChild(tip); + +buttonContainer.appendChild(document.createElement('br')); + +// tip english +const tipEn = document.createElement('span'); +tipEn.innerHTML = + 'Click the canvas to begin. Show the edge whose both end nodes are inside the lens, meanwhile the edge should meet the requirements defined by shouldShow in.'; +buttonContainer.appendChild(tipEn); + +buttonContainer.appendChild(document.createElement('br')); + +// enable/disable the fisheye lens button +const swithButton = document.createElement('input'); +swithButton.type = 'button'; +swithButton.value = 'Disable'; +swithButton.style.height = '25px'; +swithButton.style.width = '60px'; +swithButton.style.marginLeft = '16px'; +buttonContainer.appendChild(swithButton); + +// list for changing trigger +const triggerTag = document.createElement('span'); +triggerTag.innerHTML = 'Trigger:'; +triggerTag.style.marginLeft = '16px'; +buttonContainer.appendChild(triggerTag); +const configTrigger = document.createElement('select'); +configTrigger.value = 'drag'; +configTrigger.style.height = '25px'; +configTrigger.style.width = '100px'; +configTrigger.style.marginLeft = '8px'; +const dragTrigger = document.createElement('option'); +dragTrigger.value = 'drag'; +dragTrigger.innerHTML = 'drag'; +configTrigger.appendChild(dragTrigger); +const mousemoveTrigger = document.createElement('option'); +mousemoveTrigger.value = 'mousemove'; +mousemoveTrigger.innerHTML = 'mousemove'; +configTrigger.appendChild(mousemoveTrigger); +const clickTrigger = document.createElement('option'); +clickTrigger.value = 'click'; +clickTrigger.innerHTML = 'click'; +configTrigger.appendChild(clickTrigger); +buttonContainer.appendChild(configTrigger); + +// list for changing scaleRBy +const scaleR = document.createElement('span'); +scaleR.innerHTML = 'Scale r by:'; +scaleR.style.marginLeft = '16px'; +buttonContainer.appendChild(scaleR); +const configScaleRBy = document.createElement('select'); +configScaleRBy.value = 'wheel'; +configScaleRBy.style.height = '25px'; +configScaleRBy.style.width = '100px'; +configScaleRBy.style.marginLeft = '8px'; +const scaleRByWheel = document.createElement('option'); +scaleRByWheel.value = 'wheel'; +scaleRByWheel.innerHTML = 'wheel'; +configScaleRBy.appendChild(scaleRByWheel); +const scaleRByUnset = document.createElement('option'); +scaleRByUnset.value = undefined; +scaleRByUnset.innerHTML = 'undefined'; +configScaleRBy.appendChild(scaleRByUnset); +buttonContainer.appendChild(configScaleRBy); + +graphDiv.appendChild(buttonContainer); + +// ========================================================= // + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 120; +const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [filterLens], + fitView: true, + defaultEdge: { + labelCfg: { + autoRotate: true, + style: { + stroke: '#fff', + lineWidth: 2, + }, + }, + }, + defaultNode: { + size: 15, + color: '#5B8FF9', + style: { + lineWidth: 2, + fill: '#C6E5FF', + }, + }, + modes: { + default: ['drag-canvas'], + }, +}); + +swithButton.addEventListener('click', (e) => { + if (swithButton.value === 'Disable') { + swithButton.value = 'Enable'; + graph.removePlugin(filterLens); + } else { + swithButton.value = 'Disable'; + filterLens = new G6.EdgeFilterLens({ + ...filterConfigs, + trigger, + }); + graph.addPlugin(filterLens); + } +}); +configScaleRBy.addEventListener('change', (e) => { + filterLens.updateParams({ scaleRBy: e.target.value }); +}); +configTrigger.addEventListener('change', (e) => { + const filterLensConfigs = filterLens._cfgs; + graph.removePlugin(filterLens); + trigger = e.target.value; + filterLens = new G6.EdgeFilterLens({ + ...filterLensConfigs, + trigger, + }); + graph.addPlugin(filterLens); +}); + +fetch('https://gw.alipayobjects.com/os/bmw-prod/afe8b2a6-f691-4070-aa73-46fc07fd1171.json') + .then((res) => res.json()) + .then((data) => { + data.edges.forEach((edge) => { + edge.size = 1 + Math.random() * 3; + edge.color = edge.size > 3 ? '#FB4B4B' : '#aaa'; + edge.style = { + opacity: 0.7, + }; + edge.label = 'a'; + }); + graph.data(data); + graph.render(); + graph.getEdges().forEach((edge) => { + edge + .getContainer() + .getChildren() + .forEach((shape) => { + if (shape.get('type') === 'text') shape.set('visible', false); + }); + }); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 120); + }; diff --git a/packages/site/examples/tool/edgeFilterLens/demo/meta.json b/packages/site/examples/tool/edgeFilterLens/demo/meta.json new file mode 100644 index 0000000000..0f80d531f4 --- /dev/null +++ b/packages/site/examples/tool/edgeFilterLens/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "edgeFilter.js", + "title": { + "zh": "边过滤镜", + "en": "Edge Filter Lens" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*elTYSJwOR7MAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "edgeFilterConfig.js", + "title": { + "zh": "更多配置", + "en": "More Configs" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ZkZ4Q6JeIaQAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/tool/edgeFilterLens/index.en.md b/packages/site/examples/tool/edgeFilterLens/index.en.md new file mode 100644 index 0000000000..e9154b8c82 --- /dev/null +++ b/packages/site/examples/tool/edgeFilterLens/index.en.md @@ -0,0 +1,10 @@ +--- +title: Edge Filter Lens +order: 7 +--- + +Edge Filter Lens is a built-in components for edge filtering in G6. + +## Usage + +Edge Filter Lens is designed for edge filtering, the desired edges will be kept inside the lens while the others will be hidden. The `type` and `shouldShow` can be assigned to design the conditions. And the lens's style can be configured by `delegateStyle`. The demo below shows how to use fisheye on graph. The API can be found at [Edge Filter Lens Plugin](/en/docs/api/Plugins#edge-filter-lens). diff --git a/packages/site/examples/tool/edgeFilterLens/index.zh.md b/packages/site/examples/tool/edgeFilterLens/index.zh.md new file mode 100644 index 0000000000..2d93071b84 --- /dev/null +++ b/packages/site/examples/tool/edgeFilterLens/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 边过滤镜 +order: 7 +--- + +G6 中内置的边过滤镜组件。 + +## 使用指南 + +边过滤镜可以将关注的边保留在过滤镜范围内,其他边将在该范围内不显示。过滤的条件可以通过 `type` 和 `shouldShow` 指定。过滤镜的样式可以通过 `delegateStyle` 指定。下面 demo 演示了如何使用边过滤镜。API 参见 [边过滤镜插件](/zh/docs/api/Plugins#edge-filter-lens)。 diff --git a/packages/site/examples/tool/fisheye/API.en.md b/packages/site/examples/tool/fisheye/API.en.md new file mode 100644 index 0000000000..f8397eeb9b --- /dev/null +++ b/packages/site/examples/tool/fisheye/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/fisheye/API.zh.md b/packages/site/examples/tool/fisheye/API.zh.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/fisheye/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/fisheye/demo/fisheye.js b/packages/site/examples/tool/fisheye/demo/fisheye.js new file mode 100644 index 0000000000..a0f2c9ddb6 --- /dev/null +++ b/packages/site/examples/tool/fisheye/demo/fisheye.js @@ -0,0 +1,193 @@ +import G6 from '@antv/g6'; + +let fisheye = new G6.Fisheye({ + r: 200, + showLabel: true, +}); +const colors = [ + '#8FE9FF', + '#87EAEF', + '#FFC9E3', + '#A7C2FF', + '#FFA1E3', + '#FFE269', + '#BFCFEE', + '#FFA0C5', + '#D5FF86', +]; + +// ================= The DOMs for configurations =============== // +const graphDiv = document.getElementById('container'); + +const buttonContainer = document.createElement('div'); +buttonContainer.style.display = 'inline-block'; +buttonContainer.style.height = '35px'; +buttonContainer.style.width = '100%'; +buttonContainer.style.textAlign = 'center'; + +// clear the fisheye effect button +const clearButton = document.createElement('input'); +clearButton.type = 'button'; +clearButton.value = 'Clear'; +clearButton.style.height = '25px'; +clearButton.style.width = '60px'; +buttonContainer.appendChild(clearButton); + +// enable/disable the fisheye lens button +const swithButton = document.createElement('input'); +swithButton.type = 'button'; +swithButton.value = 'Disable'; +swithButton.style.height = '25px'; +swithButton.style.width = '60px'; +swithButton.style.marginLeft = '16px'; +buttonContainer.appendChild(swithButton); + +buttonContainer.appendChild(document.createElement('br')); + +// list for changing trigger +const trigger = document.createElement('span'); +trigger.innerHTML = 'Trigger:'; +trigger.style.marginLeft = '16px'; +buttonContainer.appendChild(trigger); +const configTrigger = document.createElement('select'); +configTrigger.value = 'mousemove'; +configTrigger.style.height = '25px'; +configTrigger.style.width = '100px'; +configTrigger.style.marginLeft = '8px'; +const mousemoveTrigger = document.createElement('option'); +mousemoveTrigger.value = 'mousemove'; +mousemoveTrigger.innerHTML = 'mousemove'; +configTrigger.appendChild(mousemoveTrigger); +const dragTrigger = document.createElement('option'); +dragTrigger.value = 'drag'; +dragTrigger.innerHTML = 'drag'; +configTrigger.appendChild(dragTrigger); +const clickTrigger = document.createElement('option'); +clickTrigger.value = 'click'; +clickTrigger.innerHTML = 'click'; +configTrigger.appendChild(clickTrigger); +buttonContainer.appendChild(configTrigger); + +// list for changing scaleRBy +const scaleR = document.createElement('span'); +scaleR.innerHTML = 'Scale r by:'; +scaleR.style.marginLeft = '16px'; +buttonContainer.appendChild(scaleR); +const configScaleRBy = document.createElement('select'); +configScaleRBy.value = 'unset'; +configScaleRBy.style.height = '25px'; +configScaleRBy.style.width = '100px'; +configScaleRBy.style.marginLeft = '8px'; +const scaleRByUnset = document.createElement('option'); +scaleRByUnset.value = 'unset'; +scaleRByUnset.innerHTML = 'unset'; +configScaleRBy.appendChild(scaleRByUnset); +const scaleRByWheel = document.createElement('option'); +scaleRByWheel.value = 'wheel'; +scaleRByWheel.innerHTML = 'wheel'; +configScaleRBy.appendChild(scaleRByWheel); +const scaleRByDrag = document.createElement('option'); +scaleRByDrag.value = 'drag'; +scaleRByDrag.innerHTML = 'drag'; +configScaleRBy.appendChild(scaleRByDrag); +buttonContainer.appendChild(configScaleRBy); + +// list for changing scaleDBy +const scaleD = document.createElement('span'); +scaleD.innerHTML = 'Scale d by:'; +scaleD.style.marginLeft = '16px'; +buttonContainer.appendChild(scaleD); +const configScaleDBy = document.createElement('select'); +configScaleDBy.value = 'unset'; +configScaleDBy.style.height = '25px'; +configScaleDBy.style.width = '100px'; +configScaleDBy.style.marginLeft = '8px'; +const scaleDByUnset = document.createElement('option'); +scaleDByUnset.value = 'unset'; +scaleDByUnset.innerHTML = 'unset'; +configScaleDBy.appendChild(scaleDByUnset); +const scaleDByWheel = document.createElement('option'); +scaleDByWheel.value = 'wheel'; +scaleDByWheel.innerHTML = 'wheel'; +configScaleDBy.appendChild(scaleDByWheel); +const scaleDByDrag = document.createElement('option'); +scaleDByDrag.value = 'drag'; +scaleDByDrag.innerHTML = 'drag'; +configScaleDBy.appendChild(scaleDByDrag); +buttonContainer.appendChild(configScaleDBy); + +graphDiv.parentNode.appendChild(buttonContainer); + +// ========================================================= // + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [fisheye], +}); + +clearButton.addEventListener('click', (e) => { + fisheye.clear(); +}); +swithButton.addEventListener('click', (e) => { + if (swithButton.value === 'Disable') { + swithButton.value = 'Enable'; + graph.removePlugin(fisheye); + } else { + swithButton.value = 'Disable'; + fisheye = new G6.Fisheye({ + r: 200, + showLabel: true, + }); + graph.addPlugin(fisheye); + } +}); +configScaleRBy.addEventListener('change', (e) => { + fisheye.updateParams({ scaleRBy: e.target.value }); +}); +configScaleDBy.addEventListener('change', (e) => { + fisheye.updateParams({ scaleDBy: e.target.value }); +}); +configTrigger.addEventListener('change', (e) => { + const fisheyConfigs = fisheye._cfgs; + graph.removePlugin(fisheye); + fisheye = new G6.Fisheye({ + ...fisheyConfigs, + trigger: e.target.value, + }); + graph.addPlugin(fisheye); +}); + +fetch('https://gw.alipayobjects.com/os/bmw-prod/afe8b2a6-f691-4070-aa73-46fc07fd1171.json') + .then((res) => res.json()) + .then((data) => { + data.nodes.forEach((node) => { + node.label = node.id; + node.size = Math.random() * 30 + 10; + node.style = { + fill: colors[Math.floor(Math.random() * 9)], + lineWidth: 0, + }; + }); + graph.data(data); + graph.render(); + graph.getNodes().forEach((node) => { + node + .getContainer() + .getChildren() + .forEach((shape) => { + if (shape.get('type') === 'text') shape.hide(); + }); + }); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/fisheye/demo/fisheyeStyle.js b/packages/site/examples/tool/fisheye/demo/fisheyeStyle.js new file mode 100644 index 0000000000..30db383001 --- /dev/null +++ b/packages/site/examples/tool/fisheye/demo/fisheyeStyle.js @@ -0,0 +1,105 @@ +import G6 from '@antv/g6'; + +let fisheye = new G6.Fisheye({ + r: 200, + showLabel: true, + delegateStyle: { + fill: '#f00', + lineDash: [5, 5], + stroke: '#666', + }, +}); +const colors = [ + '#8FE9FF', + '#87EAEF', + '#FFC9E3', + '#A7C2FF', + '#FFA1E3', + '#FFE269', + '#BFCFEE', + '#FFA0C5', + '#D5FF86', +]; + +const graphDiv = document.getElementById('container'); + +const buttonContainer = document.createElement('div'); +buttonContainer.style.display = 'inline-block'; +buttonContainer.style.height = '35px'; +buttonContainer.style.width = '220px'; + +// clear the fisheye effect button +const clearButton = document.createElement('input'); +clearButton.type = 'button'; +clearButton.value = 'Clear'; +clearButton.style.height = '25px'; +clearButton.style.width = '100px'; +buttonContainer.appendChild(clearButton); + +// enable/disable the fisheye lens button +const switchButton = document.createElement('input'); +switchButton.type = 'button'; +switchButton.value = 'Disable'; +switchButton.style.height = '25px'; +switchButton.style.width = '100px'; +switchButton.style.marginLeft = '10px'; +buttonContainer.appendChild(switchButton); + +graphDiv.parentNode.appendChild(buttonContainer); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [fisheye], +}); + +clearButton.addEventListener('click', (e) => { + fisheye.clear(); +}); +switchButton.addEventListener('click', (e) => { + if (switchButton.value === 'Disable') { + switchButton.value = 'Enable'; + graph.removePlugin(fisheye); + } else { + switchButton.value = 'Disable'; + fisheye = new G6.Fisheye({ + r: 200, + showLabel: true, + }); + graph.addPlugin(fisheye); + } +}); + +fetch('https://gw.alipayobjects.com/os/bmw-prod/afe8b2a6-f691-4070-aa73-46fc07fd1171.json') + .then((res) => res.json()) + .then((data) => { + data.nodes.forEach((node) => { + node.label = node.id; + node.size = Math.random() * 30 + 10; + node.style = { + fill: colors[Math.floor(Math.random() * 9)], + lineWidth: 0, + }; + }); + graph.data(data); + graph.render(); + graph.getNodes().forEach((node) => { + node + .getContainer() + .getChildren() + .forEach((shape) => { + if (shape.get('type') === 'text') shape.hide(); + }); + }); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/fisheye/demo/meta.json b/packages/site/examples/tool/fisheye/demo/meta.json new file mode 100644 index 0000000000..d9d61a4c15 --- /dev/null +++ b/packages/site/examples/tool/fisheye/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "fisheye.js", + "title": { + "zh": "鱼眼放大镜", + "en": "Fisheye Lens" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*kHtkQ5N4TNEAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "fisheyeStyle.js", + "title": { + "zh": "鱼眼放大镜样式", + "en": "Fisheye Lens Style" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*kHtkQ5N4TNEAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tool/fisheye/index.en.md b/packages/site/examples/tool/fisheye/index.en.md new file mode 100644 index 0000000000..9c5a5a8352 --- /dev/null +++ b/packages/site/examples/tool/fisheye/index.en.md @@ -0,0 +1,10 @@ +--- +title: Fisheye +order: 6 +--- + +Fisheye is a built-in components for focus+context magnification in G6. + +## Usage + +Fisheye is designed for focus+context exploration, it keeps the context and the relationships between context and the focus while magnifing the focus area. The demo below shows how to use fisheye on graph. And the Fisheye lens's style can be configured by `delegateStyle`. The API can be found at [Fisheye Plugin](/en/docs/api/Plugins#fisheye). diff --git a/packages/site/examples/tool/fisheye/index.zh.md b/packages/site/examples/tool/fisheye/index.zh.md new file mode 100644 index 0000000000..9047acdab7 --- /dev/null +++ b/packages/site/examples/tool/fisheye/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 鱼眼放大镜 +order: 6 +--- + +G6 中内置的 Fisheye 组件。 + +## 使用指南 + +Fisheye 鱼眼放大镜是为 focus+context 的探索场景设计的,它能够保证在放大关注区域的同时,保证上下文以及上下文与关注中心的关系不丢失。下面的代码演示展示了如何在图上使用 Fisheye。Fisheye 放大镜的样式可以通过 `delegateStyle` 配置。API 详见 [Fisheye 插件](/zh/docs/api/Plugins#fisheye)。 diff --git a/packages/site/examples/tool/legend/API.en.md b/packages/site/examples/tool/legend/API.en.md new file mode 100644 index 0000000000..f8397eeb9b --- /dev/null +++ b/packages/site/examples/tool/legend/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/legend/API.zh.md b/packages/site/examples/tool/legend/API.zh.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/legend/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/legend/demo/legend.js b/packages/site/examples/tool/legend/demo/legend.js new file mode 100644 index 0000000000..d05d2f5da8 --- /dev/null +++ b/packages/site/examples/tool/legend/demo/legend.js @@ -0,0 +1,245 @@ +import G6 from '@antv/g6' + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 50; +const el = document.createElement('pre'); +el.innerHTML = + 'Click the legend item to filter\n点击图例上的元素进行筛选'; +container.appendChild(el); + +const typeConfigs = { + 'type1': { + type: 'circle', + size: 5, + style: { + fill: '#5B8FF9' + } + }, + 'type2': { + type: 'circle', + size: 20, + style: { + fill: '#5AD8A6' + } + }, + 'type3': { + type: 'rect', + size: [10, 10], + style: { + fill: '#5D7092' + } + }, + 'eType1': { + type: 'line', + style: { + width: 20, + stroke: '#F6BD16', + } + }, + 'eType2': { + type: 'cubic', + }, + 'eType3': { + type: 'quadratic', + style: { + width: 25, + stroke: '#6F5EF9' + } + } +} + +const data = { + nodes: [ + { + id: '1', + label: '1:type1', + legendType: 'type1', + }, + { + id: '2', + label: '2:type2', + legendType: 'type2', + }, + { + id: '3', + label: '3:type1', + legendType: 'type1', + }, + { + id: '4', + label: '4:type3', + legendType: 'type3', + }, + ], + edges: [{ + source: '1', + target: '2', + legendType: 'eType1', + label: '1->2:edge-type1', + }, { + source: '1', + target: '4', + legendType: 'eType3', + label: '1->4:edge-type3' + }, { + source: '3', + target: '4' + }, { + source: '2', + target: '4', + legendType: 'eType1', + label: '2->4:edge-type1' + }] +}; + +data.nodes.forEach(node => { + if (!node.legendType) return; + node = Object.assign(node, {...typeConfigs[node.legendType]}); +}) +data.edges.forEach(edge => { + if (!edge.legendType) return; + const config = typeConfigs[edge.legendType]; + edge = Object.assign(edge, {...config}); +}) + +const legendData = { + nodes: [{ + id: 'type1', + label: 'node-type1', + order: 4, + ...typeConfigs['type1'] + }, { + id: 'type2', + label: 'node-type2', + order: 0, + ...typeConfigs['type2'] + }, { + id: 'type3', + label: 'node-type3', + order: 2, + ...typeConfigs['type3'] + }], + edges: [{ + id: 'eType1', + label: 'edge-type1', + order: 2, + ...typeConfigs['eType1'] + }, { + id: 'eType2', + label: 'edge-type2', + ...typeConfigs['eType2'] + }, { + id: 'eType3', + label: 'edge-type3', + ...typeConfigs['eType3'] + }] +} +const legend = new G6.Legend({ + data: legendData, + align: 'center', + layout: 'horizontal', // vertical + position: 'bottom-right', + vertiSep: 12, + horiSep: 24, + offsetY: -24, + padding: [4, 16, 8, 16], + containerStyle: { + fill: '#ccc', + lineWidth: 1 + }, + title: 'Legend', + titleConfig: { + position: 'center', + offsetX: 0, + offsetY: 12, + }, + filter: { + enable: true, + multiple: true, + trigger: 'click', + graphActiveState: 'activeByLegend', + graphInactiveState: 'inactiveByLegend', + filterFunctions: { + 'type1': (d) => { + if (d.legendType === 'type1') return true; + return false + }, + 'type2': (d) => { + if (d.legendType === 'type2') return true; + return false + }, + 'type3': (d) => { + if (d.legendType === 'type3') return true; + return false + }, + 'eType1': (d) => { + if (d.legendType === 'eType1') return true; + return false + }, + 'eType2': (d) => { + if (d.legendType === 'eType2') return true; + return false + }, + 'eType3': (d) => { + if (d.legendType === 'eType3') return true; + return false + }, + } + } +}); + + +const graph = new G6.Graph({ + // 使用 contextMenu plugins 时,需要将 container 设置为 position: relative; + container: 'container', + width, + height, + linkCenter: true, + defaultNode: { + labelCfg: { + position: "bottom", + style: { + stroke: '#fff', + lineWidth: 4 + } + } + }, + defaultEdge: { + labelCfg: { + autoRotate: true, + style: { + stroke: '#fff', + lineWidth: 4 + } + } + }, + nodeStateStyles: { + activeByLegend: { + lineWidth: 10, + strokeOpacity: 0.5 + }, + inactiveByLegend: { + opacity: 0.5 + } + }, + edgeStateStyles: { + activeByLegend: { + lineWidth: 3 + }, + inactiveByLegend: { + opacity: 0.5 + } + }, + plugins: [legend], +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/legend/demo/legendClick.js b/packages/site/examples/tool/legend/demo/legendClick.js new file mode 100644 index 0000000000..ab6fdb6ea7 --- /dev/null +++ b/packages/site/examples/tool/legend/demo/legendClick.js @@ -0,0 +1,609 @@ +import G6 from '@antv/g6'; + + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 50; +const el = document.createElement('pre'); +el.innerHTML = + 'Click the legend item to filter\n点击图例上的元素进行筛选'; +container.appendChild(el); + +const data = { + nodes: [ + { + id: '0', + label: '0', + cluster: 'a', + }, + { + id: '1', + label: '1', + cluster: 'a', + }, + { + id: '2', + label: '2', + cluster: 'a', + }, + { + id: '3', + label: '3', + cluster: 'a', + }, + { + id: '4', + label: '4', + cluster: 'a', + }, + { + id: '5', + label: '5', + cluster: 'a', + }, + { + id: '6', + label: '6', + cluster: 'a', + }, + { + id: '7', + label: '7', + cluster: 'a', + }, + { + id: '8', + label: '8', + cluster: 'a', + }, + { + id: '9', + label: '9', + cluster: 'a', + }, + { + id: '10', + label: '10', + cluster: 'a', + }, + { + id: '11', + label: '11', + cluster: 'a', + }, + { + id: '12', + label: '12', + cluster: 'a', + }, + { + id: '13', + label: '13', + cluster: 'b', + }, + { + id: '14', + label: '14', + cluster: 'b', + }, + { + id: '15', + label: '15', + cluster: 'b', + }, + { + id: '16', + label: '16', + cluster: 'b', + }, + { + id: '17', + label: '17', + cluster: 'b', + }, + { + id: '18', + label: '18', + cluster: 'c', + }, + { + id: '19', + label: '19', + cluster: 'c', + }, + { + id: '20', + label: '20', + cluster: 'c', + }, + { + id: '21', + label: '21', + cluster: 'c', + }, + { + id: '22', + label: '22', + cluster: 'c', + }, + { + id: '23', + label: '23', + cluster: 'c', + }, + { + id: '24', + label: '24', + cluster: 'c', + }, + { + id: '25', + label: '25', + cluster: 'c', + }, + { + id: '26', + label: '26', + cluster: 'c', + }, + { + id: '27', + label: '27', + cluster: 'c', + }, + { + id: '28', + label: '28', + cluster: 'c', + }, + { + id: '29', + label: '29', + cluster: 'c', + }, + { + id: '30', + label: '30', + cluster: 'c', + }, + { + id: '31', + label: '31', + cluster: 'd', + }, + { + id: '32', + label: '32', + cluster: 'd', + }, + { + id: '33', + label: '33', + cluster: 'd', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '0', + target: '16', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '22', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '10', + target: '24', + }, + { + source: '10', + target: '21', + }, + { + source: '10', + target: '20', + }, + { + source: '11', + target: '24', + }, + { + source: '11', + target: '22', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + { + source: '16', + target: '17', + }, + { + source: '16', + target: '18', + }, + { + source: '16', + target: '21', + }, + { + source: '16', + target: '22', + }, + { + source: '17', + target: '18', + }, + { + source: '17', + target: '20', + }, + { + source: '18', + target: '19', + }, + { + source: '19', + target: '20', + }, + { + source: '19', + target: '33', + }, + { + source: '19', + target: '22', + }, + { + source: '19', + target: '23', + }, + { + source: '20', + target: '21', + }, + { + source: '21', + target: '22', + }, + { + source: '22', + target: '24', + }, + { + source: '22', + target: '25', + }, + { + source: '22', + target: '26', + }, + { + source: '22', + target: '23', + }, + { + source: '22', + target: '28', + }, + { + source: '22', + target: '30', + }, + { + source: '22', + target: '31', + }, + { + source: '22', + target: '32', + }, + { + source: '22', + target: '33', + }, + { + source: '23', + target: '28', + }, + { + source: '23', + target: '27', + }, + { + source: '23', + target: '29', + }, + { + source: '23', + target: '30', + }, + { + source: '23', + target: '31', + }, + { + source: '23', + target: '33', + }, + { + source: '32', + target: '33', + }, + ], +}; + +const colors = [ + '#BDD2FD', + '#BDEFDB', + '#C2C8D5', + '#FBE5A2', + '#F6C3B7', + '#B6E3F5', + '#D3C6EA', + '#FFD8B8', + '#AAD8D8', + '#FFD6E7', +]; +const strokes = [ + '#5B8FF9', + '#5AD8A6', + '#5D7092', + '#F6BD16', + '#E8684A', + '#6DC8EC', + '#9270CA', + '#FF9D4D', + '#269A99', + '#FF99C3', +]; + +const clusterMap = new Map(); +let clusterId = 0; +data.nodes.forEach(function (node) { + // cluster + if (node.cluster && clusterMap.get(node.cluster) === undefined) { + clusterMap.set(node.cluster, clusterId); + clusterId++; + } + const cid = clusterMap.get(node.cluster); + if (!node.style) { + node.style = {}; + } + node.style.fill = colors[cid % colors.length]; + node.style.stroke = strokes[cid % strokes.length]; +}); + +data.edges.forEach(edge => { + edge.cluster = data.nodes[+edge.source].cluster; + const cid = clusterMap.get(edge.cluster); + console.log(edge.cluster, cid) + edge.style = { + stroke: colors[cid % colors.length] + } +}) + + +const legendData = { + nodes: [ + { id: 'a', label: 'a', }, + { id: 'b', label: 'b', }, + { id: 'c', label: 'c', }, + { id: 'd', label: 'd', } + ], edges: [ + { id: 'a', label: 'a', }, + { id: 'b', label: 'b', }, + { id: 'c', label: 'c', }, + { id: 'd', label: 'd', } + ] +} +legendData.nodes.forEach(node => { + const cid = clusterMap.get(node.id); + node.style = { + fill: colors[cid % colors.length], + stroke: strokes[cid % strokes.length] + }; +}) +legendData.edges.forEach(node => { + const cid = clusterMap.get(node.id); + node.style = { + fill: colors[cid % colors.length], + stroke: strokes[cid % strokes.length] + }; +}) +const legend = new G6.Legend({ + data: legendData, + align: 'center', + layout: 'horizontal', // vertical + position: 'bottom-left', + vertiSep: 12, + horiSep: 24, + offsetY: -24, + padding: [4, 16, 8, 16], + containerStyle: { + fill: '#ccc', + lineWidth: 1 + }, + title: 'Legend', + titleConfig: { + position: 'left', + offsetX: 0, + offsetY: 12, + }, + filter: { + enable: true, + multiple: true, + trigger: 'click', + graphActiveState: 'activeByLegend', + graphInactiveState: 'inactiveByLegend', + filterFunctions: { + 'a': (d) => { + if (d.cluster === 'a') return true; + return false + }, + 'b': (d) => { + if (d.cluster === 'b') return true; + return false + }, + 'c': (d) => { + if (d.cluster === 'c') return true; + return false + }, + 'd': (d) => { + if (d.cluster === 'd') return true; + return false + }, + } + } +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas', 'drag-node'], + }, + plugins: [legend], + animate: true, + defaultNode: { + size: 20, + style: { + lineWidth: 2, + }, + }, + defaultEdge: { + size: 1, + color: '#e2e2e2', + style: { + endArrow: { + path: 'M 0,0 L 8,4 L 8,-4 Z', + fill: '#e2e2e2', + }, + }, + }, + nodeStateStyles: { + activeByLegend: { + lineWidth: 5, + strokeOpacity: 0.5, + stroke: '#f00' + }, + inactiveByLegend: { + opacity: 0.5 + } + }, + edgeStateStyles: { + activeByLegend: { + lineWidth: 10, + strokeOpacity: 0.5 + }, + inactiveByLegend: { + opacity: 0.5 + } + }, + layout: { + type: 'gForce', + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/legend/demo/legendMouseenter.js b/packages/site/examples/tool/legend/demo/legendMouseenter.js new file mode 100644 index 0000000000..1ea5934bcc --- /dev/null +++ b/packages/site/examples/tool/legend/demo/legendMouseenter.js @@ -0,0 +1,244 @@ +import G6 from '@antv/g6' + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 50; +const el = document.createElement('pre'); +el.innerHTML = + 'Hover the legend item to filter\n鼠标进入图例上的元素进行筛选'; +container.appendChild(el); + +const typeConfigs = { + 'type1': { + type: 'circle', + size: 5, + style: { + fill: '#5B8FF9' + } + }, + 'type2': { + type: 'circle', + size: 20, + style: { + fill: '#5AD8A6' + } + }, + 'type3': { + type: 'rect', + size: [10, 10], + style: { + fill: '#5D7092' + } + }, + 'eType1': { + type: 'line', + style: { + width: 20, + stroke: '#F6BD16', + } + }, + 'eType2': { + type: 'cubic', + }, + 'eType3': { + type: 'quadratic', + style: { + width: 25, + stroke: '#6F5EF9' + } + } +} + +const data = { + nodes: [ + { + id: '1', + label: '1:type1', + legendType: 'type1', + }, + { + id: '2', + label: '2:type2', + legendType: 'type2', + }, + { + id: '3', + label: '3:type1', + legendType: 'type1', + }, + { + id: '4', + label: '4:type3', + legendType: 'type3', + }, + ], + edges: [{ + source: '1', + target: '2', + legendType: 'eType1', + label: '1->2:edge-type1', + }, { + source: '1', + target: '4', + legendType: 'eType3', + label: '1->4:edge-type3' + }, { + source: '3', + target: '4' + }, { + source: '2', + target: '4', + legendType: 'eType1', + label: '2->4:edge-type1' + }] +}; + +data.nodes.forEach(node => { + if (!node.legendType) return; + node = Object.assign(node, {...typeConfigs[node.legendType]}); +}) +data.edges.forEach(edge => { + if (!edge.legendType) return; + const config = typeConfigs[edge.legendType]; + edge = Object.assign(edge, {...config}); +}) + +const legendData = { + nodes: [{ + id: 'type1', + label: 'node-type1', + order: 4, + ...typeConfigs['type1'] + }, { + id: 'type2', + label: 'node-type2', + order: 0, + ...typeConfigs['type2'] + }, { + id: 'type3', + label: 'node-type3', + order: 2, + ...typeConfigs['type3'] + }], + edges: [{ + id: 'eType1', + label: 'edge-type1', + order: 2, + ...typeConfigs['eType1'] + }, { + id: 'eType2', + label: 'edge-type2', + ...typeConfigs['eType2'] + }, { + id: 'eType3', + label: 'edge-type3', + ...typeConfigs['eType3'] + }] +} +const legend = new G6.Legend({ + data: legendData, + align: 'center', + layout: 'horizontal', // vertical + position: 'bottom-right', + vertiSep: 12, + horiSep: 24, + offsetY: -24, + padding: [4, 16, 8, 16], + containerStyle: { + fill: '#ccc', + lineWidth: 1 + }, + title: 'Legend', + titleConfig: { + position: 'center', + offsetX: 0, + offsetY: 12, + }, + filter: { + enable: true, + trigger: 'mouseenter', + graphActiveState: 'activeByLegend', + graphInactiveState: 'inactiveByLegend', + filterFunctions: { + 'type1': (d) => { + if (d.legendType === 'type1') return true; + return false + }, + 'type2': (d) => { + if (d.legendType === 'type2') return true; + return false + }, + 'type3': (d) => { + if (d.legendType === 'type3') return true; + return false + }, + 'eType1': (d) => { + if (d.legendType === 'eType1') return true; + return false + }, + 'eType2': (d) => { + if (d.legendType === 'eType2') return true; + return false + }, + 'eType3': (d) => { + if (d.legendType === 'eType3') return true; + return false + }, + } + } +}); + + +const graph = new G6.Graph({ + // 使用 contextMenu plugins 时,需要将 container 设置为 position: relative; + container: 'container', + width, + height, + linkCenter: true, + defaultNode: { + labelCfg: { + position: "bottom", + style: { + stroke: '#fff', + lineWidth: 4 + } + } + }, + defaultEdge: { + labelCfg: { + autoRotate: true, + style: { + stroke: '#fff', + lineWidth: 4 + } + } + }, + nodeStateStyles: { + activeByLegend: { + lineWidth: 10, + strokeOpacity: 0.5 + }, + inactiveByLegend: { + opacity: 0.5 + } + }, + edgeStateStyles: { + activeByLegend: { + lineWidth: 3 + }, + inactiveByLegend: { + opacity: 0.5 + } + }, + plugins: [legend], +}); + +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/legend/demo/meta.json b/packages/site/examples/tool/legend/demo/meta.json new file mode 100644 index 0000000000..50616888a5 --- /dev/null +++ b/packages/site/examples/tool/legend/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "legend.js", + "title": { + "zh": "图例-点击筛选", + "en": "Legend with Clicking Filtering" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*0RYmToKWsOQAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "legendMouseenter.js", + "title": { + "zh": "图例-Hover 筛选", + "en": "Legend with Hovering Filtering" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ftAtR6k9RWIAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "legendClick.js", + "title": { + "zh": "图例-点击筛选2", + "en": "Legend with Clicking Filtering 2" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*nCTfRq6522kAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/tool/legend/index.en.md b/packages/site/examples/tool/legend/index.en.md new file mode 100644 index 0000000000..38b0044788 --- /dev/null +++ b/packages/site/examples/tool/legend/index.en.md @@ -0,0 +1,27 @@ +--- +title: Legend +order: 9 +--- + +Using contextMenu in G6. + +## Usage + +The demo below show how to use contextMenu on a graph. Menu's style can be defined by the CSS: + +```css +/* Define the CSS with the id of your menu */ + #contextMenu { + position: absolute; + /* ... Other styles */ + } + #contextMenu li { + cursor: pointer; + /* ... Other styles */ + } + #contextMenu li:hover { + color: #aaa; + /* ... Other styles */ + } +`); +``` diff --git a/packages/site/examples/tool/legend/index.zh.md b/packages/site/examples/tool/legend/index.zh.md new file mode 100644 index 0000000000..2705eb14c4 --- /dev/null +++ b/packages/site/examples/tool/legend/index.zh.md @@ -0,0 +1,26 @@ +--- +title: 图例 Legend +order: 9 +--- + +G6 中内置的 contextMenu。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 ContextMenu。如果需要定义菜单的样式,需要定义菜单对应标签的 CSS 样式,例如: + +```css +/* 根据菜单对应标签的 id 进行设置 */ + #contextMenu { + position: absolute; + /* ... Other styles */ + } + #contextMenu li { + cursor: pointer; + /* ... Other styles */ + } + #contextMenu li:hover { + color: #aaa; + /* ... Other styles */ + } +``` diff --git a/packages/site/examples/tool/minimap/API.en.md b/packages/site/examples/tool/minimap/API.en.md new file mode 100644 index 0000000000..f8397eeb9b --- /dev/null +++ b/packages/site/examples/tool/minimap/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/minimap/API.zh.md b/packages/site/examples/tool/minimap/API.zh.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/minimap/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/minimap/demo/imageMinimap.js b/packages/site/examples/tool/minimap/demo/imageMinimap.js new file mode 100644 index 0000000000..accfd1acb0 --- /dev/null +++ b/packages/site/examples/tool/minimap/demo/imageMinimap.js @@ -0,0 +1,79 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-minimap-container { + border: 1px solid #e2e2e2; + } + .g6-minimap-viewport { + border: 2px solid rgb(25, 128, 255); + } +`); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 110; +const minimap = new G6.ImageMinimap({ + height: 100, + padding: 10, + graphImg: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*DcGMQ7AN3Z0AAAAAAAAAAABkARQnAQ', +}); +const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + linkCenter: true, + layout: { + type: 'dendrogram', + direction: 'LR', // H / V / LR / RL / TB / BT + nodeSep: 30, + rankSep: 100, + }, + defaultNode: { + size: 26, + style: { + fill: '#C6E5FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + type: 'cubic-horizontal', + style: { + stroke: '#A3B1BF', + }, + }, + modes: { + default: ['drag-canvas', 'zoom-canvas'], + }, + plugins: [minimap], +}); + +graph.node(function (node) { + return { + label: node.id, + labelCfg: { + position: node.children && node.children.length > 0 ? 'left' : 'right', + offset: 5, + }, + }; +}); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + graph.data(data); + graph.render(); + }); + +graph.on('canvas:click', () => { + minimap.updateGraphImg( + 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*7QSRRJwAWxQAAAAAAAAAAABkARQnAQ', + ); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 110); + }; diff --git a/packages/site/examples/tool/minimap/demo/meta.json b/packages/site/examples/tool/minimap/demo/meta.json new file mode 100644 index 0000000000..bafebce2af --- /dev/null +++ b/packages/site/examples/tool/minimap/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "minimap.js", + "title": { + "zh": "小地图", + "en": "Minimap" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*r3dAQo-R3BoAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "imageMinimap.js", + "title": { + "zh": "使用图片的小地图", + "en": "Image Minimap" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*r3dAQo-R3BoAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tool/minimap/demo/minimap.js b/packages/site/examples/tool/minimap/demo/minimap.js new file mode 100644 index 0000000000..d7e2b3c329 --- /dev/null +++ b/packages/site/examples/tool/minimap/demo/minimap.js @@ -0,0 +1,224 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-minimap-container { + border: 1px solid #e2e2e2; + } + .g6-minimap-viewport { + border: 2px solid rgb(25, 128, 255); + } +`); +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 110; +const minimap = new G6.Minimap({ + size: [150, 100], +}); +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + layout: { + type: 'dagre', + rankdir: 'LR', + align: 'DL', + nodesepFunc: () => 1, + ranksepFunc: () => 1, + }, + defaultNode: { + size: [80, 40], + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: ['drag-canvas', 'zoom-canvas'], + }, + plugins: [minimap], +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 110); + }; diff --git a/packages/site/examples/tool/minimap/index.en.md b/packages/site/examples/tool/minimap/index.en.md new file mode 100644 index 0000000000..a7dda42d0f --- /dev/null +++ b/packages/site/examples/tool/minimap/index.en.md @@ -0,0 +1,19 @@ +--- +title: Minimap +order: 2 +--- + +Minimap is a built-in components in G6. + +## Usage + +The demo below show how to use Minimap on graph. Minimap's style can be defined by the CSS with class names `g6-minimap-container` abd `g6-minimap-viewport`: + +```css +.g6-minimap-container { + border: 1px solid #e2e2e2; +} +.g6-minimap-viewport { + border: 2px solid rgb(25, 128, 255); +} +``` diff --git a/packages/site/examples/tool/minimap/index.zh.md b/packages/site/examples/tool/minimap/index.zh.md new file mode 100644 index 0000000000..ee9d0f344b --- /dev/null +++ b/packages/site/examples/tool/minimap/index.zh.md @@ -0,0 +1,19 @@ +--- +title: Minimap +order: 2 +--- + +G6 中内置的 Minimap 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 Minimap。如果需要定义 Minimap 的样式,需要定义类名为 `g6-minimap-container` 与 `g6-minimap-viewport` 的标签的 CSS 样式: + +```css +.g6-minimap-container { + border: 1px solid #e2e2e2; +} +.g6-minimap-viewport { + border: 2px solid rgb(25, 128, 255); +} +``` diff --git a/packages/site/examples/tool/snapline/API.en.md b/packages/site/examples/tool/snapline/API.en.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/snapline/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/snapline/API.zh.md b/packages/site/examples/tool/snapline/API.zh.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/snapline/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/snapline/demo/custom.js b/packages/site/examples/tool/snapline/demo/custom.js new file mode 100644 index 0000000000..3b6d1a684b --- /dev/null +++ b/packages/site/examples/tool/snapline/demo/custom.js @@ -0,0 +1,202 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 110; +const snapLine = new G6.SnapLine({ + line: { + stroke: 'red', + lineWidth: 3, + }, + itemAlignType: true, +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + defaultNode: { + size: 30, + }, + modes: { + default: ['drag-canvas', 'zoom-canvas', 'drag-node'], + }, + plugins: [snapLine], +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 110); + }; diff --git a/packages/site/examples/tool/snapline/demo/default.js b/packages/site/examples/tool/snapline/demo/default.js new file mode 100644 index 0000000000..a67948e044 --- /dev/null +++ b/packages/site/examples/tool/snapline/demo/default.js @@ -0,0 +1,196 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { + id: '0', + label: '0', + }, + { + id: '1', + label: '1', + }, + { + id: '2', + label: '2', + }, + { + id: '3', + label: '3', + }, + { + id: '4', + label: '4', + }, + { + id: '5', + label: '5', + }, + { + id: '6', + label: '6', + }, + { + id: '7', + label: '7', + }, + { + id: '8', + label: '8', + }, + { + id: '9', + label: '9', + }, + { + id: '10', + label: '10', + }, + { + id: '11', + label: '11', + }, + { + id: '12', + label: '12', + }, + { + id: '13', + label: '13', + }, + { + id: '14', + label: '14', + }, + { + id: '15', + label: '15', + }, + ], + edges: [ + { + source: '0', + target: '1', + }, + { + source: '0', + target: '2', + }, + { + source: '0', + target: '3', + }, + { + source: '0', + target: '4', + }, + { + source: '0', + target: '5', + }, + { + source: '0', + target: '7', + }, + { + source: '0', + target: '8', + }, + { + source: '0', + target: '9', + }, + { + source: '0', + target: '10', + }, + { + source: '0', + target: '11', + }, + { + source: '0', + target: '13', + }, + { + source: '0', + target: '14', + }, + { + source: '0', + target: '15', + }, + { + source: '2', + target: '3', + }, + { + source: '4', + target: '5', + }, + { + source: '4', + target: '6', + }, + { + source: '5', + target: '6', + }, + { + source: '7', + target: '13', + }, + { + source: '8', + target: '14', + }, + { + source: '9', + target: '10', + }, + { + source: '10', + target: '14', + }, + { + source: '10', + target: '12', + }, + { + source: '11', + target: '14', + }, + { + source: '12', + target: '13', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 110; +const snapLine = new G6.SnapLine(); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + defaultNode: { + size: 30, + }, + modes: { + default: ['drag-canvas', 'zoom-canvas', 'drag-node'], + }, + plugins: [snapLine], +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 110); + }; diff --git a/packages/site/examples/tool/snapline/demo/meta.json b/packages/site/examples/tool/snapline/demo/meta.json new file mode 100644 index 0000000000..3e1c012084 --- /dev/null +++ b/packages/site/examples/tool/snapline/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "default.js", + "title": { + "zh": "对齐线", + "en": "SnapLine" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*fRMFSZCjuoMAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "custom.js", + "title": { + "zh": "自定义对齐线样式", + "en": "Custom SnapLine" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*G-vPSa3I1lkAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/tool/snapline/index.en.md b/packages/site/examples/tool/snapline/index.en.md new file mode 100644 index 0000000000..132cb9df7b --- /dev/null +++ b/packages/site/examples/tool/snapline/index.en.md @@ -0,0 +1,6 @@ +--- +title: SnapLine +order: 8 +--- + +SnapLine is a built-in components in G6. diff --git a/packages/site/examples/tool/snapline/index.zh.md b/packages/site/examples/tool/snapline/index.zh.md new file mode 100644 index 0000000000..4f9a86bf83 --- /dev/null +++ b/packages/site/examples/tool/snapline/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 对齐线 +order: 8 +--- + +G6 中内置的对齐线组件。 diff --git a/packages/site/examples/tool/timebar/API.en.md b/packages/site/examples/tool/timebar/API.en.md new file mode 100644 index 0000000000..f8397eeb9b --- /dev/null +++ b/packages/site/examples/tool/timebar/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/timebar/API.zh.md b/packages/site/examples/tool/timebar/API.zh.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/timebar/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/timebar/demo/meta.json b/packages/site/examples/tool/timebar/demo/meta.json new file mode 100644 index 0000000000..c9d6561d71 --- /dev/null +++ b/packages/site/examples/tool/timebar/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "timebar.js", + "title": { + "zh": "趋势时间轴", + "en": "TrendTimeBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*lfvIQJYbs7oAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "simple-timebar.js", + "title": { + "zh": "简易时间轴", + "en": "SimperTimeBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*g2zhQqP6ruYAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "slice-timebar.js", + "title": { + "zh": "刻度时间轴", + "en": "SliceTimeBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*n6ECQ7Jn5pQAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/packages/site/examples/tool/timebar/demo/simple-timebar.js b/packages/site/examples/tool/timebar/demo/simple-timebar.js new file mode 100644 index 0000000000..c76e0fe687 --- /dev/null +++ b/packages/site/examples/tool/timebar/demo/simple-timebar.js @@ -0,0 +1,110 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [], + edges: [], +}; + +for (let i = 0; i < 100; i++) { + const id = `node-${i}`; + data.nodes.push({ + id, + date: `2020${i}`, + value: Math.round(Math.random() * 300), + }); + + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}`, + }); +} + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 100; + +const timeBarData = []; + +const nodeSize = 20; + +for (let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300), + }); +} + +const timebar = new G6.TimeBar({ + x: 0, + y: 0, + width, + height: 150, + padding: 10, + type: 'simple', + trend: { + data: timeBarData, + }, +}); + +// constrained the layout inside the area +const constrainBox = { x: 10, y: 10, width: 580, height: 450 }; + +const onTick = () => { + let minx = 99999999; + let maxx = -99999999; + let miny = 99999999; + let maxy = -99999999; + data.nodes.forEach((node) => { + if (minx > node.x) { + minx = node.x; + } + if (maxx < node.x) { + maxx = node.x; + } + if (miny > node.y) { + miny = node.y; + } + if (maxy < node.y) { + maxy = node.y; + } + }); + const scalex = (constrainBox.width - nodeSize / 2) / (maxx - minx); + const scaley = (constrainBox.height - nodeSize / 2) / (maxy - miny); + data.nodes.forEach((node) => { + node.x = (node.x - minx) * scalex + constrainBox.x; + node.y = (node.y - miny) * scaley + constrainBox.y; + }); +}; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [timebar], + layout: { + type: 'force', + preventOverlap: true, + onTick, + }, + defaultNode: { + size: nodeSize, + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + modes: { + default: ['drag-node'], + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 100); + }; diff --git a/packages/site/examples/tool/timebar/demo/slice-timebar.js b/packages/site/examples/tool/timebar/demo/slice-timebar.js new file mode 100644 index 0000000000..6da96589d6 --- /dev/null +++ b/packages/site/examples/tool/timebar/demo/slice-timebar.js @@ -0,0 +1,131 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [], + edges: [], +}; + +for (let i = 1; i < 60; i++) { + const id = `node-${i}`; + const month = i < 30 ? '01' : '02'; + const day = i % 30 < 10 ? `0${i % 30}` : `${i % 30}`; + data.nodes.push({ + id, + date: parseInt(`2020${month}${day}`), + value: Math.round(Math.random() * 300), + label: parseInt(`2020${i}`), + }); + + data.edges.push({ + source: `node-${Math.round(Math.random() * 60)}`, + target: `node-${Math.round(Math.random() * 60)}`, + }); +} + +const timeBarData = []; + +for (let i = 1; i < 60; i++) { + const month = i < 30 ? '01' : '02'; + const day = i % 30 < 10 ? `0${i % 30}` : `${i % 30}`; + timeBarData.push({ + date: parseInt(`2020${month}${day}`), + value: Math.round(Math.random() * 300), + }); +} + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 100; + +const nodeSize = 20; + +let count = 0; +const timebar = new G6.TimeBar({ + x: 0, + y: 0, + width, + height: 150, + padding: 10, + type: 'tick', + tick: { + data: timeBarData, + width: width, + height: 42, + padding: 2, + tickLabelFormatter: (d) => { + count++; + const dateStr = `${d.date}`; + if ((count - 1) % 10 === 0) { + return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`; + } + return false; + }, + tooltipFomatter: (d) => { + const dateStr = `${d}`; + return `${dateStr.substr(0, 4)}-${dateStr.substr(4, 2)}-${dateStr.substr(6, 2)}`; + }, + }, +}); + +// constrained the layout inside the area +const constrainBox = { x: 10, y: 10, width: 580, height: 450 }; + +const onTick = () => { + let minx = 99999999; + let maxx = -99999999; + let miny = 99999999; + let maxy = -99999999; + data.nodes.forEach((node) => { + if (minx > node.x) { + minx = node.x; + } + if (maxx < node.x) { + maxx = node.x; + } + if (miny > node.y) { + miny = node.y; + } + if (maxy < node.y) { + maxy = node.y; + } + }); + const scalex = (constrainBox.width - nodeSize / 2) / (maxx - minx); + const scaley = (constrainBox.height - nodeSize / 2) / (maxy - miny); + data.nodes.forEach((node) => { + node.x = (node.x - minx) * scalex + constrainBox.x; + node.y = (node.y - miny) * scaley + constrainBox.y; + }); +}; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [timebar], + layout: { + type: 'force', + preventOverlap: true, + onTick, + }, + defaultNode: { + size: nodeSize, + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + modes: { + default: ['drag-node'], + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 100); + }; diff --git a/packages/site/examples/tool/timebar/demo/timebar.js b/packages/site/examples/tool/timebar/demo/timebar.js new file mode 100644 index 0000000000..d4adf75507 --- /dev/null +++ b/packages/site/examples/tool/timebar/demo/timebar.js @@ -0,0 +1,110 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [], + edges: [], +}; + +for (let i = 0; i < 100; i++) { + const id = `node-${i}`; + data.nodes.push({ + id, + date: `2020${i}`, + value: Math.round(Math.random() * 300), + }); + + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}`, + }); +} + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 100; +const timeBarData = []; + +const nodeSize = 20; + +for (let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300), + }); +} + +const timebar = new G6.TimeBar({ + x: 0, + y: 0, + width, + height: 150, + padding: 10, + type: 'trend', + trend: { + data: timeBarData, + }, +}); + +// constrained the layout inside the area +const constrainBox = { x: 10, y: 10, width: 580, height: 450 }; + +const onTick = () => { + let minx = 99999999; + let maxx = -99999999; + let miny = 99999999; + let maxy = -99999999; + let maxsize = -9999999; + data.nodes.forEach((node) => { + if (minx > node.x) { + minx = node.x; + } + if (maxx < node.x) { + maxx = node.x; + } + if (miny > node.y) { + miny = node.y; + } + if (maxy < node.y) { + maxy = node.y; + } + }); + const scalex = (constrainBox.width - nodeSize / 2) / (maxx - minx); + const scaley = (constrainBox.height - nodeSize / 2) / (maxy - miny); + data.nodes.forEach((node) => { + node.x = (node.x - minx) * scalex + constrainBox.x; + node.y = (node.y - miny) * scaley + constrainBox.y; + }); +}; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [timebar], + layout: { + type: 'force', + preventOverlap: true, + onTick, + }, + defaultNode: { + size: nodeSize, + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + modes: { + default: ['drag-node'], + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 100); + }; diff --git a/packages/site/examples/tool/timebar/index.en.md b/packages/site/examples/tool/timebar/index.en.md new file mode 100644 index 0000000000..af93ac8e8b --- /dev/null +++ b/packages/site/examples/tool/timebar/index.en.md @@ -0,0 +1,10 @@ +--- +title: TimeBar +order: 5 +--- + +TimeBar is a build-in component in G6. + +## Usage + +The demo below shows how to use TimeBar on graph. The second demo demonstrates the configurations for the TimeBar. diff --git a/packages/site/examples/tool/timebar/index.zh.md b/packages/site/examples/tool/timebar/index.zh.md new file mode 100644 index 0000000000..f8d15a631f --- /dev/null +++ b/packages/site/examples/tool/timebar/index.zh.md @@ -0,0 +1,52 @@ +--- +title: 时间轴 +order: 5 +--- + +[AntV G6](https://github.com/antvis/G6) 内置了三种形态的 TimeBar 组件: + +- 带有趋势图的 TimeBar 组件; +- 简易版的 TimeBar 组件; +- 刻度 TimeBar 组件。 + +并且每种类型的 TimeBar 组件都可以配合播放、快进、后退等控制按钮组使用。 + + +
趋势图 TimeBar 组件
+ + +
简易版 TimeBar 组件
+ + +
刻度 TimeBar 组件
+ +
在趋势图 TimeBar 基础上,我们可以通过配置数据,实现更加复杂的趋势图 TimeBar 组件,如下图所示。 + + + +
虽然 G6 提供了各种不同类型的 TimeBar 组件,但在使用的方式却非常简单,通过配置字段就可以进行区分。
+ +## 使用指南 + +使用 G6 内置的 TimeBar 组件,和使用其他组件的方式完全相同。 + +```javascript +import G6 from '@antv/g6'; + +const timebar = new G6.TimeBar({ + width: 500, + height: 150, + padding: 10, + type: 'trend', + trend: { + data: timeBarData, + }, +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + plugins: [timebar], +}); +``` diff --git a/packages/site/examples/tool/toolbar/API.en.md b/packages/site/examples/tool/toolbar/API.en.md new file mode 100644 index 0000000000..f8397eeb9b --- /dev/null +++ b/packages/site/examples/tool/toolbar/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/toolbar/API.zh.md b/packages/site/examples/tool/toolbar/API.zh.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/toolbar/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/toolbar/demo/meta.json b/packages/site/examples/tool/toolbar/demo/meta.json new file mode 100644 index 0000000000..fcba7f753a --- /dev/null +++ b/packages/site/examples/tool/toolbar/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "toolbar.js", + "title": { + "zh": "工具栏", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*MhpmS68lZW0AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "self-toolbar.js", + "title": { + "zh": "自定义工具栏", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ljOiTJAuQnIAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tool/toolbar/demo/self-toolbar.js b/packages/site/examples/tool/toolbar/demo/self-toolbar.js new file mode 100644 index 0000000000..786dd1c197 --- /dev/null +++ b/packages/site/examples/tool/toolbar/demo/self-toolbar.js @@ -0,0 +1,143 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-toolbar-ul { + position: absolute; + top: 70px; + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + width: 100px; + cursor: pointer; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const toolbar = new G6.ToolBar({ + // container: tc, + className: 'g6-toolbar-ul', + getContent: () => { + return ` +
    +
  • 增加节点
  • +
  • 撤销
  • +
  • 回退
  • +
+ `; + }, + handleClick: (code, graph) => { + if (code === 'add') { + graph.addItem('node', { + id: 'node2', + label: 'node2', + x: 300, + y: 150, + }); + } else if (code === 'undo') { + toolbar.undo(); + } else if (code === 'redo') { + toolbar.redo(); + } + }, +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + // 设置为true,启用 redo & undo 栈功能 + enabledStack: true, + plugins: [toolbar], + defaultNode: { + size: [80, 40], + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: ['drag-node'], + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/toolbar/demo/toolbar.js b/packages/site/examples/tool/toolbar/demo/toolbar.js new file mode 100644 index 0000000000..97ed1a9b82 --- /dev/null +++ b/packages/site/examples/tool/toolbar/demo/toolbar.js @@ -0,0 +1,109 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-component-toolbar li { + list-style-type: none !important; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const toolbar = new G6.ToolBar({ + position: { x: 10, y: 10 }, +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [toolbar], + // 设置为true,启用 redo & undo 栈功能 + enabledStack: true, + defaultNode: { + size: [80, 40], + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: ['drag-node'], + }, +}); +graph.data(data); +graph.render(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/toolbar/index.en.md b/packages/site/examples/tool/toolbar/index.en.md new file mode 100644 index 0000000000..8256d984eb --- /dev/null +++ b/packages/site/examples/tool/toolbar/index.en.md @@ -0,0 +1,16 @@ +--- +title: ToolBar +order: 4 +--- + +ToolBar is a build-in Component in G6. + +## Usage + +The demo below show how to use toolbar on graph. Toolbar's style can be defined by the CSS with class name `g6-component-toolbar`: + +```css +.g6-component-toolbar { + // css styles + } +``` diff --git a/packages/site/examples/tool/toolbar/index.zh.md b/packages/site/examples/tool/toolbar/index.zh.md new file mode 100644 index 0000000000..6c9fe587be --- /dev/null +++ b/packages/site/examples/tool/toolbar/index.zh.md @@ -0,0 +1,16 @@ +--- +title: 工具栏 +order: 4 +--- + +G6 中内置的 ToolBar 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 ToolBar。ToolBar 的样式可以使用 G6 内置的,也可以完全自定义 ToolBar 的内容,要修改内置 ToolBar 的样式,只需要修改 g6-component-toolbar 的样式: + +```css +.g6-component-toolbar { + // 自定义 CSS 内容 + } +``` diff --git a/packages/site/examples/tool/tooltip/API.en.md b/packages/site/examples/tool/tooltip/API.en.md new file mode 100644 index 0000000000..f8397eeb9b --- /dev/null +++ b/packages/site/examples/tool/tooltip/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/tooltip/API.zh.md b/packages/site/examples/tool/tooltip/API.zh.md new file mode 100644 index 0000000000..e721fcb82d --- /dev/null +++ b/packages/site/examples/tool/tooltip/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tool/tooltip/demo/meta.json b/packages/site/examples/tool/tooltip/demo/meta.json new file mode 100644 index 0000000000..7cab051865 --- /dev/null +++ b/packages/site/examples/tool/tooltip/demo/meta.json @@ -0,0 +1,48 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "tooltipPlugin.js", + "title": { + "zh": "提示框插件", + "en": "Tooltip Plugin" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*kjsSQ5pgVmoAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "tooltipFix.js", + "title": { + "zh": "固定提示框位置", + "en": "Tooltip Plugin with fixToNode" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*kjsSQ5pgVmoAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "tooltipPluginLocal.js", + "title": { + "zh": "提示框插件局部生效", + "en": "Local Tooltip Plugin" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*kjsSQ5pgVmoAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "tooltipClick.js", + "title": { + "zh": "点击出现提示框", + "en": "Tooltip Plugin with Click Trigger" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*kjsSQ5pgVmoAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "tooltipLocalCustom.js", + "title": { + "zh": "局部图形响应提示框 behavior-自定义节点", + "en": "Local Tooltip Behavor" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*bIHUToKq6OYAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tool/tooltip/demo/tooltipClick.js b/packages/site/examples/tool/tooltip/demo/tooltipClick.js new file mode 100644 index 0000000000..1eab5be6f1 --- /dev/null +++ b/packages/site/examples/tool/tooltip/demo/tooltipClick.js @@ -0,0 +1,131 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-component-tooltip { + background-color: rgba(255, 255, 255, 0.8); + padding: 0px 10px 24px 10px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const tooltip = new G6.Tooltip({ + offsetX: 10, + offsetY: 10, + // v4.2.1 起支持配置 trigger,click 代表点击后出现 tooltip。默认为 mouseenter + trigger: 'click', + // the types of items that allow the tooltip show up + // 允许出现 tooltip 的 item 类型 + itemTypes: ['node', 'edge'], + // custom the tooltip's content + // 自定义 tooltip 内容 + getContent: (e) => { + const outDiv = document.createElement('div'); + outDiv.style.width = 'fit-content'; + //outDiv.style.padding = '0px 0px 20px 0px'; + outDiv.innerHTML = ` +

Custom Content

+
    +
  • Type: ${e.item.getType()}
  • +
+
    +
  • Label: ${e.item.getModel().label || e.item.getModel().id}
  • +
`; + return outDiv; + }, +}); +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [tooltip], + modes: { + default: ['drag-node'], + }, + defaultNode: { + size: [80, 40], + type: 'rect', + }, +}); +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); +graph.on('node:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); +graph.on('edge:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); +graph.on('edge:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/tooltip/demo/tooltipFix.js b/packages/site/examples/tool/tooltip/demo/tooltipFix.js new file mode 100644 index 0000000000..b44b7b6ac8 --- /dev/null +++ b/packages/site/examples/tool/tooltip/demo/tooltipFix.js @@ -0,0 +1,131 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-component-tooltip { + background-color: rgba(255, 255, 255, 0.8); + padding: 0px 10px 24px 10px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const tooltip = new G6.Tooltip({ + offsetX: 10, + offsetY: 0, + // v4.2.1 起支持 fixToNode,tooltip 相对于节点固定位置 + fixToNode: [1, 0], + // the types of items that allow the tooltip show up + // 允许出现 tooltip 的 item 类型 + itemTypes: ['node', 'edge'], + // custom the tooltip's content + // 自定义 tooltip 内容 + getContent: (e) => { + const outDiv = document.createElement('div'); + outDiv.style.width = 'fit-content'; + //outDiv.style.padding = '0px 0px 20px 0px'; + outDiv.innerHTML = ` +

Custom Content

+
    +
  • Type: ${e.item.getType()}
  • +
+
    +
  • Label: ${e.item.getModel().label || e.item.getModel().id}
  • +
`; + return outDiv; + }, +}); +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [tooltip], + modes: { + default: ['drag-node'], + }, + defaultNode: { + size: [80, 40], + type: 'rect', + }, +}); +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); +graph.on('node:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); +graph.on('edge:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); +graph.on('edge:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/tooltip/demo/tooltipLocalCustom.js b/packages/site/examples/tool/tooltip/demo/tooltipLocalCustom.js new file mode 100644 index 0000000000..1210d0a68f --- /dev/null +++ b/packages/site/examples/tool/tooltip/demo/tooltipLocalCustom.js @@ -0,0 +1,174 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-tooltip { + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +`); + +G6.registerNode( + 'rNode', + { + drawShape: (cfg, group) => { + const rect = group.addShape('rect', { + attrs: { + x: -cfg.size[0] / 2, + y: -cfg.size[1] / 2, + width: cfg.size[0], + height: cfg.size[1], + ...cfg.style, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', + }); + const responseRect = group.addShape('rect', { + attrs: { + width: 30, + height: 15, + x: -cfg.size[0] / 2 + 5, + y: -cfg.size[1] / 2 + 5, + fill: '#fff', + stroke: cfg.style.stroke, + lineWidth: 1, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'tooltip-response-shape', + }); + const responseText = group.addShape('text', { + attrs: { + text: 'hover here!', + fill: '#f00', + x: -cfg.size[0] / 2 + 10, + y: -cfg.size[1] / 2 + 10, + textBaseline: 'top', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'tooltip-response-text-shape', + }); + const textBBox = responseText.getBBox(); + responseRect.attr({ + width: textBBox.width + 10, + height: textBBox.height + 10, + }); + return rect; + }, + }, + 'rect', +); +const data = { + nodes: [ + { + id: '0', + label: 'A', + x: 220, + y: 100, + description: 'This is node A.', + subdescription: 'This is subdescription of node A.', + }, + { + id: '2', + label: 'No Tooltip', + x: 150, + y: 310, + description: 'This is node B.', + subdescription: 'This is subdescription of node B.', + }, + { + id: '3', + label: 'C', + x: 320, + y: 310, + description: 'This is node C.', + subdescription: 'This is subdescription of node C.', + }, + ], + edges: [ + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = 'Hover the sub rect with red border to show the tooltip.'; +const container = document.getElementById('container'); +container.appendChild(descriptionDiv); + +const width = container.scrollWidth; +const height = (container.scrollHeight || 500) - 30; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + defaultNode: { + size: [100, 100], + type: 'rNode', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node', + { + type: 'tooltip', + formatText: function formatText(model) { + const text = 'description: ' + model.description; + return text; + }, + offset: 30, + shouldBegin: (e) => { + if (e.item.getModel().id === '2') return false; + const target = e.target; + if (target.get('name') === 'tooltip-response-text-shape') return true; + return false; + }, + }, + ], + }, +}); +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); +graph.on('node:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); +graph.on('edge:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); +graph.on('edge:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight - 30); + }; diff --git a/packages/site/examples/tool/tooltip/demo/tooltipPlugin.js b/packages/site/examples/tool/tooltip/demo/tooltipPlugin.js new file mode 100644 index 0000000000..e41713f8d4 --- /dev/null +++ b/packages/site/examples/tool/tooltip/demo/tooltipPlugin.js @@ -0,0 +1,136 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-component-tooltip { + background-color: rgba(255, 255, 255, 0.8); + padding: 0px 10px 24px 10px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const tooltip = new G6.Tooltip({ + offsetX: 10, + offsetY: 10, + // the types of items that allow the tooltip show up + // 允许出现 tooltip 的 item 类型 + itemTypes: ['node', 'edge'], + // custom the tooltip's content + // 自定义 tooltip 内容 + getContent: (e) => { + const outDiv = document.createElement('div'); + outDiv.style.width = 'fit-content'; + //outDiv.style.padding = '0px 0px 20px 0px'; + outDiv.innerHTML = ` +

Custom Content

+
    +
  • Type: ${e.item.getType()}
  • +
+
    +
  • Label: ${e.item.getModel().label || e.item.getModel().id}
  • +
`; + return outDiv; + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [tooltip], + modes: { + default: ['drag-node'], + }, + defaultNode: { + size: [80, 40], + type: 'rect', + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, +}); +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); +graph.on('node:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); +graph.on('edge:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); +graph.on('edge:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/tooltip/demo/tooltipPluginLocal.js b/packages/site/examples/tool/tooltip/demo/tooltipPluginLocal.js new file mode 100644 index 0000000000..cd5e968ded --- /dev/null +++ b/packages/site/examples/tool/tooltip/demo/tooltipPluginLocal.js @@ -0,0 +1,151 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-component-tooltip { + background-color: rgba(255, 255, 255, 0.8); + padding: 0px 10px 24px 10px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'Has Tooltip - node0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'No Tooltip - node1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'Tooltip on Text - node2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'Tooltip on KeyShape - node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const tooltip = new G6.Tooltip({ + offsetX: 10, + offsetY: 10, + // the types of items that allow the tooltip show up + // 允许出现 tooltip 的 item 类型 + itemTypes: ['node', 'edge'], + // custom the tooltip's content + // 自定义 tooltip 内容 + getContent: (e) => { + const outDiv = document.createElement('div'); + outDiv.style.width = 'fit-content'; + //outDiv.style.padding = '0px 0px 20px 0px'; + outDiv.innerHTML = ` +

Custom Content

+
    +
  • Type: ${e.item.getType()}
  • +
+
    +
  • Label: ${e.item.getModel().label || e.item.getModel().id}
  • +
`; + return outDiv; + }, + shouldBegin: (e) => { + console.log(e.target); + let res = true; + switch (e.item.getModel().id) { + case '1': + res = false; + break; + case '2': + if (e.target.get('name') === 'text-shape') res = true; + else res = false; + break; + case '3': + if (e.target.get('name') !== 'text-shape') res = true; + else res = false; + break; + default: + res = true; + break; + } + return res; + }, +}); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [tooltip], + modes: { + default: ['drag-node'], + }, + defaultNode: { + size: [80, 40], + type: 'rect', + }, +}); +graph.data(data); +graph.render(); + +graph.on('node:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); +graph.on('node:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); +graph.on('edge:mouseenter', (e) => { + graph.setItemState(e.item, 'active', true); +}); +graph.on('edge:mouseleave', (e) => { + graph.setItemState(e.item, 'active', false); +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tool/tooltip/index.en.md b/packages/site/examples/tool/tooltip/index.en.md new file mode 100644 index 0000000000..f6981cdb8c --- /dev/null +++ b/packages/site/examples/tool/tooltip/index.en.md @@ -0,0 +1,25 @@ +--- +title: Tooltip +order: 0 +--- + +G6 has two type of Tooltip, plugin and behavior. We recommand you to use the Tooltip Plugin since the Tooltip behavior will be discarded in v4.0. + +## Usage + +The first demo below shows the Tooltip plugin. Instantiate the Tooltip plugin and assign it to the `plugins` of the graph. Notice that the `offsetX` and `offsetY` can be used to adjust the offset of the tooltip to the mouse, the padding of the parent container should be counted into them, e.g. the padding of the parent container in this demo is `24px 16px`, so we set the `offsetX` and `offsetY` tobe `16 + 10` and `24 + 10` respectively. The Tooltip's style can be defined by the CSS with class name `g6-component-tooltip` as below. For more detail, please refer to [Tooltip Plugin API](/en/docs/api/Plugins#tooltip). + +The second to the forth demo below show the Tooltip behavior. Tooltip's style can be defined by the CSS with class name `g6-tooltip` as below. For more detail, please refer to [Tooltip Behavior](/en/docs/manual/middle/states/defaultBehavior#tooltip). + +```css +// change the 'g6-tooltip' tobe 'g6-component-tooltip' for plugin type Tooltip +.g6-tooltip { + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +``` diff --git a/packages/site/examples/tool/tooltip/index.zh.md b/packages/site/examples/tool/tooltip/index.zh.md new file mode 100644 index 0000000000..ae167a9b01 --- /dev/null +++ b/packages/site/examples/tool/tooltip/index.zh.md @@ -0,0 +1,25 @@ +--- +title: 提示框 +order: 0 +--- + +G6 提供了两种 Tooltip,分别插件和 behavior。Tooltip behavior 即将在 v4.0 中废除,推荐使用插件 Tooltip。 + +## 使用指南 + +下面的第一个示例是插件 Tooltip。将实例化的 Tooltip 配置到图的 `plugins` 中即可。需要注意的是 `offsetX` 与 `offsetY` 用于调整 tooltip 的偏移,需要考虑父容器的 `padding` 值。如在本例中,画布上层容器有 `24px 16px` 的 padding,则设置 tooltip 的 `offsetX` 与 `offsetY` 分别为 `16 + 10` 与 `24 + 10`。若需要定义 tooltip 的样式,需要定义类名为 `g6-component-tooltip` 的标签的 CSS 样式,见下方。使用方法详见 [Tooltip 插件 API](/zh/docs/api/Plugins#tooltip)。 + +下面第二~第四个示例展示了 tooltip behavior。若需要定义 tooltip 的样式,需要定义类名为 `g6-tooltip` 的标签的 CSS 样式,见下方。使用方法详见 [Tooltip Behavior](/zh/docs/manual/middle/states/defaultBehavior#tooltip)。 + +```css +// 使用 Tooltip 插件,请将 'g6-tooltip' 替换为 'g6-component-tooltip' +.g6-tooltip { + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + background-color: rgba(255, 255, 255, 0.8); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + } +``` diff --git a/packages/site/examples/tree/compactBox/API.en.md b/packages/site/examples/tree/compactBox/API.en.md new file mode 100644 index 0000000000..2d6094cb87 --- /dev/null +++ b/packages/site/examples/tree/compactBox/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tree/compactBox/API.zh.md b/packages/site/examples/tree/compactBox/API.zh.md new file mode 100644 index 0000000000..0c8200c973 --- /dev/null +++ b/packages/site/examples/tree/compactBox/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tree/compactBox/demo/basicCompactBox.js b/packages/site/examples/tree/compactBox/demo/basicCompactBox.js new file mode 100644 index 0000000000..61b69dff7a --- /dev/null +++ b/packages/site/examples/tree/compactBox/demo/basicCompactBox.js @@ -0,0 +1,78 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.getModel(); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'cubic-horizontal', + }, + layout: { + type: 'compactBox', + direction: 'LR', + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + getVGap: function getVGap() { + return 10; + }, + getHGap: function getHGap() { + return 100; + }, + }, + }); + + graph.node(function (node) { + return { + label: node.id, + labelCfg: { + offset: 10, + position: node.children && node.children.length > 0 ? 'left' : 'right', + }, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/compactBox/demo/compactBoxLeftAlign.js b/packages/site/examples/tree/compactBox/demo/compactBoxLeftAlign.js new file mode 100644 index 0000000000..6942fda558 --- /dev/null +++ b/packages/site/examples/tree/compactBox/demo/compactBoxLeftAlign.js @@ -0,0 +1,130 @@ +import G6 from '@antv/g6'; + +const fontSize = 15; + +G6.registerNode('crect', { + draw: (cfg, group) => { + const width = cfg.id.length * 10; + const rect = group.addShape('rect', { + attrs: { + x: 0, + y: -10, + ...cfg.style, + width, + height: 20, + lineWidth: 0, + opacity: 0, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', + draggable: true, + }); + const label = group.addShape('text', { + attrs: { + text: cfg.id, + fill: '#ccc', + fontSize, + x: 0, + y: 0, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'label-shape', + draggable: true, + }); + const labelBBox = label.getBBox(); + const icon = group.addShape('circle', { + attrs: { + x: labelBBox.maxX + 10, + y: (labelBBox.minY + labelBBox.maxY) / 2, + r: 5, + stroke: '#000', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'circle-shape', + draggable: true, + }); + const bboxWidth = label.getBBox().width + 20; + rect.attr({ width: bboxWidth }); + + group.addShape('path', { + attrs: { + lineWidth: 1, + fill: '#ccc', + stroke: '#ccc', + path: [ + ['M', 0, 0], + ['L', bboxWidth, 0], + ], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'path-shape', + draggable: true, + }); + + return rect; + }, + getAnchorPoints: (type, cfg) => { + return [ + [0, 0.5], + [1, 0.5], + ]; + }, +}); + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const descriptionDiv = document.createElement('div'); + descriptionDiv.innerHTML = 'The nodes of a subtree is aligned to the left.'; + container.appendChild(descriptionDiv); + + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: ['collapse-expand', 'drag-canvas', 'zoom-canvas'], + }, + defaultNode: { + type: 'crect', + }, + defaultEdge: { + type: 'cubic-horizontal', + style: { + stroke: '#A3B1BF', + }, + }, + layout: { + type: 'compactBox', + direction: 'LR', + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getVGap: function getVGap() { + return 10; + }, + getHGap: function getHGap() { + return 100; + }, + getWidth: function getWidth(d) { + return G6.Util.getTextSize(d.id, fontSize)[0] + 20; + }, + }, + fitView: true, + }); + graph.data(data); + graph.render(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/compactBox/demo/meta.json b/packages/site/examples/tree/compactBox/demo/meta.json new file mode 100644 index 0000000000..6ca59ef0f0 --- /dev/null +++ b/packages/site/examples/tree/compactBox/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicCompactBox.js", + "title": { + "zh": "紧凑树", + "en": "CompactBox Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*XlXOR5pmM3oAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "tbCompactBox.js", + "title": { + "zh": "至上而下的紧凑树", + "en": "Top to Bottom CompactBox" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*KrAqTrFbNjMAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "compactBoxLeftAlign.js", + "title": { + "zh": "节点左对齐的紧凑树", + "en": "CompactBox with Left Align Nodes" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*b3G-QZZekE8AAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tree/compactBox/demo/tbCompactBox.js b/packages/site/examples/tree/compactBox/demo/tbCompactBox.js new file mode 100644 index 0000000000..a2d0bf6dc5 --- /dev/null +++ b/packages/site/examples/tree/compactBox/demo/tbCompactBox.js @@ -0,0 +1,89 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.getModel(); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'cubic-vertical', + }, + layout: { + type: 'compactBox', + direction: 'TB', + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + getVGap: function getVGap() { + return 80; + }, + getHGap: function getHGap() { + return 20; + }, + }, + }); + + graph.node(function (node) { + let position = 'right'; + let rotate = 0; + if (!node.children) { + position = 'bottom'; + rotate = Math.PI / 2; + } + return { + label: node.id, + labelCfg: { + position, + offset: 5, + style: { + rotate, + textAlign: 'start', + }, + }, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/compactBox/index.en.md b/packages/site/examples/tree/compactBox/index.en.md new file mode 100644 index 0000000000..a70b1aecf4 --- /dev/null +++ b/packages/site/examples/tree/compactBox/index.en.md @@ -0,0 +1,14 @@ +--- +title: CompactBox +order: 0 +redirect_from: + - /en/examples +--- + +The nodes with the same depth will be layed on the same level. The node size will be considred while doing layout.
compactbox + +## Usage + +CompactBox is an appropriate layout method for tree data structure. As the demo below, you can deploy it in `layout` while instantiating Graph. + +You can set different configurations for different nodes if the parameter is Function type. Please refer to the ducuments for more information. diff --git a/packages/site/examples/tree/compactBox/index.zh.md b/packages/site/examples/tree/compactBox/index.zh.md new file mode 100644 index 0000000000..1d8f99b704 --- /dev/null +++ b/packages/site/examples/tree/compactBox/index.zh.md @@ -0,0 +1,14 @@ +--- +title: 紧凑树 +order: 0 +redirect_from: + - /zh/examples +--- + +从根节点开始,同一深度的节点在同一层,并且布局时会将节点大小考虑进去。
compactbox + +## 使用指南 + +紧凑树适用于展示树结构数据,配合 TreeGraph 使用。如下面代码所示,可在实例化 TreeGraph 时使用该布局。 + +其配置项中 Function 类型的配置项可以为不同的元素配置不同的值。具体描述请查看相关教程。 diff --git a/packages/site/examples/tree/customItemTree/API.en.md b/packages/site/examples/tree/customItemTree/API.en.md new file mode 100644 index 0000000000..8f1203f627 --- /dev/null +++ b/packages/site/examples/tree/customItemTree/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tree/customItemTree/API.zh.md b/packages/site/examples/tree/customItemTree/API.zh.md new file mode 100644 index 0000000000..c655cc24c6 --- /dev/null +++ b/packages/site/examples/tree/customItemTree/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tree/customItemTree/demo/customEdgeTree.js b/packages/site/examples/tree/customItemTree/demo/customEdgeTree.js new file mode 100644 index 0000000000..683a15d1f0 --- /dev/null +++ b/packages/site/examples/tree/customItemTree/demo/customEdgeTree.js @@ -0,0 +1,360 @@ +import G6 from '@antv/g6'; + +const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) { + return [ + ['M', x - r, y - r], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x + 2 - r, y - r], + ['L', x + r - 2, y - r], + ]; +}; +const EXPAND_ICON = function EXPAND_ICON(x, y, r) { + return [ + ['M', x - r, y - r], + ['a', r, r, 0, 1, 0, r * 2, 0], + ['a', r, r, 0, 1, 0, -r * 2, 0], + ['M', x + 2 - r, y - r], + ['L', x + r - 2, y - r], + ['M', x, y - 2 * r + 2], + ['L', x, y - 2], + ]; +}; + +const data = { + id: 'root', + label: 'root', + children: [ + { + id: 'c1', + label: 'c1', + children: [ + { + id: 'c1-1', + label: 'c1-1', + }, + { + id: 'c1-2', + label: 'c1-2', + children: [ + { + id: 'c1-2-1', + label: 'c1-2-1', + }, + { + id: 'c1-2-2', + label: 'c1-2-2', + }, + ], + }, + ], + }, + { + id: 'c2', + label: 'c2', + }, + { + id: 'c3', + label: 'c3', + children: [ + { + id: 'c3-1', + label: 'c3-1', + }, + { + id: 'c3-2', + label: 'c3-2', + children: [ + { + id: 'c3-2-1', + label: 'c3-2-1', + }, + { + id: 'c3-2-2', + label: 'c3-2-2', + }, + { + id: 'c3-2-3', + label: 'c3-2-3', + }, + ], + }, + { + id: 'c3-3', + label: 'c3-3', + }, + ], + }, + ], +}; + +G6.Util.traverseTree(data, (d) => { + d.leftIcon = { + style: { + fill: '#e6fffb', + stroke: '#e6fffb', + }, + img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q_FQT6nwEC8AAAAAAAAAAABkARQnAQ', + }; + return true; +}); + +G6.registerNode( + 'icon-node', + { + options: { + size: [60, 20], + stroke: '#91d5ff', + fill: '#91d5ff', + }, + draw(cfg, group) { + const styles = this.getShapeStyle(cfg); + const { labelCfg = {} } = cfg; + + const w = styles.width; + const h = styles.height; + + const keyShape = group.addShape('rect', { + attrs: { + ...styles, + x: -w / 2, + y: -h / 2, + }, + }); + + /** + * leftIcon 格式如下: + * { + * style: ShapeStyle; + * img: '' + * } + */ + console.log('cfg.leftIcon', cfg.leftIcon); + if (cfg.leftIcon) { + const { style, img } = cfg.leftIcon; + group.addShape('rect', { + attrs: { + x: 1 - w / 2, + y: 1 - h / 2, + width: 38, + height: styles.height - 2, + fill: '#8c8c8c', + ...style, + }, + }); + + group.addShape('image', { + attrs: { + x: 8 - w / 2, + y: 8 - h / 2, + width: 24, + height: 24, + img: + img || + 'https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'image-shape', + }); + } + + // 如果不需要动态增加或删除元素,则不需要 add 这两个 marker + group.addShape('marker', { + attrs: { + x: 40 - w / 2, + y: 52 - h / 2, + r: 6, + stroke: '#73d13d', + cursor: 'pointer', + symbol: EXPAND_ICON, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'add-item', + }); + + group.addShape('marker', { + attrs: { + x: 80 - w / 2, + y: 52 - h / 2, + r: 6, + stroke: '#ff4d4f', + cursor: 'pointer', + symbol: COLLAPSE_ICON, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'remove-item', + }); + + if (cfg.label) { + group.addShape('text', { + attrs: { + ...labelCfg.style, + text: cfg.label, + x: 50 - w / 2, + y: 25 - h / 2, + }, + }); + } + + return keyShape; + }, + update: undefined, + }, + 'rect', +); + +G6.registerEdge('flow-line', { + draw(cfg, group) { + const startPoint = cfg.startPoint; + const endPoint = cfg.endPoint; + + const { style } = cfg; + const shape = group.addShape('path', { + attrs: { + stroke: style.stroke, + endArrow: style.endArrow, + path: [ + ['M', startPoint.x, startPoint.y], + ['L', startPoint.x, (startPoint.y + endPoint.y) / 2], + ['L', endPoint.x, (startPoint.y + endPoint.y) / 2], + ['L', endPoint.x, endPoint.y], + ], + }, + }); + + return shape; + }, +}); + +const defaultStateStyles = { + hover: { + stroke: '#1890ff', + lineWidth: 2, + }, +}; + +const defaultNodeStyle = { + fill: '#91d5ff', + stroke: '#40a9ff', + radius: 5, +}; + +const defaultEdgeStyle = { + stroke: '#91d5ff', + endArrow: { + path: 'M 0,0 L 12, 6 L 9,0 L 12, -6 Z', + fill: '#91d5ff', + d: -20, + }, +}; + +const defaultLayout = { + type: 'compactBox', + direction: 'TB', + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + getVGap: function getVGap() { + return 40; + }, + getHGap: function getHGap() { + return 70; + }, +}; + +const defaultLabelCfg = { + style: { + fill: '#000', + fontSize: 12, + }, +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const minimap = new G6.Minimap({ + size: [150, 100], +}); +const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [minimap], + modes: { + default: ['drag-canvas', 'zoom-canvas'], + }, + defaultNode: { + type: 'icon-node', + size: [120, 40], + style: defaultNodeStyle, + labelCfg: defaultLabelCfg, + }, + defaultEdge: { + type: 'flow-line', + style: defaultEdgeStyle, + }, + nodeStateStyles: defaultStateStyles, + edgeStateStyles: defaultStateStyles, + layout: defaultLayout, +}); + +graph.data(data); +graph.render(); +graph.fitView(); + +graph.on('node:mouseenter', (evt) => { + const { item } = evt; + graph.setItemState(item, 'hover', true); +}); + +graph.on('node:mouseleave', (evt) => { + const { item } = evt; + graph.setItemState(item, 'hover', false); +}); + +graph.on('node:click', (evt) => { + const { item, target } = evt; + const targetType = target.get('type'); + const name = target.get('name'); + + // 增加元素 + if (targetType === 'marker') { + const model = item.getModel(); + if (name === 'add-item') { + if (!model.children) { + model.children = []; + } + const id = `n-${Math.random()}`; + model.children.push({ + id, + label: id.substr(0, 8), + leftIcon: { + style: { + fill: '#e6fffb', + stroke: '#e6fffb', + }, + img: + 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q_FQT6nwEC8AAAAAAAAAAABkARQnAQ', + }, + }); + graph.updateChild(model, model.id); + } else if (name === 'remove-item') { + graph.removeChild(model.id); + } + } +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tree/customItemTree/demo/customTree.js b/packages/site/examples/tree/customItemTree/demo/customTree.js new file mode 100644 index 0000000000..17187a688c --- /dev/null +++ b/packages/site/examples/tree/customItemTree/demo/customTree.js @@ -0,0 +1,142 @@ +import G6 from '@antv/g6'; + +G6.registerNode( + 'tree-node', + { + drawShape: function drawShape(cfg, group) { + const rect = group.addShape('rect', { + attrs: { + fill: '#fff', + stroke: '#666', + x: 0, + y: 0, + width:1, + height: 1 + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'rect-shape', + }); + const content = cfg.name.replace(/(.{19})/g, '$1\n'); + const text = group.addShape('text', { + attrs: { + text: content, + x: 0, + y: 0, + textAlign: 'left', + textBaseline: 'middle', + fill: '#666', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', + }); + const bbox = text.getBBox(); + const hasChildren = cfg.children && cfg.children.length > 0; + rect.attr({ + x: -bbox.width / 2 - 4, + y: -bbox.height / 2 - 6, + width: bbox.width + (hasChildren ? 26 : 12), + height: bbox.height + 12, + }); + text.attr({ + x: -bbox.width / 2, + y: 0 + }) + if (hasChildren) { + group.addShape('marker', { + attrs: { + x: bbox.width / 2 + 12, + y: 0, + r: 6, + symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse, + stroke: '#666', + lineWidth: 2, + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'collapse-icon', + }); + } + return rect; + }, + update: (cfg, item) => { + const group = item.getContainer(); + const icon = group.find((e) => e.get('name') === 'collapse-icon'); + icon.attr('symbol', cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse); + }, + }, + 'single-node', +); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + graph.updateItem(item, { + collapsed, + }); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + type: 'tree-node', + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'cubic-horizontal', + style: { + stroke: '#A3B1BF', + }, + }, + layout: { + type: 'compactBox', + direction: 'LR', + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + getVGap: function getVGap() { + return 20; + }, + getHGap: function getHGap() { + return 80; + }, + }, +}); +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/modeling-methods.json') + .then((res) => res.json()) + .then((data) => { + G6.Util.traverseTree(data, function (item) { + item.id = item.name; + }); + graph.data(data); + graph.render(); + graph.fitView(); + }); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tree/customItemTree/demo/meta.json b/packages/site/examples/tree/customItemTree/demo/meta.json new file mode 100644 index 0000000000..b0b8a2b768 --- /dev/null +++ b/packages/site/examples/tree/customItemTree/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "customEdgeTree.js", + "title": { + "zh": "自定义树图中的边", + "en": "Edges on Tree" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*AncCQ5e7XncAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "treeEdgeLabel.js", + "title": { + "zh": "树图边上的标签", + "en": "Labels on Tree Edge" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*LtTbS4D59H4AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "customTree.js", + "title": { + "zh": "自定义复杂树图样式", + "en": "Custom Tree Node 1" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*D8kzQYHaUiIAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tree/customItemTree/demo/treeEdgeLabel.js b/packages/site/examples/tree/customItemTree/demo/treeEdgeLabel.js new file mode 100644 index 0000000000..71c789150f --- /dev/null +++ b/packages/site/examples/tree/customItemTree/demo/treeEdgeLabel.js @@ -0,0 +1,165 @@ +import G6 from '@antv/g6'; + +const data = { + isRoot: true, + id: 'Root', + style: { + fill: 'red', + }, + children: [ + { + id: 'SubTreeNode1', + raw: {}, + children: [ + { + id: 'SubTreeNode1.1', + }, + { + id: 'SubTreeNode1.2', + children: [ + { + id: 'SubTreeNode1.2.1', + }, + { + id: 'SubTreeNode1.2.2', + }, + { + id: 'SubTreeNode1.2.3', + }, + ], + }, + ], + }, + { + id: 'SubTreeNode2', + children: [ + { + id: 'SubTreeNode2.1', + }, + ], + }, + { + id: 'SubTreeNode3', + children: [ + { + id: 'SubTreeNode3.1', + }, + { + id: 'SubTreeNode3.2', + }, + { + id: 'SubTreeNode3.3', + }, + ], + }, + { + id: 'SubTreeNode4', + }, + { + id: 'SubTreeNode5', + }, + { + id: 'SubTreeNode6', + }, + { + id: 'SubTreeNode7', + }, + { + id: 'SubTreeNode8', + }, + { + id: 'SubTreeNode9', + }, + { + id: 'SubTreeNode10', + }, + { + id: 'SubTreeNode11', + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 30, + }, + layout: { + type: 'compactBox', + direction: 'LR', + getId: function getId(d) { + return d.id; + }, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + getVGap: function getVGap() { + return 10; + }, + getHGap: function getHGap() { + return 100; + }, + }, +}); + +graph.node(function (node) { + return { + size: 16, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + style: { + fill: '#C6E5FF', + stroke: '#5B8FF9', + }, + label: node.id, + labelCfg: { + position: node.children && node.children.length > 0 ? 'left' : 'right', + offset: 5, + }, + }; +}); +let i = 0; +graph.edge(function () { + i++; + return { + type: 'cubic-horizontal', + color: '#A3B1BF', + label: i, + }; +}); + +graph.data(data); +graph.render(); +graph.fitView(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tree/customItemTree/index.en.md b/packages/site/examples/tree/customItemTree/index.en.md new file mode 100644 index 0000000000..f24192d89c --- /dev/null +++ b/packages/site/examples/tree/customItemTree/index.en.md @@ -0,0 +1,14 @@ +--- +title: Custom Tree Items +order: 5 +--- + +Thanks to the item's custom mechanism of G6, users can design their own tree with freedom. + +## Usage + +Combining with custom items, the complex tree visualization can be realized. + +- Examples 1 : Customize the node and the edge with tree graph, support add or remove node. +- Examples 2 : Add labels for edges. +- Examples 3 : Customize the node with expand and collapse button. diff --git a/packages/site/examples/tree/customItemTree/index.zh.md b/packages/site/examples/tree/customItemTree/index.zh.md new file mode 100644 index 0000000000..8883ca562e --- /dev/null +++ b/packages/site/examples/tree/customItemTree/index.zh.md @@ -0,0 +1,14 @@ +--- +title: 定制树图元素样式 +order: 5 +--- + +G6 自定义节点与边机制允许用户对树图视觉样式进行定制。 + +## 使用指南 + +通过结合自定义元素,可以实现较为复杂的树可视化。 + +- 代码演示 1: 自定义树图中的节点及边,支持增加和删除节点; +- 代码演示 2 :为边增加文本标签。 +- 代码演示 3 :自定义带有收缩/扩展按钮的节点。 diff --git a/packages/site/examples/tree/dendrogram/API.en.md b/packages/site/examples/tree/dendrogram/API.en.md new file mode 100644 index 0000000000..7764eb60ef --- /dev/null +++ b/packages/site/examples/tree/dendrogram/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tree/dendrogram/API.zh.md b/packages/site/examples/tree/dendrogram/API.zh.md new file mode 100644 index 0000000000..c34fe443de --- /dev/null +++ b/packages/site/examples/tree/dendrogram/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tree/dendrogram/demo/basicDendrogram.js b/packages/site/examples/tree/dendrogram/demo/basicDendrogram.js new file mode 100644 index 0000000000..6a3273f14b --- /dev/null +++ b/packages/site/examples/tree/dendrogram/demo/basicDendrogram.js @@ -0,0 +1,65 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'cubic-horizontal', + }, + layout: { + type: 'dendrogram', + direction: 'LR', // H / V / LR / RL / TB / BT + nodeSep: 30, + rankSep: 100, + }, + }); + + graph.node(function (node) { + return { + label: node.id, + labelCfg: { + position: node.children && node.children.length > 0 ? 'left' : 'right', + offset: 5, + }, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/dendrogram/demo/meta.json b/packages/site/examples/tree/dendrogram/demo/meta.json new file mode 100644 index 0000000000..8d0a9e8585 --- /dev/null +++ b/packages/site/examples/tree/dendrogram/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basicDendrogram.js", + "title": { + "zh": "生态树", + "en": "Basic Dendrogram Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ffD6S74MXw4AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "tbDendrogram.js", + "title": { + "zh": "至上而下的生态树", + "en": "Top to Bottom Dendrogram" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*nTKmRKkyUVUAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tree/dendrogram/demo/tbDendrogram.js b/packages/site/examples/tree/dendrogram/demo/tbDendrogram.js new file mode 100644 index 0000000000..6a0d3491ab --- /dev/null +++ b/packages/site/examples/tree/dendrogram/demo/tbDendrogram.js @@ -0,0 +1,76 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'cubic-vertical', + }, + layout: { + type: 'dendrogram', + direction: 'TB', // H / V / LR / RL / TB / BT + nodeSep: 40, + rankSep: 100, + }, + }); + + graph.node(function (node) { + let position = 'right'; + let rotate = 0; + if (!node.children) { + position = 'bottom'; + rotate = Math.PI / 2; + } + return { + label: node.id, + labelCfg: { + position, + offset: 5, + style: { + rotate, + textAlign: 'start', + }, + }, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/dendrogram/index.en.md b/packages/site/examples/tree/dendrogram/index.en.md new file mode 100644 index 0000000000..6629766d19 --- /dev/null +++ b/packages/site/examples/tree/dendrogram/index.en.md @@ -0,0 +1,12 @@ +--- +title: Dendrogram +order: 1 +--- + +The leaves will be aligned on the same level. This algorithm does not consider the node size, which means all the nodes will be regarded as unit size with 1px.
dendrogram + +## Usage + +Dendrogram is an appropriate layout method for tree data structure. Please use it with TreeGraph. As the demo below, you can deploy it in `layout` while instantiating Graph. + +You can set different configurations for different nodes if the parameter is Function type. Please refer to the ducuments for more information. diff --git a/packages/site/examples/tree/dendrogram/index.zh.md b/packages/site/examples/tree/dendrogram/index.zh.md new file mode 100644 index 0000000000..57d9c3e38f --- /dev/null +++ b/packages/site/examples/tree/dendrogram/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 生态树 +order: 1 +--- + +不管数据的深度多少,总是叶节点对齐。不考虑节点大小,布局时将节点视为 1 个像素点。
dendrogram + +## 使用指南 + +生态树适用于展示树结构数据,配合 TreeGraph 使用。如下面代码所示,可在实例化 TreeGraph 时使用该布局。 diff --git a/packages/site/examples/tree/indented/API.en.md b/packages/site/examples/tree/indented/API.en.md new file mode 100644 index 0000000000..94a70e8908 --- /dev/null +++ b/packages/site/examples/tree/indented/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tree/indented/API.zh.md b/packages/site/examples/tree/indented/API.zh.md new file mode 100644 index 0000000000..380e25baa2 --- /dev/null +++ b/packages/site/examples/tree/indented/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tree/indented/demo/filesystem.js b/packages/site/examples/tree/indented/demo/filesystem.js new file mode 100644 index 0000000000..d2d048ef95 --- /dev/null +++ b/packages/site/examples/tree/indented/demo/filesystem.js @@ -0,0 +1,197 @@ +import G6 from '@antv/g6'; + +G6.registerNode('file-node', { + draw: function draw(cfg, group) { + const keyShape = group.addShape('rect', { + attrs: { + x: 10, + y: -12, + fill: '#fff', + stroke: null, + }, + }); + let isLeaf = false; + if (cfg.collapsed) { + group.addShape('marker', { + attrs: { + symbol: 'triangle', + x: 4, + y: -2, + r: 4, + fill: '#666', + }, + name: 'marker-shape', + }); + } else if (cfg.children && cfg.children.length > 0) { + group.addShape('marker', { + attrs: { + symbol: 'triangle-down', + x: 4, + y: -2, + r: 4, + fill: '#666', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'marker-shape', + }); + } else { + isLeaf = true; + } + const shape = group.addShape('text', { + attrs: { + x: 15, + y: 4, + text: cfg.name, + fill: '#666', + fontSize: 16, + textAlign: 'left', + fontFamily: + typeof window !== 'undefined' + ? window.getComputedStyle(document.body, null).getPropertyValue('font-family') || + 'Arial, sans-serif' + : 'Arial, sans-serif', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'text-shape', + }); + const bbox = shape.getBBox(); + let backRectW = bbox.width; + let backRectX = keyShape.attr('x'); + if (!isLeaf) { + backRectW += 8; + backRectX -= 15; + } + keyShape.attr({ + width: backRectW, + height: bbox.height + 4, + x: backRectX, + }); + return keyShape; + }, +}); +G6.registerEdge( + 'step-line', + { + getControlPoints: function getControlPoints(cfg) { + const startPoint = cfg.startPoint; + const endPoint = cfg.endPoint; + return [ + startPoint, + { + x: startPoint.x, + y: endPoint.y, + }, + endPoint, + ]; + }, + }, + 'polyline', +); + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; +const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: [ + { + type: 'collapse-expand', + animate: false, + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultEdge: { + style: { + stroke: '#A3B1BF', + }, + }, + layout: { + type: 'indented', + isHorizontal: true, + direction: 'LR', + indent: 30, + getHeight: function getHeight() { + return 16; + }, + getWidth: function getWidth() { + return 16; + }, + }, +}); +const data = { + id: '1', + name: 'src', + children: [ + { + id: '1-1', + name: 'behavior', + children: [], + }, + { + id: '1-3', + name: 'graph', + children: [ + { + id: '1-3-1', + name: 'controller', + children: [], + }, + ], + }, + { + id: '1-5', + name: 'item', + children: [], + }, + { + id: '1-6', + name: 'shape', + children: [ + { + id: '1-6-2', + name: 'extend', + children: [], + }, + ], + }, + { + id: '1-7', + name: 'util', + children: [], + }, + ], +}; + +graph.node((node) => { + return { + type: 'file-node', + label: node.name, + }; +}); +graph.edge(() => { + return { + type: 'step-line', + }; +}); + +graph.data(data); +graph.render(); +graph.fitView(); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tree/indented/demo/hIntended.js b/packages/site/examples/tree/indented/demo/hIntended.js new file mode 100644 index 0000000000..d4ec78512d --- /dev/null +++ b/packages/site/examples/tree/indented/demo/hIntended.js @@ -0,0 +1,72 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas'], + }, + defaultNode: { + size: 26, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'cubic-horizontal', + }, + layout: { + type: 'indented', + direction: 'H', + indent: 80, + getHeight: () => { + return 10; + }, + getWidth: () => { + return 10; + }, + getSide: (d) => { + if (d.id === 'Regression' || d.id === 'Classification') return 'left'; + return 'right'; + }, + }, + }); + + let centerX = 0; + graph.node(function (node) { + if (node.id === 'Modeling Methods') { + centerX = node.x; + } + return { + label: node.id, + labelCfg: { + position: + node.children && node.children.length > 0 + ? 'left' + : node.x > centerX + ? 'right' + : 'left', + offset: 5, + }, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/indented/demo/intendAlignTop.js b/packages/site/examples/tree/indented/demo/intendAlignTop.js new file mode 100644 index 0000000000..88b78b6894 --- /dev/null +++ b/packages/site/examples/tree/indented/demo/intendAlignTop.js @@ -0,0 +1,159 @@ +import G6 from '@antv/g6'; + +G6.registerNode('card-node', { + draw: function drawShape(cfg, group) { + const r = 2; + const color = '#5B8FF9'; + const w = cfg.size[0]; + const h = cfg.size[1]; + const shape = group.addShape('rect', { + attrs: { + x: -w / 2, + y: -h / 2, + width: w, //200, + height: h, // 60 + stroke: color, + radius: r, + fill: '#fff', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'main-box', + draggable: true, + }); + + group.addShape('rect', { + attrs: { + x: -w / 2, + y: -h / 2, + width: w, //200, + height: h / 2, // 60 + fill: color, + radius: [r, r, 0, 0], + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'title-box', + draggable: true, + }); + + // title text + group.addShape('text', { + attrs: { + textBaseline: 'top', + x: -w / 2 + 8, + y: -h / 2 + 2, + lineHeight: 20, + text: cfg.id, + fill: '#fff', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'title', + }); + cfg.children && + group.addShape('marker', { + attrs: { + x: w / 2, + y: 0, + r: 6, + cursor: 'pointer', + symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse, + stroke: '#666', + lineWidth: 1, + fill: '#fff', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: 'collapse-icon', + }); + group.addShape('text', { + attrs: { + textBaseline: 'top', + x: -w / 2 + 8, + y: -h / 2 + 24, + lineHeight: 20, + text: 'description', + fill: 'rgba(0,0,0, 1)', + }, + // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type + name: `description`, + }); + return shape; + }, + setState(name, value, item) { + if (name === 'collapsed') { + const marker = item.get('group').find((ele) => ele.get('name') === 'collapse-icon'); + const icon = value ? G6.Marker.expand : G6.Marker.collapse; + marker.attr('symbol', icon); + } + }, +}); + +const data = { + id: 'A', + children: [ + { + id: 'A1', + children: [{ id: 'A11' }, { id: 'A12' }, { id: 'A13' }, { id: 'A14' }], + }, + { + id: 'A2', + children: [ + { + id: 'A21', + children: [{ id: 'A211' }, { id: 'A212' }], + }, + { + id: 'A22', + }, + ], + }, + ], +}; + +const container = document.getElementById('container'); +const width = container.scrollWidth; +const height = container.scrollHeight || 500; + +const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: ['drag-canvas'], + }, + defaultNode: { + type: 'card-node', + size: [100, 40], + }, + defaultEdge: { + type: 'cubic-horizontal', + style: { + endArrow: true, + }, + }, + layout: { + type: 'indented', + direction: 'LR', + dropCap: false, + indent: 200, + getHeight: () => { + return 60; + }, + }, +}); + +graph.data(data); +graph.render(); +graph.fitView(); +graph.on('node:click', (e) => { + if (e.target.get('name') === 'collapse-icon') { + e.item.getModel().collapsed = !e.item.getModel().collapsed; + graph.setItemState(e.item, 'collapsed', e.item.getModel().collapsed); + graph.layout(); + } +}); + +if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; diff --git a/packages/site/examples/tree/indented/demo/meta.json b/packages/site/examples/tree/indented/demo/meta.json new file mode 100644 index 0000000000..ddfae06ace --- /dev/null +++ b/packages/site/examples/tree/indented/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "filesystem.js", + "title": { + "zh": "缩进树-文件系统", + "en": "FileSystem-Indented Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*PJnfTKFRaCAAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "intendAlignTop.js", + "title": { + "zh": "缩进树-顶部对齐", + "en": "FileSystem-Align Top" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*kbzRRZk2t2cAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "hIntended.js", + "title": { + "zh": "缩进树-子节点两侧分布", + "en": "Two Side Indented Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*42EXSYyBcTYAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tree/indented/index.en.md b/packages/site/examples/tree/indented/index.en.md new file mode 100644 index 0000000000..0de3e6f41e --- /dev/null +++ b/packages/site/examples/tree/indented/index.en.md @@ -0,0 +1,12 @@ +--- +title: Indented +order: 3 +--- + +In indented tree layout, each node takes a row or a column.
indented + +## Usage + +Indented is an appropriate layout method for tree data structure. Please use it with TreeGraph. As the demo below, you can deploy it in `layout` while instantiating Graph. + +You can set different configurations for different nodes if the parameter is Function type. Please refer to the ducuments for more information. diff --git a/packages/site/examples/tree/indented/index.zh.md b/packages/site/examples/tree/indented/index.zh.md new file mode 100644 index 0000000000..01b3f08fab --- /dev/null +++ b/packages/site/examples/tree/indented/index.zh.md @@ -0,0 +1,12 @@ +--- +title: 缩进树 +order: 3 +--- + +缩进树布局。每个元素会占一行/一列。
indented + +## 使用指南 + +缩进树适用于展示树结构数据,配合 TreeGraph 使用。如下面代码所示,可在实例化 TreeGraph 时使用该布局。 + +该布局有以下配置项,Function 类型的配置项可以为不同的元素配置不同的值。具体描述请查看相关教程。 diff --git a/packages/site/examples/tree/mindmap/API.en.md b/packages/site/examples/tree/mindmap/API.en.md new file mode 100644 index 0000000000..76fafac5aa --- /dev/null +++ b/packages/site/examples/tree/mindmap/API.en.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tree/mindmap/API.zh.md b/packages/site/examples/tree/mindmap/API.zh.md new file mode 100644 index 0000000000..380e25baa2 --- /dev/null +++ b/packages/site/examples/tree/mindmap/API.zh.md @@ -0,0 +1,5 @@ +--- +title: API +--- + + diff --git a/packages/site/examples/tree/mindmap/demo/hCustomSideMindmap.js b/packages/site/examples/tree/mindmap/demo/hCustomSideMindmap.js new file mode 100644 index 0000000000..0385d81114 --- /dev/null +++ b/packages/site/examples/tree/mindmap/demo/hCustomSideMindmap.js @@ -0,0 +1,91 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'cubic-horizontal', + }, + layout: { + type: 'mindmap', + direction: 'H', + getHeight: () => { + return 16; + }, + getWidth: () => { + return 16; + }, + getVGap: () => { + return 10; + }, + getHGap: () => { + return 50; + }, + getSide: (d) => { + if (d.id === 'Classification') { + return 'left'; + } + return 'right'; + }, + }, + }); + + let centerX = 0; + graph.node(function (node) { + if (node.id === 'Modeling Methods') { + centerX = node.x; + } + + return { + label: node.id, + labelCfg: { + position: + node.children && node.children.length > 0 + ? 'left' + : node.x > centerX + ? 'right' + : 'left', + offset: 5, + }, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/mindmap/demo/hLeftMindmap.js b/packages/site/examples/tree/mindmap/demo/hLeftMindmap.js new file mode 100644 index 0000000000..9fbc83696a --- /dev/null +++ b/packages/site/examples/tree/mindmap/demo/hLeftMindmap.js @@ -0,0 +1,88 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'cubic-horizontal', + }, + layout: { + type: 'mindmap', + direction: 'H', + getHeight: () => { + return 16; + }, + getWidth: () => { + return 16; + }, + getVGap: () => { + return 10; + }, + getHGap: () => { + return 100; + }, + getSide: () => { + return 'left'; + }, + }, + }); + + let centerX = 0; + graph.node(function (node) { + if (node.id === 'Modeling Methods') { + centerX = node.x; + } + + return { + label: node.id, + labelCfg: { + position: + node.children && node.children.length > 0 + ? 'right' + : node.x > centerX + ? 'right' + : 'left', + offset: 5, + }, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/mindmap/demo/hMindmap.js b/packages/site/examples/tree/mindmap/demo/hMindmap.js new file mode 100644 index 0000000000..c69d8e7532 --- /dev/null +++ b/packages/site/examples/tree/mindmap/demo/hMindmap.js @@ -0,0 +1,85 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'cubic-horizontal', + }, + layout: { + type: 'mindmap', + direction: 'H', + getHeight: () => { + return 16; + }, + getWidth: () => { + return 16; + }, + getVGap: () => { + return 10; + }, + getHGap: () => { + return 50; + }, + }, + }); + + let centerX = 0; + graph.node(function (node) { + if (node.id === 'Modeling Methods') { + centerX = node.x; + } + + return { + label: node.id, + labelCfg: { + position: + node.children && node.children.length > 0 + ? 'left' + : node.x > centerX + ? 'right' + : 'left', + offset: 5, + }, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/mindmap/demo/hRightMindmap.js b/packages/site/examples/tree/mindmap/demo/hRightMindmap.js new file mode 100644 index 0000000000..90b9468e5a --- /dev/null +++ b/packages/site/examples/tree/mindmap/demo/hRightMindmap.js @@ -0,0 +1,88 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + anchorPoints: [ + [0, 0.5], + [1, 0.5], + ], + }, + defaultEdge: { + type: 'cubic-horizontal', + }, + layout: { + type: 'mindmap', + direction: 'H', + getHeight: () => { + return 16; + }, + getWidth: () => { + return 16; + }, + getVGap: () => { + return 10; + }, + getHGap: () => { + return 100; + }, + getSide: () => { + return 'right'; + }, + }, + }); + + let centerX = 0; + graph.node(function (node) { + if (node.id === 'Modeling Methods') { + centerX = node.x; + } + + return { + label: node.id, + labelCfg: { + position: + node.children && node.children.length > 0 + ? 'right' + : node.x > centerX + ? 'right' + : 'left', + offset: 5, + }, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/mindmap/demo/meta.json b/packages/site/examples/tree/mindmap/demo/meta.json new file mode 100644 index 0000000000..747920ce10 --- /dev/null +++ b/packages/site/examples/tree/mindmap/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "hMindmap.js", + "title": { + "zh": "脑图树-子节点自动两侧分布", + "en": "Two Side Mindmap Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wRZjTL3fCbEAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "hRightMindmap.js", + "title": { + "zh": "脑图树-子节点右侧分布", + "en": "Right Side Mindmap Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*W6OGRLg2UJcAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "hLeftMindmap.js", + "title": { + "zh": "脑图树-子节点左侧分布", + "en": "Left Side Mindmap Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*NNUaTaN9yIgAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "hCustomSideMindmap.js", + "title": { + "zh": "脑图树-自定义子节点分布", + "en": "Custom Mindmap Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Su39QqQr9PYAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tree/mindmap/index.en.md b/packages/site/examples/tree/mindmap/index.en.md new file mode 100644 index 0000000000..47cbe14abf --- /dev/null +++ b/packages/site/examples/tree/mindmap/index.en.md @@ -0,0 +1,12 @@ +--- +title: Mindmap +order: 2 +--- + +The nodes with same depth will be placed on the same level. Difference from compactBox, Mindmap will not consider the node size.
mindmap + +## Usage + +Mindmap is an appropriate layout method for tree data structure. Please use it with TreeGraph. As the demo below, you can deploy it in `layout` while instantiating Graph. + +You can set different configurations for different nodes if the parameter is Function type. Please refer to the ducuments for more information. diff --git a/packages/site/examples/tree/mindmap/index.zh.md b/packages/site/examples/tree/mindmap/index.zh.md new file mode 100644 index 0000000000..e8976b7138 --- /dev/null +++ b/packages/site/examples/tree/mindmap/index.zh.md @@ -0,0 +1,24 @@ +--- +title: 脑图树 +order: 2 +--- + +深度相同的节点将会被放置在同一层,与 compactBox 不同的是,布局不会考虑节点的大小。
mindmap + +## 使用指南 + +脑图树适用于展示树结构数据,配合 TreeGraph 使用。如下面代码所示,可在实例化 TreeGraph 时使用该布局。 + +该布局有以下配置项,Function 类型的配置项可以为不同的元素配置不同的值。具体描述请查看相关教程。 + +- **direction**: String 类型,树的布局方向。可选值有:'H':根节点的子节点分成两部分横向放置在根节点左右两侧 ,如代码演示 1 | 'V':将根节点的所有孩子纵向排列。 + +- **getWidth**: Number | Function 类型,每个节点的宽度。 + +- **getHeight**: Number | Function 类型,每个节点的高度。 + +- **getHGap**: Number | Function 类型,每个节点的水平间隙。 + +- **getVGap**: Number | Function 类型,每个节点的垂直间隙。 + +- **getSide**: Function 类型,节点排布在根节点的左侧/右侧。若设置了该值,则所有节点会在根节点同一侧,即 direction = 'H' 不再起效。若该参数为回调函数,则可以指定每一个节点在根节点的左/右侧。如代码演示 2、3、4。 diff --git a/packages/site/examples/tree/radialtree/API.en.md b/packages/site/examples/tree/radialtree/API.en.md new file mode 100644 index 0000000000..b1de5fcac5 --- /dev/null +++ b/packages/site/examples/tree/radialtree/API.en.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +## Dendrogram + + + +## CompactBox + + diff --git a/packages/site/examples/tree/radialtree/API.zh.md b/packages/site/examples/tree/radialtree/API.zh.md new file mode 100644 index 0000000000..55e69f716a --- /dev/null +++ b/packages/site/examples/tree/radialtree/API.zh.md @@ -0,0 +1,11 @@ +--- +title: API +--- + +## Dendrogram + + + +## CompactBox + + diff --git a/packages/site/examples/tree/radialtree/demo/meta.json b/packages/site/examples/tree/radialtree/demo/meta.json new file mode 100644 index 0000000000..2ff9ab464c --- /dev/null +++ b/packages/site/examples/tree/radialtree/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "radialDendrogram.js", + "title": { + "zh": "生态辐射树", + "en": "Radial Dendrograme Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*mS3_Rb4VuPoAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "radialCompactBox.js", + "title": { + "zh": "紧凑辐射树", + "en": "Radial CompactBox Layout" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*xENwSLuyTZ4AAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/packages/site/examples/tree/radialtree/demo/radialCompactBox.js b/packages/site/examples/tree/radialtree/demo/radialCompactBox.js new file mode 100644 index 0000000000..e19a0e83b4 --- /dev/null +++ b/packages/site/examples/tree/radialtree/demo/radialCompactBox.js @@ -0,0 +1,69 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + }, + layout: { + type: 'compactBox', + direction: 'RL', + getId: function getId(d) { + return d.id; + }, + getHeight: () => { + return 26; + }, + getWidth: () => { + return 26; + }, + getVGap: () => { + return 20; + }, + getHGap: () => { + return 30; + }, + radial: true, + }, + }); + + graph.node(function (node) { + return { + label: node.id, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/radialtree/demo/radialDendrogram.js b/packages/site/examples/tree/radialtree/demo/radialDendrogram.js new file mode 100644 index 0000000000..9282f5b50e --- /dev/null +++ b/packages/site/examples/tree/radialtree/demo/radialDendrogram.js @@ -0,0 +1,56 @@ +import G6 from '@antv/g6'; + +fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => { + const container = document.getElementById('container'); + const width = container.scrollWidth; + const height = container.scrollHeight || 500; + const graph = new G6.TreeGraph({ + container: 'container', + width, + height, + linkCenter: true, + modes: { + default: [ + { + type: 'collapse-expand', + onChange: function onChange(item, collapsed) { + const data = item.get('model'); + data.collapsed = collapsed; + return true; + }, + }, + 'drag-canvas', + 'zoom-canvas', + ], + }, + defaultNode: { + size: 26, + }, + layout: { + type: 'dendrogram', + direction: 'LR', + nodeSep: 20, + rankSep: 100, + radial: true, + }, + }); + + graph.node(function (node) { + return { + label: node.id, + }; + }); + + graph.data(data); + graph.render(); + graph.fitView(); + + if (typeof window !== 'undefined') + window.onresize = () => { + if (!graph || graph.get('destroyed')) return; + if (!container || !container.scrollWidth || !container.scrollHeight) return; + graph.changeSize(container.scrollWidth, container.scrollHeight); + }; + }); diff --git a/packages/site/examples/tree/radialtree/index.en.md b/packages/site/examples/tree/radialtree/index.en.md new file mode 100644 index 0000000000..ee15834630 --- /dev/null +++ b/packages/site/examples/tree/radialtree/index.en.md @@ -0,0 +1,12 @@ +--- +title: Radial Tree +order: 4 +--- + +In radial tree layout, root node will be placed on the center. the branches will be placed in radial around the root. Dendrogram and CompactBox can be transformed into radial tree by configuration.
radialtree + +## Usage + +Radial tree layout is a deformation of Dendrogram and CompactBox. It is an appropriate layout method for tree data structure. Please use it with TreeGraph. As the demo below, you can deploy it in `layout` while instantiating Graph. + +Same as Dendrogram and CompactBox, only set the `radial` to `true`. We recommend to set `direction` to `LR` or `RL` while using radial tree layout. diff --git a/packages/site/examples/tree/radialtree/index.zh.md b/packages/site/examples/tree/radialtree/index.zh.md new file mode 100644 index 0000000000..419609b239 --- /dev/null +++ b/packages/site/examples/tree/radialtree/index.zh.md @@ -0,0 +1,12 @@ +--- +title: 辐射树 +order: 4 +--- + +生态树、紧凑树两种布局方法可以通过配置项变换为辐射型树布局。跟节点位于辐射树中心,其他分支辐射式展开。
radialtree + +## 使用指南 + +辐射树是通过生态树、紧凑树布局的变形。适用于展示树结构数据,配合 TreeGraph 使用。如下面代码所示,可在实例化 TreeGraph 时使用该布局。 + +使用方式与对应的生态树、紧凑树相同,配置 `radial` 为 `true` 时,将会以辐射形式展示树。在使用辐射树时建议将布局的 `direction` 配置为 `LR` 或 `RL`。 diff --git a/packages/site/package.json b/packages/site/package.json new file mode 100644 index 0000000000..16a21540a1 --- /dev/null +++ b/packages/site/package.json @@ -0,0 +1,54 @@ +{ + "private": true, + "name": "@antv/g6-site", + "version": "4.8.0", + "description": "G6 sites deployed on gh-pages", + "keywords": [ + "antv", + "g6", + "graph", + "graph analysis", + "graph editor", + "graph visualization", + "relational data", + "site" + ], + "homepage": "https://g6.antv.antgroup.com", + "bugs": { + "url": "https://github.com/antvis/g6/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/antvis/g6" + }, + "license": "MIT", + "author": "https://github.com/orgs/antvis/people", + "scripts": { + "site:build": "dumi build", + "site:develop": "dumi dev", + "site:preview": "dumi preview", + "site:deploy": "npm run site:build && gh-pages -d dist", + "start": "npm run site:develop" + }, + "devDependencies": { + "cross-env": "^7.0.3" + }, + "dependencies": { + "@ant-design/icons": "^4.0.6", + "@antv/chart-node-g6": "^0.0.3", + "@antv/dumi-theme-antv": "^0.3.0-beta.5", + "@antv/g6": "4.7.9", + "@antv/g6-react-node": "^1.4.5", + "@antv/util": "^2.0.9", + "@antv/vis-predict-engine": "^0.1.1", + "@microsoft/api-extractor": "^7.33.6", + "dumi": "^2.0.0-beta.15", + "insert-css": "^2.0.0", + "typedoc": "^0.17.6", + "typedoc-plugin-markdown": "^2.2.11", + "typescript": "^4.6.3" + }, + "resolutions": { + "@types/react": "^16.9.35" + } +} \ No newline at end of file