diff --git a/packages/g6/__tests__/dataset/flare.json b/packages/g6/__tests__/dataset/flare.json
new file mode 100644
index 00000000000..67776f21f11
--- /dev/null
+++ b/packages/g6/__tests__/dataset/flare.json
@@ -0,0 +1,355 @@
+{
+ "id": "flare",
+ "children": [
+ {
+ "id": "analytics",
+ "children": [
+ {
+ "id": "cluster",
+ "children": [
+ { "id": "AgglomerativeCluster", "value": 3938 },
+ { "id": "CommunityStructure", "value": 3812 },
+ { "id": "HierarchicalCluster", "value": 6714 },
+ { "id": "MergeEdge", "value": 743 }
+ ]
+ },
+ {
+ "id": "graph",
+ "children": [
+ { "id": "BetweennessCentrality", "value": 3534 },
+ { "id": "LinkDistance", "value": 5731 },
+ { "id": "MaxFlowMinCut", "value": 7840 },
+ { "id": "ShortestPaths", "value": 5914 },
+ { "id": "SpanningTree", "value": 3416 }
+ ]
+ },
+ {
+ "id": "optimization",
+ "children": [{ "id": "AspectRatioBanker", "value": 7074 }]
+ }
+ ]
+ },
+ {
+ "id": "animate",
+ "children": [
+ { "id": "Easing", "value": 17010 },
+ { "id": "FunctionSequence", "value": 5842 },
+ {
+ "id": "interpolate",
+ "children": [
+ { "id": "ArrayInterpolator", "value": 1983 },
+ { "id": "ColorInterpolator", "value": 2047 },
+ { "id": "DateInterpolator", "value": 1375 },
+ { "id": "Interpolator", "value": 8746 },
+ { "id": "MatrixInterpolator", "value": 2202 },
+ { "id": "NumberInterpolator", "value": 1382 },
+ { "id": "ObjectInterpolator", "value": 1629 },
+ { "id": "PointInterpolator", "value": 1675 },
+ { "id": "RectangleInterpolator", "value": 2042 }
+ ]
+ },
+ { "id": "ISchedulable", "value": 1041 },
+ { "id": "Parallel", "value": 5176 },
+ { "id": "Pause", "value": 449 },
+ { "id": "Scheduler", "value": 5593 },
+ { "id": "Sequence", "value": 5534 },
+ { "id": "Transition", "value": 9201 },
+ { "id": "Transitioner", "value": 19975 },
+ { "id": "TransitionEvent", "value": 1116 },
+ { "id": "Tween", "value": 6006 }
+ ]
+ },
+ {
+ "id": "display",
+ "children": [
+ { "id": "DirtySprite", "value": 8833 },
+ { "id": "LineSprite", "value": 1732 },
+ { "id": "RectSprite", "value": 3623 },
+ { "id": "TextSprite", "value": 10066 }
+ ]
+ },
+ {
+ "id": "flex",
+ "children": [{ "id": "FlareVis", "value": 4116 }]
+ },
+ {
+ "id": "physics",
+ "children": [
+ { "id": "DragForce", "value": 1082 },
+ { "id": "GravityForce", "value": 1336 },
+ { "id": "IForce", "value": 319 },
+ { "id": "NBodyForce", "value": 10498 },
+ { "id": "Particle", "value": 2822 },
+ { "id": "Simulation", "value": 9983 },
+ { "id": "Spring", "value": 2213 },
+ { "id": "SpringForce", "value": 1681 }
+ ]
+ },
+ {
+ "id": "query",
+ "children": [
+ { "id": "AggregateExpression", "value": 1616 },
+ { "id": "And", "value": 1027 },
+ { "id": "Arithmetic", "value": 3891 },
+ { "id": "Average", "value": 891 },
+ { "id": "BinaryExpression", "value": 2893 },
+ { "id": "Comparison", "value": 5103 },
+ { "id": "CompositeExpression", "value": 3677 },
+ { "id": "Count", "value": 781 },
+ { "id": "DateUtil", "value": 4141 },
+ { "id": "Distinct", "value": 933 },
+ { "id": "Expression", "value": 5130 },
+ { "id": "ExpressionIterator", "value": 3617 },
+ { "id": "Fn", "value": 3240 },
+ { "id": "If", "value": 2732 },
+ { "id": "IsA", "value": 2039 },
+ { "id": "Literal", "value": 1214 },
+ { "id": "Match", "value": 3748 },
+ { "id": "Maximum", "value": 843 },
+ {
+ "id": "methods",
+ "children": [
+ { "id": "add", "value": 593 },
+ { "id": "and", "value": 330 },
+ { "id": "average", "value": 287 },
+ { "id": "count", "value": 277 },
+ { "id": "distinct", "value": 292 },
+ { "id": "div", "value": 595 },
+ { "id": "eq", "value": 594 },
+ { "id": "fn", "value": 460 },
+ { "id": "gt", "value": 603 },
+ { "id": "gte", "value": 625 },
+ { "id": "iff", "value": 748 },
+ { "id": "isa", "value": 461 },
+ { "id": "lt", "value": 597 },
+ { "id": "lte", "value": 619 },
+ { "id": "max", "value": 283 },
+ { "id": "min", "value": 283 },
+ { "id": "mod", "value": 591 },
+ { "id": "mul", "value": 603 },
+ { "id": "neq", "value": 599 },
+ { "id": "not", "value": 386 },
+ { "id": "or", "value": 323 },
+ { "id": "orderby", "value": 307 },
+ { "id": "range", "value": 772 },
+ { "id": "select", "value": 296 },
+ { "id": "stddev", "value": 363 },
+ { "id": "sub", "value": 600 },
+ { "id": "sum", "value": 280 },
+ { "id": "update", "value": 307 },
+ { "id": "variance", "value": 335 },
+ { "id": "where", "value": 299 },
+ { "id": "xor", "value": 354 },
+ { "id": "-", "value": 264 }
+ ]
+ },
+ { "id": "Minimum", "value": 843 },
+ { "id": "Not", "value": 1554 },
+ { "id": "Or", "value": 970 },
+ { "id": "Query", "value": 13896 },
+ { "id": "Range", "value": 1594 },
+ { "id": "StringUtil", "value": 4130 },
+ { "id": "Sum", "value": 791 },
+ { "id": "Variable", "value": 1124 },
+ { "id": "Variance", "value": 1876 },
+ { "id": "Xor", "value": 1101 }
+ ]
+ },
+ {
+ "id": "scale",
+ "children": [
+ { "id": "IScaleMap", "value": 2105 },
+ { "id": "LinearScale", "value": 1316 },
+ { "id": "LogScale", "value": 3151 },
+ { "id": "OrdinalScale", "value": 3770 },
+ { "id": "QuantileScale", "value": 2435 },
+ { "id": "QuantitativeScale", "value": 4839 },
+ { "id": "RootScale", "value": 1756 },
+ { "id": "Scale", "value": 4268 },
+ { "id": "ScaleType", "value": 1821 },
+ { "id": "TimeScale", "value": 5833 }
+ ]
+ },
+ {
+ "id": "util",
+ "children": [
+ { "id": "Arrays", "value": 8258 },
+ { "id": "Colors", "value": 10001 },
+ { "id": "Dates", "value": 8217 },
+ { "id": "Displays", "value": 12555 },
+ { "id": "Filter", "value": 2324 },
+ { "id": "Geometry", "value": 10993 },
+ {
+ "id": "heap",
+ "children": [
+ { "id": "FibonacciHeap", "value": 9354 },
+ { "id": "HeapNode", "value": 1233 }
+ ]
+ },
+ { "id": "IEvaluable", "value": 335 },
+ { "id": "IPredicate", "value": 383 },
+ { "id": "IValueProxy", "value": 874 },
+ {
+ "id": "math",
+ "children": [
+ { "id": "DenseMatrix", "value": 3165 },
+ { "id": "IMatrix", "value": 2815 },
+ { "id": "SparseMatrix", "value": 3366 }
+ ]
+ },
+ { "id": "Maths", "value": 17705 },
+ { "id": "Orientation", "value": 1486 },
+ {
+ "id": "palette",
+ "children": [
+ { "id": "ColorPalette", "value": 6367 },
+ { "id": "Palette", "value": 1229 },
+ { "id": "ShapePalette", "value": 2059 },
+ { "id": "SizePalette", "value": 2291 }
+ ]
+ },
+ { "id": "Property", "value": 5559 },
+ { "id": "Shapes", "value": 19118 },
+ { "id": "Sort", "value": 6887 },
+ { "id": "Stats", "value": 6557 },
+ { "id": "Strings", "value": 22026 }
+ ]
+ },
+ {
+ "id": "vis",
+ "children": [
+ {
+ "id": "axis",
+ "children": [
+ { "id": "Axes", "value": 1302 },
+ { "id": "Axis", "value": 24593 },
+ { "id": "AxisGridLine", "value": 652 },
+ { "id": "AxisLabel", "value": 636 },
+ { "id": "CartesianAxes", "value": 6703 }
+ ]
+ },
+ {
+ "id": "controls",
+ "children": [
+ { "id": "AnchorControl", "value": 2138 },
+ { "id": "ClickControl", "value": 3824 },
+ { "id": "Control", "value": 1353 },
+ { "id": "ControlList", "value": 4665 },
+ { "id": "DragControl", "value": 2649 },
+ { "id": "ExpandControl", "value": 2832 },
+ { "id": "HoverControl", "value": 4896 },
+ { "id": "IControl", "value": 763 },
+ { "id": "PanZoomControl", "value": 5222 },
+ { "id": "SelectionControl", "value": 7862 },
+ { "id": "TooltipControl", "value": 8435 }
+ ]
+ },
+ {
+ "id": "data",
+ "children": [
+ { "id": "Data", "value": 20544 },
+ { "id": "DataList", "value": 19788 },
+ { "id": "DataSprite", "value": 10349 },
+ { "id": "EdgeSprite", "value": 3301 },
+ { "id": "NodeSprite", "value": 19382 },
+ {
+ "id": "render",
+ "children": [
+ { "id": "ArrowType", "value": 698 },
+ { "id": "EdgeRenderer", "value": 5569 },
+ { "id": "IRenderer", "value": 353 },
+ { "id": "ShapeRenderer", "value": 2247 }
+ ]
+ },
+ { "id": "ScaleBinding", "value": 11275 },
+ { "id": "Tree", "value": 7147 },
+ { "id": "TreeBuilder", "value": 9930 }
+ ]
+ },
+ {
+ "id": "events",
+ "children": [
+ { "id": "DataEvent", "value": 2313 },
+ { "id": "SelectionEvent", "value": 1880 },
+ { "id": "TooltipEvent", "value": 1701 },
+ { "id": "VisualizationEvent", "value": 1117 }
+ ]
+ },
+ {
+ "id": "legend",
+ "children": [
+ { "id": "Legend", "value": 20859 },
+ { "id": "LegendItem", "value": 4614 },
+ { "id": "LegendRange", "value": 10530 }
+ ]
+ },
+ {
+ "id": "operator",
+ "children": [
+ {
+ "id": "distortion",
+ "children": [
+ { "id": "BifocalDistortion", "value": 4461 },
+ { "id": "Distortion", "value": 6314 },
+ { "id": "FisheyeDistortion", "value": 3444 }
+ ]
+ },
+ {
+ "id": "encoder",
+ "children": [
+ { "id": "ColorEncoder", "value": 3179 },
+ { "id": "Encoder", "value": 4060 },
+ { "id": "PropertyEncoder", "value": 4138 },
+ { "id": "ShapeEncoder", "value": 1690 },
+ { "id": "SizeEncoder", "value": 1830 }
+ ]
+ },
+ {
+ "id": "filter",
+ "children": [
+ { "id": "FisheyeTreeFilter", "value": 5219 },
+ { "id": "GraphDistanceFilter", "value": 3165 },
+ { "id": "VisibilityFilter", "value": 3509 }
+ ]
+ },
+ { "id": "IOperator", "value": 1286 },
+ {
+ "id": "label",
+ "children": [
+ { "id": "Labeler", "value": 9956 },
+ { "id": "RadialLabeler", "value": 3899 },
+ { "id": "StackedAreaLabeler", "value": 3202 }
+ ]
+ },
+ {
+ "id": "layout",
+ "children": [
+ { "id": "AxisLayout", "value": 6725 },
+ { "id": "BundledEdgeRouter", "value": 3727 },
+ { "id": "CircleLayout", "value": 9317 },
+ { "id": "CirclePackingLayout", "value": 12003 },
+ { "id": "DendrogramLayout", "value": 4853 },
+ { "id": "ForceDirectedLayout", "value": 8411 },
+ { "id": "IcicleTreeLayout", "value": 4864 },
+ { "id": "IndentedTreeLayout", "value": 3174 },
+ { "id": "Layout", "value": 7881 },
+ { "id": "NodeLinkTreeLayout", "value": 12870 },
+ { "id": "PieLayout", "value": 2728 },
+ { "id": "RadialTreeLayout", "value": 12348 },
+ { "id": "RandomLayout", "value": 870 },
+ { "id": "StackedAreaLayout", "value": 9121 },
+ { "id": "TreeMapLayout", "value": 9191 }
+ ]
+ },
+ { "id": "Operator", "value": 2490 },
+ { "id": "OperatorList", "value": 5248 },
+ { "id": "OperatorSequence", "value": 4190 },
+ { "id": "OperatorSwitch", "value": 2581 },
+ { "id": "SortOperator", "value": 2023 }
+ ]
+ },
+ { "id": "Visualization", "value": 16540 }
+ ]
+ }
+ ]
+}
diff --git a/packages/g6/__tests__/demos/case-radial-dendrogram.ts b/packages/g6/__tests__/demos/case-radial-dendrogram.ts
new file mode 100644
index 00000000000..24dbb6d33e6
--- /dev/null
+++ b/packages/g6/__tests__/demos/case-radial-dendrogram.ts
@@ -0,0 +1,58 @@
+import data from '@@/dataset/flare.json';
+import { Graph, treeToGraphData } from '@antv/g6';
+
+export const caseRadialDendrogram: TestCase = async (context) => {
+ const graph = new Graph({
+ ...context,
+ autoFit: 'view',
+ data: treeToGraphData(data),
+ node: {
+ style: {
+ size: 14,
+ labelText: (d) => d.id,
+ labelBackground: true,
+ },
+ state: {
+ active: {
+ fill: '#00C9C9',
+ },
+ },
+ },
+ edge: {
+ type: 'cubic-radial',
+ style: {
+ lineWidth: 2,
+ },
+ state: {
+ active: {
+ stroke: '#009999',
+ },
+ },
+ },
+ layout: [
+ {
+ type: 'dendrogram',
+ radial: true,
+ nodeSep: 30,
+ rankSep: 200,
+ },
+ ],
+ behaviors: [
+ 'drag-canvas',
+ 'zoom-canvas',
+ 'drag-element',
+ {
+ key: 'hover-activate',
+ type: 'hover-activate',
+ degree: 5,
+ direction: 'in',
+ inactiveState: 'inactive',
+ },
+ ],
+ transforms: ['position-radial-labels'],
+ });
+
+ await graph.render();
+
+ return graph;
+};
diff --git a/packages/g6/__tests__/demos/element-edge-cubic-radial.ts b/packages/g6/__tests__/demos/element-edge-cubic-radial.ts
new file mode 100644
index 00000000000..5e3c825e9fc
--- /dev/null
+++ b/packages/g6/__tests__/demos/element-edge-cubic-radial.ts
@@ -0,0 +1,25 @@
+import data from '@@/dataset/algorithm-category.json';
+import { Graph, treeToGraphData } from '@antv/g6';
+
+export const elementEdgeCubicRadial: TestCase = async (context) => {
+ const graph = new Graph({
+ ...context,
+ autoFit: 'view',
+ data: treeToGraphData(data),
+ edge: {
+ type: 'cubic-radial',
+ },
+ layout: [
+ {
+ type: 'dendrogram',
+ radial: true,
+ nodeSep: 30,
+ rankSep: 200,
+ },
+ ],
+ });
+
+ await graph.render();
+
+ return graph;
+};
diff --git a/packages/g6/__tests__/demos/index.ts b/packages/g6/__tests__/demos/index.ts
index 8c88fae237e..cd2322be201 100644
--- a/packages/g6/__tests__/demos/index.ts
+++ b/packages/g6/__tests__/demos/index.ts
@@ -25,6 +25,7 @@ export { caseDecisionTree } from './case-decision-tree';
export { caseIndentedTree } from './case-indented-tree';
export { caseMindmap } from './case-mindmap';
export { caseOrgChart } from './case-org-chart';
+export { caseRadialDendrogram } from './case-radial-dendrogram';
export { caseWhyDoCats } from './case-why-do-cats';
export { commonGraph } from './common-graph';
export { controllerViewport } from './controller-viewport';
@@ -34,6 +35,7 @@ export { elementCombo } from './element-combo';
export { elementEdgeArrow } from './element-edge-arrow';
export { elementEdgeCubic } from './element-edge-cubic';
export { elementEdgeCubicHorizontal } from './element-edge-cubic-horizontal';
+export { elementEdgeCubicRadial } from './element-edge-cubic-radial';
export { elementEdgeCubicVertical } from './element-edge-cubic-vertical';
export { elementEdgeCustomArrow } from './element-edge-custom-arrow';
export { elementEdgeLine } from './element-edge-line';
@@ -87,6 +89,7 @@ export { layoutCustomIterative } from './layout-custom-iterative';
export { layoutD3Force } from './layout-d3-force';
export { layoutDagre } from './layout-dagre';
export { layoutDendrogramBasic } from './layout-dendrogram-basic';
+export { layoutDendrogramRadial } from './layout-dendrogram-radial';
export { layoutDendrogramTb } from './layout-dendrogram-tb';
export { layoutForce } from './layout-force';
export { layoutForceCollision } from './layout-force-collision';
@@ -132,5 +135,6 @@ export { pluginTooltip } from './plugin-tooltip';
export { pluginWatermark } from './plugin-watermark';
export { pluginWatermarkImage } from './plugin-watermark-image';
export { theme } from './theme';
+export { transformPositionRadialLabels } from './transform-position-radial-labels';
export { transformProcessParallelEdges } from './transform-process-parallel-edges';
export { viewportFit } from './viewport-fit';
diff --git a/packages/g6/__tests__/demos/layout-dendrogram-radial.ts b/packages/g6/__tests__/demos/layout-dendrogram-radial.ts
new file mode 100644
index 00000000000..2a607c34c7e
--- /dev/null
+++ b/packages/g6/__tests__/demos/layout-dendrogram-radial.ts
@@ -0,0 +1,27 @@
+import data from '@@/dataset/algorithm-category.json';
+import { Graph, treeToGraphData } from '@antv/g6';
+
+export const layoutDendrogramRadial: TestCase = async (context) => {
+ const graph = new Graph({
+ ...context,
+ autoFit: 'view',
+ data: treeToGraphData(data),
+ node: {
+ style: {
+ labelText: (d) => d.id,
+ },
+ },
+ layout: [
+ {
+ type: 'dendrogram',
+ radial: true,
+ nodeSep: 30,
+ rankSep: 200,
+ },
+ ],
+ });
+
+ await graph.render();
+
+ return graph;
+};
diff --git a/packages/g6/__tests__/demos/transform-position-radial-labels.ts b/packages/g6/__tests__/demos/transform-position-radial-labels.ts
new file mode 100644
index 00000000000..50b399a594b
--- /dev/null
+++ b/packages/g6/__tests__/demos/transform-position-radial-labels.ts
@@ -0,0 +1,28 @@
+import data from '@@/dataset/algorithm-category.json';
+import { Graph, treeToGraphData } from '@antv/g6';
+
+export const transformPositionRadialLabels: TestCase = async (context) => {
+ const graph = new Graph({
+ ...context,
+ autoFit: 'view',
+ data: treeToGraphData(data),
+ node: {
+ style: {
+ labelText: (d) => d.id,
+ },
+ },
+ layout: [
+ {
+ type: 'dendrogram',
+ radial: true,
+ nodeSep: 30,
+ rankSep: 200,
+ },
+ ],
+ transforms: ['position-radial-labels'],
+ });
+
+ await graph.render();
+
+ return graph;
+};
diff --git a/packages/g6/__tests__/snapshots/elements/edges/cubic-radial/default.svg b/packages/g6/__tests__/snapshots/elements/edges/cubic-radial/default.svg
new file mode 100644
index 00000000000..efdf6ba145f
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/elements/edges/cubic-radial/default.svg
@@ -0,0 +1,374 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/snapshots/transforms/transform-position-radial-labels/default.svg b/packages/g6/__tests__/snapshots/transforms/transform-position-radial-labels/default.svg
new file mode 100644
index 00000000000..825033c8351
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/transforms/transform-position-radial-labels/default.svg
@@ -0,0 +1,591 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/unit/elements/edges/cubic-radial.spec.ts b/packages/g6/__tests__/unit/elements/edges/cubic-radial.spec.ts
new file mode 100644
index 00000000000..67d85e7c2bd
--- /dev/null
+++ b/packages/g6/__tests__/unit/elements/edges/cubic-radial.spec.ts
@@ -0,0 +1,11 @@
+import { elementEdgeCubicRadial } from '@@/demos';
+import { createDemoGraph } from '@@/utils';
+
+describe('element edge cubic radial', () => {
+ it('render', async () => {
+ const graph = await createDemoGraph(elementEdgeCubicRadial);
+ await expect(graph).toMatchSnapshot(__filename);
+
+ graph.destroy();
+ });
+});
diff --git a/packages/g6/__tests__/unit/registry.spec.ts b/packages/g6/__tests__/unit/registry.spec.ts
index 4ea1275c3ff..b0a95b012d3 100644
--- a/packages/g6/__tests__/unit/registry.spec.ts
+++ b/packages/g6/__tests__/unit/registry.spec.ts
@@ -3,6 +3,7 @@ import {
CircleCombo,
Cubic,
CubicHorizontal,
+ CubicRadial,
CubicVertical,
Diamond,
Donut,
@@ -47,6 +48,7 @@ describe('registry', () => {
quadratic: Quadratic,
'cubic-horizontal': CubicHorizontal,
'cubic-vertical': CubicVertical,
+ 'cubic-radial': CubicRadial,
});
expect(getExtensions(ExtensionCategory.COMBO)).toEqual({
circle: CircleCombo,
diff --git a/packages/g6/__tests__/unit/runtime/data.spec.ts b/packages/g6/__tests__/unit/runtime/data.spec.ts
index 8ccefef6052..bf4d4a6032b 100644
--- a/packages/g6/__tests__/unit/runtime/data.spec.ts
+++ b/packages/g6/__tests__/unit/runtime/data.spec.ts
@@ -590,6 +590,14 @@ describe('DataController', () => {
expect(controller.getNodeLikeData()).toEqual([...data.combos, ...data.nodes]);
});
+ it('getRootsData', () => {
+ const controller = new DataController();
+
+ controller.addData(treeToGraphData(tree));
+
+ expect(controller.getRootsData('tree').map(idOf)).toEqual(['Modeling Methods']);
+ });
+
it('getAncestorsData getParentData getChildrenData', () => {
const controller = new DataController();
diff --git a/packages/g6/__tests__/unit/transforms/transform-position-radial-labels.spec.ts b/packages/g6/__tests__/unit/transforms/transform-position-radial-labels.spec.ts
new file mode 100644
index 00000000000..75049143e46
--- /dev/null
+++ b/packages/g6/__tests__/unit/transforms/transform-position-radial-labels.spec.ts
@@ -0,0 +1,11 @@
+import { transformPositionRadialLabels } from '@@/demos';
+import { createDemoGraph } from '@@/utils';
+
+describe('transform position radial labels', () => {
+ it('render', async () => {
+ const graph = await createDemoGraph(transformPositionRadialLabels);
+ await expect(graph).toMatchSnapshot(__filename);
+
+ graph.destroy();
+ });
+});
diff --git a/packages/g6/__tests__/unit/utils/point.spec.ts b/packages/g6/__tests__/unit/utils/point.spec.ts
index a6ced8c43ae..e52a9efe7bc 100644
--- a/packages/g6/__tests__/unit/utils/point.spec.ts
+++ b/packages/g6/__tests__/unit/utils/point.spec.ts
@@ -9,6 +9,7 @@ import {
getEllipseIntersectPoint,
getPolygonIntersectPoint,
getRectIntersectPoint,
+ getSymmetricPoint,
isCollinear,
isHorizontal,
isOrthogonal,
@@ -100,6 +101,11 @@ describe('Point Functions', () => {
expect(isCollinear([100, 100], [50, 50], [150, 100])).toEqual(false);
});
+ it('getSymmetricPoint', () => {
+ expect(getSymmetricPoint([50, 50], [100, 100])).toEqual([150, 150]);
+ expect(getSymmetricPoint([-50, -50], [0, 0])).toEqual([50, 50]);
+ });
+
it('getRectIntersectPoint', () => {
const rect = new Rect({
style: {
@@ -110,6 +116,7 @@ describe('Point Functions', () => {
},
});
expect(getRectIntersectPoint([110, 110], rect.getBounds())).toEqual([102, 102]);
+ expect(getRectIntersectPoint([110, 110], rect.getBounds(), true)).toEqual([100, 100]);
});
it('getEllipseIntersectPoint', () => {
@@ -121,6 +128,7 @@ describe('Point Functions', () => {
},
});
expect(getEllipseIntersectPoint([110, 100], circle.getBounds())).toEqual([101, 100]);
+ expect(getEllipseIntersectPoint([110, 100], circle.getBounds(), true)).toEqual([99, 100]);
const circle2 = new Circle({
style: {
diff --git a/packages/g6/__tests__/unit/utils/relation.spec.ts b/packages/g6/__tests__/unit/utils/relation.spec.ts
index e23869249a6..8c12b080409 100644
--- a/packages/g6/__tests__/unit/utils/relation.spec.ts
+++ b/packages/g6/__tests__/unit/utils/relation.spec.ts
@@ -32,11 +32,15 @@ describe('relation', () => {
expect(getElementNthDegreeIds(graph, 'edge', '1-2', 1)).toEqual(['1', '2', '1-2']);
expect(getElementNthDegreeIds(graph, 'edge', '1-2', 2)).toEqual(['1', '1-2', '1-3', '2', '3', '2-4', '4']);
expect(getElementNthDegreeIds(graph, 'combo', 'combo1', 1)).toEqual(['combo1', 'combo1-6', '6']);
+ expect(getElementNthDegreeIds(graph, 'node', '1', 1, 'in')).toEqual(['1']);
+ expect(getElementNthDegreeIds(graph, 'node', '1', 1, 'out')).toEqual(['1', '1-2', '1-3', '2', '3']);
});
it('getNodeNthDegreeIds', () => {
expect(getNodeNthDegreeIds(graph, '1', 0)).toEqual(['1']);
expect(getNodeNthDegreeIds(graph, '1', 1)).toEqual(['1', '1-2', '1-3', '2', '3']);
expect(getNodeNthDegreeIds(graph, '1', 2)).toEqual(['1', '1-2', '1-3', '2', '2-4', '3', '3-5', '4', '5']);
+ expect(getNodeNthDegreeIds(graph, '1', 1, 'in')).toEqual(['1']);
+ expect(getNodeNthDegreeIds(graph, '1', 1, 'out')).toEqual(['1', '1-2', '1-3', '2', '3']);
});
});
diff --git a/packages/g6/__tests__/unit/utils/vector.spec.ts b/packages/g6/__tests__/unit/utils/vector.spec.ts
index 9cd1c61bcce..80480ce82e2 100644
--- a/packages/g6/__tests__/unit/utils/vector.spec.ts
+++ b/packages/g6/__tests__/unit/utils/vector.spec.ts
@@ -11,6 +11,7 @@ import {
multiply,
normalize,
perpendicular,
+ rad,
scale,
subtract,
toVector2,
@@ -105,4 +106,9 @@ describe('Vector Functions', () => {
expect(toVector3([1, 2, 3])).toEqual([1, 2, 3]);
expect(toVector3([1, 2])).toEqual([1, 2, 0]);
});
+
+ it('rad', () => {
+ expect(rad([1, 0])).toEqual(0);
+ expect(rad([0, 1])).toEqual(Math.PI / 2);
+ });
});
diff --git a/packages/g6/package.json b/packages/g6/package.json
index 09f051892db..8356392b55e 100644
--- a/packages/g6/package.json
+++ b/packages/g6/package.json
@@ -63,7 +63,7 @@
"@antv/g-canvas": "^2.0.10",
"@antv/g-plugin-dragndrop": "^2.0.8",
"@antv/graphlib": "^2.0.3",
- "@antv/hierarchy": "^0.6.12",
+ "@antv/hierarchy": "^0.6.13",
"@antv/layout": "^1.2.14-beta.6",
"@antv/util": "^3.3.7",
"bubblesets-js": "^2.3.3",
diff --git a/packages/g6/src/behaviors/hover-activate.ts b/packages/g6/src/behaviors/hover-activate.ts
index 6d6e0c3267a..7ad59d30f54 100644
--- a/packages/g6/src/behaviors/hover-activate.ts
+++ b/packages/g6/src/behaviors/hover-activate.ts
@@ -2,7 +2,7 @@ import { isFunction } from '@antv/util';
import { CommonEvent } from '../constants';
import { ELEMENT_TYPES } from '../constants/element';
import type { RuntimeContext } from '../runtime/types';
-import type { Element, ElementType, ID, IDragEvent, IPointerEvent, State } from '../types';
+import type { EdgeDirection, Element, ElementType, ID, IDragEvent, IPointerEvent, State } from '../types';
import { idsOf } from '../utils/id';
import { getElementNthDegreeIds } from '../utils/relation';
import type { BaseBehaviorOptions } from './base-behavior';
@@ -39,6 +39,19 @@ export interface HoverActivateOptions extends BaseBehaviorOptions {
* @defaultValue 0
*/
degree?: number;
+ /**
+ * 指定边的方向
+ * - `'both'`: 表示激活当前节点的所有关系
+ * - `'in'`: 表示激活当前节点的入边和入节点
+ * - `'out'`: 表示激活当前节点的出边和出节点
+ *
+ * Specify the direction of the edge
+ * - `'both'`: Activate all relationships of the current node
+ * - `'in'`: Activate the incoming edges and nodes of the current node
+ * - `'out'`: Activate the outgoing edges and nodes of the current node
+ * @defaultValue 'both'
+ */
+ direction?: EdgeDirection;
/**
* 激活元素的状态,默认为 `active`
*
@@ -80,6 +93,7 @@ export class HoverActivate extends BaseBehavior {
animation: false,
enable: true,
degree: 0,
+ direction: 'both',
state: 'active',
inactiveState: undefined,
};
@@ -123,11 +137,11 @@ export class HoverActivate extends BaseBehavior {
if (!this.options.state && !this.options.inactiveState) return;
const { graph } = this.context;
- const { state, degree, animation, inactiveState } = this.options;
+ const { state, degree, direction, animation, inactiveState } = this.options;
const { targetType, target } = event;
const activeIds = degree
- ? getElementNthDegreeIds(graph, targetType as ElementType, target.id, degree)
+ ? getElementNthDegreeIds(graph, targetType as ElementType, target.id, degree, direction)
: [target.id];
const states: Record = {};
diff --git a/packages/g6/src/elements/combos/circle.ts b/packages/g6/src/elements/combos/circle.ts
index 2cd059fae34..a1340cb6505 100644
--- a/packages/g6/src/elements/combos/circle.ts
+++ b/packages/g6/src/elements/combos/circle.ts
@@ -54,8 +54,8 @@ export class CircleCombo extends BaseCombo {
return [expandedR * 2, expandedR * 2, 0];
}
- public getIntersectPoint(point: Point): Point {
+ public getIntersectPoint(point: Point, useExtendedLine = false): Point {
const keyShapeBounds = this.getShape('key').getBounds();
- return getEllipseIntersectPoint(point, keyShapeBounds);
+ return getEllipseIntersectPoint(point, keyShapeBounds, useExtendedLine);
}
}
diff --git a/packages/g6/src/elements/edges/cubic-radial.ts b/packages/g6/src/elements/edges/cubic-radial.ts
new file mode 100644
index 00000000000..b6cb13942d0
--- /dev/null
+++ b/packages/g6/src/elements/edges/cubic-radial.ts
@@ -0,0 +1,77 @@
+import type { DisplayObjectConfig } from '@antv/g';
+import type { NodeData } from '../../spec';
+import type { Point } from '../../types';
+import { positionOf } from '../../utils/position';
+import { mergeOptions } from '../../utils/style';
+import { distance, rad, subtract } from '../../utils/vector';
+import type { CubicStyleProps } from './cubic';
+import { Cubic } from './cubic';
+
+/**
+ * 径向贝塞尔曲线样式配置项
+ *
+ * Radial cubic style props
+ */
+export interface CubicRadialStyleProps extends CubicStyleProps {}
+
+/**
+ * 径向贝塞尔曲线
+ *
+ * Radial cubic edge
+ */
+export class CubicRadial extends Cubic {
+ static defaultStyleProps: Partial = {
+ curvePosition: 0.5,
+ curveOffset: 20,
+ };
+
+ constructor(options: DisplayObjectConfig) {
+ super(mergeOptions({ style: CubicRadial.defaultStyleProps }, options));
+ }
+
+ private get ref(): NodeData {
+ return this.context.model.getRootsData()[0];
+ }
+
+ protected getEndpoints(attributes: Required): [Point, Point] {
+ if (this.sourceNode.id === this.ref.id) {
+ return super.getEndpoints(attributes);
+ }
+
+ const refPoint = positionOf(this.ref);
+ const sourcePoint = this.sourceNode.getIntersectPoint(refPoint, true);
+ const targetPoint = this.targetNode.getIntersectPoint(refPoint);
+
+ return [sourcePoint, targetPoint];
+ }
+
+ private toRadialCoordinate(p: Point) {
+ const refPoint = positionOf(this.ref);
+ const r = distance(p, refPoint);
+ const radian = rad(subtract(p, refPoint));
+ return [r, radian];
+ }
+
+ protected getControlPoints(
+ sourcePoint: Point,
+ targetPoint: Point,
+ curvePosition: [number, number],
+ curveOffset: [number, number],
+ ): [Point, Point] {
+ const [r1, rad1] = this.toRadialCoordinate(sourcePoint);
+ const [r2] = this.toRadialCoordinate(targetPoint);
+
+ const rDist = r2 - r1;
+
+ return [
+ [
+ sourcePoint[0] + (rDist * curvePosition[0] + curveOffset[0]) * Math.cos(rad1),
+ sourcePoint[1] + (rDist * curvePosition[0] + curveOffset[0]) * Math.sin(rad1),
+ ],
+ [
+ targetPoint[0] - (rDist * curvePosition[1] - curveOffset[0]) * Math.cos(rad1),
+ targetPoint[1] - (rDist * curvePosition[1] - curveOffset[0]) * Math.sin(rad1),
+ ],
+ ];
+ }
+}
diff --git a/packages/g6/src/elements/edges/index.ts b/packages/g6/src/elements/edges/index.ts
index dcbc4068d01..fe53486a4e8 100644
--- a/packages/g6/src/elements/edges/index.ts
+++ b/packages/g6/src/elements/edges/index.ts
@@ -1,6 +1,7 @@
export { BaseEdge } from './base-edge';
export { Cubic } from './cubic';
export { CubicHorizontal } from './cubic-horizontal';
+export { CubicRadial } from './cubic-radial';
export { CubicVertical } from './cubic-vertical';
export { Line } from './line';
export { Polyline } from './polyline';
@@ -9,6 +10,7 @@ export { Quadratic } from './quadratic';
export type { BaseEdgeStyleProps } from './base-edge';
export type { CubicStyleProps } from './cubic';
export type { CubicHorizontalStyleProps } from './cubic-horizontal';
+export type { CubicRadialStyleProps } from './cubic-radial';
export type { CubicVerticalStyleProps } from './cubic-vertical';
export type { LineStyleProps } from './line';
export type { PolylineStyleProps } from './polyline';
diff --git a/packages/g6/src/elements/nodes/base-node.ts b/packages/g6/src/elements/nodes/base-node.ts
index f6ef8aa3fd1..bfd91bd038e 100644
--- a/packages/g6/src/elements/nodes/base-node.ts
+++ b/packages/g6/src/elements/nodes/base-node.ts
@@ -354,11 +354,12 @@ export abstract class BaseNode, container: Group): void {
diff --git a/packages/g6/src/elements/nodes/circle.ts b/packages/g6/src/elements/nodes/circle.ts
index 4f806dbdd57..454c053a698 100644
--- a/packages/g6/src/elements/nodes/circle.ts
+++ b/packages/g6/src/elements/nodes/circle.ts
@@ -45,8 +45,8 @@ export class Circle extends BaseNode {
return style ? ({ width: size, height: size, ...style } as IconStyleProps) : false;
}
- public getIntersectPoint(point: Point): Point {
+ public getIntersectPoint(point: Point, useExtendedLine = false): Point {
const keyShapeBounds = this.getShape('key').getBounds();
- return getEllipseIntersectPoint(point, keyShapeBounds);
+ return getEllipseIntersectPoint(point, keyShapeBounds, useExtendedLine);
}
}
diff --git a/packages/g6/src/elements/nodes/ellipse.ts b/packages/g6/src/elements/nodes/ellipse.ts
index a63ba0050cc..c2ce377516b 100644
--- a/packages/g6/src/elements/nodes/ellipse.ts
+++ b/packages/g6/src/elements/nodes/ellipse.ts
@@ -51,8 +51,8 @@ export class Ellipse extends BaseNode {
return style ? ({ width: size, height: size, ...style } as IconStyleProps) : false;
}
- public getIntersectPoint(point: Point): Point {
+ public getIntersectPoint(point: Point, useExtendedLine = false): Point {
const keyShapeBounds = this.getShape('key').getBounds();
- return getEllipseIntersectPoint(point, keyShapeBounds);
+ return getEllipseIntersectPoint(point, keyShapeBounds, useExtendedLine);
}
}
diff --git a/packages/g6/src/elements/shapes/polygon.ts b/packages/g6/src/elements/shapes/polygon.ts
index 7499f504a34..8a90bf90a6d 100644
--- a/packages/g6/src/elements/shapes/polygon.ts
+++ b/packages/g6/src/elements/shapes/polygon.ts
@@ -43,9 +43,9 @@ export abstract class Polygon e
protected abstract getPoints(attributes: Required): Point[];
- public getIntersectPoint(point: Point): Point {
+ public getIntersectPoint(point: Point, useExtendedLine = false): Point {
const { points } = this.getShape('key').attributes;
const center: Point = [+(this.attributes?.x || 0), +(this.attributes?.y || 0)];
- return getPolygonIntersectPoint(point, center, points!).point;
+ return getPolygonIntersectPoint(point, center, points!, true, useExtendedLine).point;
}
}
diff --git a/packages/g6/src/exports.ts b/packages/g6/src/exports.ts
index 7de656410d4..f1389e529a3 100644
--- a/packages/g6/src/exports.ts
+++ b/packages/g6/src/exports.ts
@@ -27,7 +27,17 @@ export {
NodeEvent,
} from './constants';
export { BaseCombo, CircleCombo, RectCombo } from './elements/combos';
-export { BaseEdge, Cubic, CubicHorizontal, CubicVertical, Line, Polyline, Quadratic } from './elements/edges';
+export {
+ BaseEdge,
+ Cubic,
+ CubicHorizontal,
+ CubicRadial,
+ CubicVertical,
+ Line,
+ Polyline,
+ Quadratic,
+} from './elements/edges';
+export { effect } from './elements/effect';
export {
BaseNode,
Circle,
@@ -47,20 +57,20 @@ export {
BaseLayout,
CircularLayout,
ComboCombinedLayout,
+ compactBox as CompactBoxLayout,
ConcentricLayout,
D3ForceLayout,
DagreLayout,
+ dendrogram as DendrogramLayout,
ForceAtlas2Layout,
ForceLayout,
FruchtermanLayout,
GridLayout,
+ indented as IndentedLayout,
MDSLayout,
+ mindmap as MindmapLayout,
RadialLayout,
RandomLayout,
- compactBox,
- dendrogram,
- indented,
- mindmap,
} from './layouts';
export {
BasePlugin,
@@ -84,10 +94,11 @@ export {
export { getExtension, getExtensions } from './registry/get';
export { register } from './registry/register';
export { Graph } from './runtime/graph';
-export { BaseTransform } from './transforms';
+export { BaseTransform, PositionRadialLabels, ProcessParallelEdges } from './transforms';
export { isCollapsed } from './utils/collapsibility';
export { idOf } from './utils/id';
export { invokeLayoutMethod } from './utils/layout';
+export { positionOf } from './utils/position';
export { omitStyleProps, subStyleProps } from './utils/prefix';
export { Shortcut } from './utils/shortcut';
export { parseSize } from './utils/size';
@@ -135,6 +146,7 @@ export type { BaseComboStyleProps, CircleComboStyleProps, RectComboStyleProps }
export type {
BaseEdgeStyleProps,
CubicHorizontalStyleProps,
+ CubicRadialStyleProps,
CubicStyleProps,
CubicVerticalStyleProps,
LineStyleProps,
@@ -205,7 +217,7 @@ export type { CustomBehaviorOption } from './spec/behavior';
export type { AnimationStage } from './spec/element/animation';
export type { LayoutOptions, STDLayoutOptions, SingleLayoutOptions } from './spec/layout';
export type { CustomPluginOption } from './spec/plugin';
-export type { BaseTransformOptions } from './transforms';
+export type { BaseTransformOptions, PositionRadialLabelsOptions, ProcessParallelEdgesOptions } from './transforms';
export type { DrawData } from './transforms/types';
export type {
BaseElementStyleProps,
diff --git a/packages/g6/src/registry/build-in.ts b/packages/g6/src/registry/build-in.ts
index 8c0aa31bc85..3197e525432 100644
--- a/packages/g6/src/registry/build-in.ts
+++ b/packages/g6/src/registry/build-in.ts
@@ -32,6 +32,7 @@ import {
CircleCombo,
Cubic,
CubicHorizontal,
+ CubicRadial,
CubicVertical,
Diamond,
Donut,
@@ -92,6 +93,7 @@ import {
CollapseExpandCombo,
CollapseExpandNode,
GetEdgeActualEnds,
+ PositionRadialLabels,
ProcessParallelEdges,
UpdateRelatedEdge,
} from '../transforms';
@@ -138,6 +140,7 @@ const BUILT_IN_EXTENSIONS: ExtensionRegistry = {
polyline: Polyline,
quadratic: Quadratic,
'cubic-horizontal': CubicHorizontal,
+ 'cubic-radial': CubicRadial,
'cubic-vertical': CubicVertical,
},
layout: {
@@ -207,6 +210,7 @@ const BUILT_IN_EXTENSIONS: ExtensionRegistry = {
'collapse-expand-node': CollapseExpandNode,
'process-parallel-edges': ProcessParallelEdges,
'get-edge-actual-ends': GetEdgeActualEnds,
+ 'position-radial-labels': PositionRadialLabels,
},
shape: {
circle: GCircle,
diff --git a/packages/g6/src/runtime/data.ts b/packages/g6/src/runtime/data.ts
index aa062399db9..1e41c0be3d3 100644
--- a/packages/g6/src/runtime/data.ts
+++ b/packages/g6/src/runtime/data.ts
@@ -166,6 +166,10 @@ export class DataController {
}, [] as ComboData[]);
}
+ public getRootsData(hierarchyKey: HierarchyKey = TREE_KEY) {
+ return this.model.getRoots(hierarchyKey).map(toG6Data);
+ }
+
public getAncestorsData(id: ID, hierarchyKey: HierarchyKey): NodeLikeData[] {
const { model } = this;
if (!model.hasNode(id) || !model.hasTreeStructure(hierarchyKey)) return [];
diff --git a/packages/g6/src/runtime/layout.ts b/packages/g6/src/runtime/layout.ts
index 26f9fdb4d2f..061d2312172 100644
--- a/packages/g6/src/runtime/layout.ts
+++ b/packages/g6/src/runtime/layout.ts
@@ -69,6 +69,13 @@ export class LayoutController {
}
}
emit(graph, new GraphLifeCycleEvent(GraphEvent.AFTER_LAYOUT));
+ this.transformDataAfterLayout();
+ }
+
+ private transformDataAfterLayout() {
+ const transforms = this.context.transform.getTransformInstance();
+
+ Object.values(transforms).forEach((transform) => transform.afterLayout());
}
/**
diff --git a/packages/g6/src/transforms/base-transform.ts b/packages/g6/src/transforms/base-transform.ts
index 5fa9f67ade7..15c7a998647 100644
--- a/packages/g6/src/transforms/base-transform.ts
+++ b/packages/g6/src/transforms/base-transform.ts
@@ -9,4 +9,6 @@ export abstract class BaseTransform 根据径向布局自动调整节点标签样式的配置项
+ *
+ * Options for automatically adjusting the style of node labels according to the radial layout
+ */
+export interface PositionRadialLabelsOptions extends BaseTransformOptions {
+ /**
+ * 偏移量
+ *
+ * Offset
+ */
+ offset?: number;
+}
+
+/**
+ * 根据径向布局自动调整节点标签样式,包括位置和旋转角度
+ *
+ * Automatically adjust the style of node labels according to the radial layout, including position and rotation angle
+ */
+export class PositionRadialLabels extends BaseTransform {
+ static defaultOptions: Partial = {
+ offset: 5,
+ };
+
+ constructor(context: RuntimeContext, options: PositionRadialLabelsOptions) {
+ super(context, Object.assign({}, PositionRadialLabels.defaultOptions, options));
+ }
+
+ private get ref(): NodeData {
+ return this.context.model.getRootsData()[0];
+ }
+
+ public afterLayout() {
+ const refPoint = positionOf(this.ref);
+
+ const { graph, model } = this.context;
+ const data = model.getData();
+
+ data.nodes?.forEach((datum) => {
+ if (idOf(datum) === idOf(this.ref)) return;
+
+ const radian = rad(subtract(positionOf(datum), refPoint));
+ const isLeft = Math.abs(radian) > Math.PI / 2;
+
+ const isLeaf = !datum.children || datum.children.length === 0;
+ const nodeHalfWidth = parseSize(graph.getElementRenderStyle(idOf(datum)).size)[0] / 2;
+ const offset = (isLeaf ? 1 : -1) * (nodeHalfWidth + this.options.offset);
+
+ const translate = `translate(${offset * Math.cos(radian)},${offset * Math.sin(radian)})`;
+ const rotate = `rotate(${isLeft ? rad2deg(radian) + 180 : rad2deg(radian)}deg)`;
+
+ model.updateNodeData([
+ {
+ id: idOf(datum),
+ style: {
+ labelTextAlign: isLeft === isLeaf ? 'right' : 'left',
+ labelTextBaseline: 'middle',
+ labelTransform: `${translate} ${rotate}`,
+ },
+ },
+ ]);
+ });
+
+ graph.draw();
+ }
+}
diff --git a/packages/g6/src/types/element.ts b/packages/g6/src/types/element.ts
index ad826e301d7..5c9713aff1a 100644
--- a/packages/g6/src/types/element.ts
+++ b/packages/g6/src/types/element.ts
@@ -29,13 +29,14 @@ export interface Node extends DisplayObject, ElementHooks, ElementMethods {
*
* Get the intersection point
* @param point - 外部位置 | external position
+ * @param useExtendedLine - 是否使用延长线 | whether to use the extended line
* @returns 交点位置 | intersection point
* @remarks
* 给定一个外部位置,返回当前节点与该位置的连边与节点的交点位置
*
* Given an external position, return the intersection point of the edge between the current node and the position and the node
*/
- getIntersectPoint(point: Point): Point;
+ getIntersectPoint(point: Point, useExtendedLine?: boolean): Point;
}
/**
diff --git a/packages/g6/src/utils/point.ts b/packages/g6/src/utils/point.ts
index 9095ed3a8f2..2848c762ff3 100644
--- a/packages/g6/src/utils/point.ts
+++ b/packages/g6/src/utils/point.ts
@@ -132,6 +132,18 @@ export function isCollinear(p1: Point, p2: Point, p3: Point): boolean {
return isLinesParallel([p1, p2], [p2, p3]);
}
+/**
+ * 计算一个点相对于另一个点的中心对称点
+ *
+ * Calculate the center symmetric point of a point relative to another point
+ * @param p - 要计算的点 | the point to calculate
+ * @param center - 中心点 | the center point
+ * @returns 中心对称点 | the center symmetric point
+ */
+export function getSymmetricPoint(p: Point, center: Point): Point {
+ return [2 * center[0] - p[0], 2 * center[1] - p[1]];
+}
+
/**
* 获取从多边形中心到给定点的连线与多边形边缘的交点
*
@@ -140,6 +152,7 @@ export function isCollinear(p1: Point, p2: Point, p3: Point): boolean {
* @param center - 多边形中心 | the center of the polygon
* @param points - 多边形顶点 | the vertices of the polygon
* @param isRelativePos - 顶点坐标是否相对中心点 | whether the vertex coordinates are relative to the center point
+ * @param useExtendedLine - 是否使用延长线 | whether to use the extended line
* @returns 交点与相交线段 | intersection and intersecting line segment
*/
export function getPolygonIntersectPoint(
@@ -147,6 +160,7 @@ export function getPolygonIntersectPoint(
center: Point,
points: Point[],
isRelativePos = true,
+ useExtendedLine = false,
): { point: Point; line?: LineSegment } {
for (let i = 0; i < points.length; i++) {
let start = points[i];
@@ -157,7 +171,8 @@ export function getPolygonIntersectPoint(
end = add(center, end);
}
- const intersect = getLinesIntersection([center, p], [start, end]);
+ const refP = useExtendedLine ? getSymmetricPoint(p, center) : p;
+ const intersect = getLinesIntersection([center, refP], [start, end]);
if (intersect) {
return {
point: intersect,
@@ -200,14 +215,15 @@ export function isPointInPolygon(point: Point, points: Point[], start?: number,
}
/**
- * 获取从矩形中心到给定点的连线与矩形边缘的交点
+ * 获取给定点到矩形中心的连线与矩形边缘的交点
*
* Gets the intersection point between the line from the center of a rectangle to a given point and the rectangle's edge
* @param p - 从矩形中心到矩形边缘的连线的外部点 | The point outside the rectangle from which the line to the rectangle's center is drawn
* @param bbox - 矩形包围盒 | the bounding box of the rectangle
+ * @param useExtendedLine - 是否使用延长线 | whether to use the extended line
* @returns 交点 | intersection
*/
-export function getRectIntersectPoint(p: Point, bbox: AABB): Point {
+export function getRectIntersectPoint(p: Point, bbox: AABB, useExtendedLine = false): Point {
const center = getXYByPlacement(bbox, 'center');
const corners = [
getXYByPlacement(bbox, 'left-top'),
@@ -215,21 +231,23 @@ export function getRectIntersectPoint(p: Point, bbox: AABB): Point {
getXYByPlacement(bbox, 'right-bottom'),
getXYByPlacement(bbox, 'left-bottom'),
];
- return getPolygonIntersectPoint(p, center, corners, false).point;
+ return getPolygonIntersectPoint(p, center, corners, false, useExtendedLine).point;
}
/**
- * 获取从椭圆中心到给定点的连线与椭圆边缘的交点
+ * 获取给定点到椭圆中心的连线与椭圆边缘的交点
*
* Gets the intersection point between the line from the center of an ellipse to a given point and the ellipse's edge
* @param p - 从椭圆中心到椭圆边缘的连线的外部点 | The point outside the ellipse from which the line to the ellipse's center is drawn
* The point outside the ellipse from which the line to the ellipse's center is drawn.
* @param bbox - 椭圆包围盒 | the bounding box of the ellipse
+ * @param useExtendedLine - 是否使用延长线 | whether to use the extended line
* @returns 交点 | intersection
*/
-export function getEllipseIntersectPoint(p: Point, bbox: AABB): Point {
+export function getEllipseIntersectPoint(p: Point, bbox: AABB, useExtendedLine = false): Point {
const center = bbox.center;
- const vec = subtract(p, bbox.center);
+ const refP = useExtendedLine ? getSymmetricPoint(p, center) : p;
+ const vec = subtract(refP, bbox.center);
const angle = Math.atan2(vec[1], vec[0]);
if (isNaN(angle)) return center;
diff --git a/packages/g6/src/utils/relation.ts b/packages/g6/src/utils/relation.ts
index 6c8142e76c7..28e7e5eb007 100644
--- a/packages/g6/src/utils/relation.ts
+++ b/packages/g6/src/utils/relation.ts
@@ -1,5 +1,5 @@
import type { Graph } from '../runtime/graph';
-import type { ElementType, ID } from '../types';
+import type { EdgeDirection, ElementType, ID } from '../types';
import { idOf } from './id';
import { bfs } from './traverse';
@@ -17,18 +17,25 @@ import { bfs } from './traverse';
* @param elementType - 元素类型 | element type
* @param elementId - 起始元素的 ID | start element ID
* @param degree - 指定的度数 | the specified degree
+ * @param direction - 边的方向 | edge direction
* @returns - 返回节点和边的 ID 数组 | Returns an array of node and edge IDs
*/
-export function getElementNthDegreeIds(graph: Graph, elementType: ElementType, elementId: ID, degree: number): ID[] {
+export function getElementNthDegreeIds(
+ graph: Graph,
+ elementType: ElementType,
+ elementId: ID,
+ degree: number,
+ direction: EdgeDirection = 'both',
+): ID[] {
if (elementType === 'combo' || elementType === 'node') {
- return getNodeNthDegreeIds(graph, elementId, degree);
+ return getNodeNthDegreeIds(graph, elementId, degree, direction);
}
const edgeData = graph.getEdgeData(elementId);
if (!edgeData) return [];
- const sourceRelations = getNodeNthDegreeIds(graph, edgeData.source, degree - 1);
- const targetRelations = getNodeNthDegreeIds(graph, edgeData.target, degree - 1);
+ const sourceRelations = getNodeNthDegreeIds(graph, edgeData.source, degree - 1, direction);
+ const targetRelations = getNodeNthDegreeIds(graph, edgeData.target, degree - 1, direction);
return Array.from(new Set([...sourceRelations, ...targetRelations, elementId]));
}
@@ -39,14 +46,20 @@ export function getElementNthDegreeIds(graph: Graph, elementType: ElementType, e
* Get all elements IDs within n-degree relationship of the specified node
* @remarks
* 节点的 0 度关系是节点本身,1 度关系是节点的直接相邻节点和边,以此类推
- *
+ * @param direction
* 0-degree relationship of a node is the node itself; 1-degree relationship is the node's neighboring nodes and related edges, etc
* @param graph - 图实例 | graph instance
* @param startNodeId - 起始节点的 ID | The ID of the starting node
* @param degree - 指定的度数 | The specified degree
+ * @param direction - 边的方向 | The direction of the edge
* @returns - 返回节点和边的 ID 数组 | Returns an array of node and edge IDs
*/
-export function getNodeNthDegreeIds(graph: Graph, startNodeId: ID, degree: number): ID[] {
+export function getNodeNthDegreeIds(
+ graph: Graph,
+ startNodeId: ID,
+ degree: number,
+ direction: EdgeDirection = 'both',
+): ID[] {
const visitedNodes = new Set();
const visitedEdges = new Set();
const relations = new Set();
@@ -57,7 +70,7 @@ export function getNodeNthDegreeIds(graph: Graph, startNodeId: ID, degree: numbe
if (depth > degree) return;
relations.add(nodeId);
- graph.getRelatedEdgesData(nodeId).forEach((edge) => {
+ graph.getRelatedEdgesData(nodeId, direction).forEach((edge) => {
const edgeId = idOf(edge);
if (!visitedEdges.has(edgeId) && depth < degree) {
relations.add(edgeId);
@@ -67,7 +80,7 @@ export function getNodeNthDegreeIds(graph: Graph, startNodeId: ID, degree: numbe
},
(nodeId: ID) => {
return graph
- .getRelatedEdgesData(nodeId)
+ .getRelatedEdgesData(nodeId, direction)
.map((edge) => (edge.source === nodeId ? edge.target : edge.source))
.filter((neighborNodeId) => {
if (!visitedNodes.has(neighborNodeId)) {
diff --git a/packages/g6/src/utils/vector.ts b/packages/g6/src/utils/vector.ts
index a0543619176..112588b355c 100644
--- a/packages/g6/src/utils/vector.ts
+++ b/packages/g6/src/utils/vector.ts
@@ -206,3 +206,16 @@ export function toVector2(a: Vector2 | Vector3): Vector2 {
export function toVector3(a: Vector2 | Vector3): Vector3 {
return isVector2(a) ? [a[0], a[1], 0] : a;
}
+
+/**
+ * 计算向量与 x 轴正方向的夹角(弧度制)
+ *
+ * The angle between the vector and the positive direction of the x-axis (radians)
+ * @param a - 向量 | The vector
+ * @returns 弧度值 | The angle in radians
+ */
+export function rad(a: Vector2 | Vector3): number {
+ const [x, y] = a;
+ if (!x && !y) return 0;
+ return Math.atan2(y, x);
+}
diff --git a/packages/site/examples/layout/compact-box/demo/basic.js b/packages/site/examples/layout/compact-box/demo/basic.js
index 0452c1055db..d8158274d8d 100644
--- a/packages/site/examples/layout/compact-box/demo/basic.js
+++ b/packages/site/examples/layout/compact-box/demo/basic.js
@@ -1,5 +1,14 @@
import { Graph, treeToGraphData } from '@antv/g6';
+/**
+ * If the node is a leaf node
+ * @param {*} d - node data
+ * @returns {boolean} - whether the node is a leaf node
+ */
+function isLeafNode(d) {
+ return !d.children || d.children.length === 0;
+}
+
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
.then((res) => res.json())
.then((data) => {
@@ -10,17 +19,10 @@ fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.j
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'collapse-expand'],
node: {
style: {
- labelText: (data) => data.id,
- labelPlacement: 'right',
- labelMaxWidth: 200,
- ports: [
- {
- placement: 'right',
- },
- {
- placement: 'left',
- },
- ],
+ labelText: (d) => d.id,
+ labelPlacement: (d) => (isLeafNode(d) ? 'right' : 'left'),
+ labelBackground: true,
+ ports: [{ placement: 'right' }, { placement: 'left' }],
},
animation: {
enter: false,
diff --git a/packages/site/examples/layout/compact-box/demo/horizontal.js b/packages/site/examples/layout/compact-box/demo/horizontal.js
deleted file mode 100644
index 95562d6545e..00000000000
--- a/packages/site/examples/layout/compact-box/demo/horizontal.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Graph, treeToGraphData } from '@antv/g6';
-
-fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
- .then((res) => res.json())
- .then((data) => {
- const graph = new Graph({
- container: 'container',
- autoFit: 'view',
- data: treeToGraphData(data),
- behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'collapse-expand'],
- node: {
- style: {
- labelText: (data) => data.id,
- labelPlacement: 'right',
- labelMaxWidth: 200,
- size: 12,
- lineWidth: 1,
- fill: '#fff',
- ports: [
- {
- placement: 'right',
- },
- {
- placement: 'left',
- },
- ],
- },
- animation: {
- enter: false,
- },
- },
- edge: {
- type: 'cubic-horizontal',
- animation: {
- enter: false,
- },
- },
- layout: {
- type: 'compact-box',
- 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 d.id.length + 20;
- },
- },
- });
-
- graph.render();
- });
diff --git a/packages/site/examples/layout/compact-box/demo/meta.json b/packages/site/examples/layout/compact-box/demo/meta.json
index 20a45c308d6..0b9211e4796 100644
--- a/packages/site/examples/layout/compact-box/demo/meta.json
+++ b/packages/site/examples/layout/compact-box/demo/meta.json
@@ -10,7 +10,7 @@
"zh": "紧凑树",
"en": "CompactBox Layout"
},
- "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*-FgIT7w4OXwAAAAAAAAAAAAADmJ7AQ/original"
+ "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Hu02Ro6UCegAAAAAAAAAAAAADmJ7AQ/original"
},
{
"filename": "vertical.js",
@@ -18,15 +18,15 @@
"zh": "从上向下布局",
"en": "Top to Bottom CompactBox"
},
- "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*KrAqTrFbNjMAAAAAAAAAAABkARQnAQ"
+ "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*YFO6Rb1hM24AAAAAAAAAAAAADmJ7AQ/original"
},
{
- "filename": "horizontal.js",
+ "filename": "radial.js",
"title": {
- "zh": "节点左对齐的紧凑树",
- "en": "CompactBox with Left Align Nodes"
+ "zh": "径向布局",
+ "en": "Radial Layout"
},
- "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*X26MRo25GKgAAAAAAAAAAAAADmJ7AQ/original"
+ "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*nwPmQqzJprwAAAAAAAAAAAAADmJ7AQ/original"
}
]
}
diff --git a/packages/site/examples/layout/compact-box/demo/radial.js b/packages/site/examples/layout/compact-box/demo/radial.js
new file mode 100644
index 00000000000..a4671e19476
--- /dev/null
+++ b/packages/site/examples/layout/compact-box/demo/radial.js
@@ -0,0 +1,43 @@
+import { Graph, treeToGraphData } from '@antv/g6';
+
+fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
+ .then((res) => res.json())
+ .then((data) => {
+ const graph = new Graph({
+ container: 'container',
+ autoFit: 'view',
+ data: treeToGraphData(data),
+ behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
+ node: {
+ style: {
+ labelText: d => d.id,
+ labelBackground: true,
+ },
+ animation: {
+ enter: false,
+ },
+ },
+ layout: {
+ type: 'compact-box',
+ radial: true,
+ direction: 'RL',
+ getId: function getId(d) {
+ return d.id;
+ },
+ getHeight: () => {
+ return 26;
+ },
+ getWidth: () => {
+ return 26;
+ },
+ getVGap: () => {
+ return 20;
+ },
+ getHGap: () => {
+ return 40;
+ },
+ },
+ });
+
+ graph.render();
+ });
diff --git a/packages/site/examples/layout/compact-box/demo/vertical.js b/packages/site/examples/layout/compact-box/demo/vertical.js
index 4acc9625828..7c065b6ba5f 100644
--- a/packages/site/examples/layout/compact-box/demo/vertical.js
+++ b/packages/site/examples/layout/compact-box/demo/vertical.js
@@ -1,5 +1,14 @@
import { Graph, treeToGraphData } from '@antv/g6';
+/**
+ * If the node is a leaf node
+ * @param {*} d - node data
+ * @returns {boolean} - whether the node is a leaf node
+ */
+function isLeafNode(d) {
+ return !d.children || d.children.length === 0;
+}
+
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
.then((res) => res.json())
.then((data) => {
@@ -7,24 +16,24 @@ fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.j
container: 'container',
autoFit: 'view',
data: treeToGraphData(data),
+ behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'collapse-expand'],
node: {
- style: {
- labelText: (data) => data.id,
- labelPlacement: 'right',
- labelMaxWidth: 200,
- transform: 'rotate(90deg)',
- size: 26,
- fill: '#EFF4FF',
- lineWidth: 1,
- stroke: '#5F95FF',
- ports: [
- {
- placement: 'bottom',
- },
- {
- placement: 'top',
- },
- ],
+ style: d => {
+ const style = {
+ labelText: d.id,
+ labelPlacement: 'right',
+ labelOffsetX: 2,
+ labelBackground: true,
+ ports: [{ placement: 'top' }, { placement: 'bottom' }],
+ }
+ if (isLeafNode(d)) {
+ Object.assign(style, {
+ labelTransform: 'rotate(90deg) translate(18px)',
+ labelBaseline: 'center',
+ labelTextAlign: 'left',
+ })
+ }
+ return style
},
animation: {
enter: false,
@@ -39,9 +48,6 @@ fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.j
layout: {
type: 'compact-box',
direction: 'TB',
- getId: function getId(d) {
- return d.id;
- },
getHeight: function getHeight() {
return 16;
},
@@ -55,7 +61,6 @@ fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.j
return 20;
},
},
- behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'collapse-expand'],
});
graph.render();
diff --git a/packages/site/examples/layout/dendrogram/demo/basic.js b/packages/site/examples/layout/dendrogram/demo/basic.js
index 531bf123fdc..de48be14c92 100644
--- a/packages/site/examples/layout/dendrogram/demo/basic.js
+++ b/packages/site/examples/layout/dendrogram/demo/basic.js
@@ -1,5 +1,14 @@
import { Graph, treeToGraphData } from '@antv/g6';
+/**
+ * If the node is a leaf node
+ * @param {*} d - node data
+ * @returns {boolean} - whether the node is a leaf node
+ */
+function isLeafNode(d) {
+ return !d.children || d.children.length === 0;
+}
+
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
.then((res) => res.json())
.then((data) => {
@@ -10,7 +19,8 @@ fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.j
node: {
style: {
labelText: (d) => d.id,
- labelPlacement: (model) => (model.children?.length ? 'left' : 'right'),
+ labelPlacement: (d) => (isLeafNode(d) ? 'right' : 'left'),
+ labelBackground: true,
ports: [{ placement: 'right' }, { placement: 'left' }],
},
animation: {
diff --git a/packages/site/examples/layout/dendrogram/demo/meta.json b/packages/site/examples/layout/dendrogram/demo/meta.json
index 9b3311bac15..d82a16c1406 100644
--- a/packages/site/examples/layout/dendrogram/demo/meta.json
+++ b/packages/site/examples/layout/dendrogram/demo/meta.json
@@ -10,7 +10,7 @@
"zh": "生态树",
"en": "Dendrogram Layout"
},
- "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*P-qOSoDNuckAAAAAAAAAAAAADmJ7AQ/original"
+ "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*7tDPSa-LHbAAAAAAAAAAAAAADmJ7AQ/original"
},
{
"filename": "vertical.js",
@@ -18,7 +18,15 @@
"zh": "垂直布局",
"en": "Vertical Layout"
},
- "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*nTKmRKkyUVUAAAAAAAAAAABkARQnAQ"
+ "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*mp3BRbyBzCEAAAAAAAAAAAAADmJ7AQ/original"
+ },
+ {
+ "filename": "radial.js",
+ "title": {
+ "zh": "径向布局",
+ "en": "Radial Layout"
+ },
+ "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*nBIIR5yhTlQAAAAAAAAAAAAADmJ7AQ/original"
}
]
}
diff --git a/packages/site/examples/layout/dendrogram/demo/radial.js b/packages/site/examples/layout/dendrogram/demo/radial.js
new file mode 100644
index 00000000000..f70e0c19d27
--- /dev/null
+++ b/packages/site/examples/layout/dendrogram/demo/radial.js
@@ -0,0 +1,29 @@
+import { Graph, treeToGraphData } from '@antv/g6';
+
+
+fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
+ .then((res) => res.json())
+ .then((data) => {
+ const graph = new Graph({
+ container: 'container',
+ autoFit: 'view',
+ data: treeToGraphData(data),
+ behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
+ node: {
+ style: {
+ labelText: d => d.id
+ },
+ animation: {
+ enter: false,
+ },
+ },
+ layout: {
+ type: 'dendrogram',
+ radial: true,
+ nodeSep: 40,
+ rankSep: 140,
+ },
+ });
+
+ graph.render();
+ });
diff --git a/packages/site/examples/layout/dendrogram/demo/vertical.js b/packages/site/examples/layout/dendrogram/demo/vertical.js
index c21f01f578f..784a68c7820 100644
--- a/packages/site/examples/layout/dendrogram/demo/vertical.js
+++ b/packages/site/examples/layout/dendrogram/demo/vertical.js
@@ -1,5 +1,14 @@
import { Graph, treeToGraphData } from '@antv/g6';
+/**
+ * If the node is a leaf node
+ * @param {*} d - node data
+ * @returns {boolean} - whether the node is a leaf node
+ */
+function isLeafNode(d) {
+ return !d.children || d.children.length === 0;
+}
+
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
.then((res) => res.json())
.then((data) => {
@@ -7,20 +16,24 @@ fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.j
container: 'container',
autoFit: 'view',
data: treeToGraphData(data),
+ behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'collapse-expand'],
node: {
- style: (data) => {
- const isLeaf = !data.children?.length;
- return {
- labelText: data.id,
- labelWordWrap: true,
- labelWordWrapWidth: 150,
- labelDx: isLeaf ? 20 : 0,
- labelDy: isLeaf ? 0 : 20,
- labelTextAlign: isLeaf ? 'start' : 'center',
- labelTextBaseline: 'middle',
- labelTransform: isLeaf ? 'rotate(90deg)' : '',
- ports: [{ placement: 'bottom' }, { placement: 'top' }],
+ style: (d) => {
+ const style = {
+ labelText: d.id,
+ labelPlacement: 'right',
+ labelOffsetX: 2,
+ labelBackground: true,
+ ports: [{ placement: 'top' }, { placement: 'bottom' }],
};
+ if (isLeafNode(d)) {
+ Object.assign(style, {
+ labelTransform: 'rotate(90deg) translate(18px)',
+ labelBaseline: 'center',
+ labelTextAlign: 'left',
+ });
+ }
+ return style;
},
animation: {
enter: false,
@@ -35,10 +48,9 @@ fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.j
layout: {
type: 'dendrogram',
direction: 'TB', // H / V / LR / RL / TB / BT
- nodeSep: 40,
- rankSep: 100,
+ nodeSep: 50,
+ rankSep: 120,
},
- behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'collapse-expand'],
});
graph.render();
diff --git a/packages/site/examples/scene-case/default/demo/meta.json b/packages/site/examples/scene-case/default/demo/meta.json
index 1b25184bd9d..e6ca846de8f 100644
--- a/packages/site/examples/scene-case/default/demo/meta.json
+++ b/packages/site/examples/scene-case/default/demo/meta.json
@@ -36,6 +36,22 @@
},
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*P39BR7WoI5oAAAAAAAAAAAAADmJ7AQ/original"
},
+ {
+ "filename": "radial-dendrogram.js",
+ "title": {
+ "zh": "径向生态树",
+ "en": "Radial Dendrogram"
+ },
+ "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*WRXHSrxqBYsAAAAAAAAAAAAADmJ7AQ/original"
+ },
+ {
+ "filename": "radial-compact-tree.js",
+ "title": {
+ "zh": "径向紧凑树",
+ "en": "Radial Compact Tree"
+ },
+ "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*uKYKSYJ6iMYAAAAAAAAAAAAADmJ7AQ/original"
+ },
{
"filename": "decision-tree.js",
"title": {
diff --git a/packages/site/examples/scene-case/default/demo/radial-compact-tree.js b/packages/site/examples/scene-case/default/demo/radial-compact-tree.js
new file mode 100644
index 00000000000..5adececf81c
--- /dev/null
+++ b/packages/site/examples/scene-case/default/demo/radial-compact-tree.js
@@ -0,0 +1,66 @@
+import { Graph, treeToGraphData } from '@antv/g6';
+
+fetch('https://assets.antv.antgroup.com/g6/flare.json')
+ .then((res) => res.json())
+ .then((data) => {
+ const graph = new Graph({
+ container: 'container',
+ autoFit: 'view',
+ data: treeToGraphData(data),
+ node: {
+ style: {
+ size: 20,
+ labelText: (d) => d.id,
+ labelBackground: true,
+ },
+ state: {
+ active: {
+ fill: '#00C9C9',
+ },
+ },
+ },
+ edge: {
+ type: 'cubic-radial',
+ state: {
+ active: {
+ lineWidth: 3,
+ stroke: '#009999',
+ },
+ },
+ },
+ layout: [
+ {
+ type: 'compact-box',
+ radial: true,
+ direction: 'RL',
+ getHeight: () => {
+ return 20;
+ },
+ getWidth: () => {
+ return 20;
+ },
+ getVGap: () => {
+ return 20;
+ },
+ getHGap: () => {
+ return 80;
+ },
+ },
+ ],
+ behaviors: [
+ 'drag-canvas',
+ 'zoom-canvas',
+ 'drag-element',
+ {
+ key: 'hover-activate',
+ type: 'hover-activate',
+ degree: 5,
+ direction: 'in',
+ inactiveState: 'inactive',
+ },
+ ],
+ transforms: ['position-radial-labels'],
+ });
+
+ graph.render();
+ });
diff --git a/packages/site/examples/scene-case/default/demo/radial-dendrogram.js b/packages/site/examples/scene-case/default/demo/radial-dendrogram.js
new file mode 100644
index 00000000000..e6544f33cb4
--- /dev/null
+++ b/packages/site/examples/scene-case/default/demo/radial-dendrogram.js
@@ -0,0 +1,55 @@
+import { Graph, treeToGraphData } from '@antv/g6';
+
+fetch('https://assets.antv.antgroup.com/g6/flare.json')
+ .then((res) => res.json())
+ .then((data) => {
+ const graph = new Graph({
+ container: 'container',
+ autoFit: 'view',
+ data: treeToGraphData(data),
+ node: {
+ style: {
+ size: 20,
+ labelText: (d) => d.id,
+ labelBackground: true,
+ },
+ state: {
+ active: {
+ fill: '#00C9C9',
+ },
+ },
+ },
+ edge: {
+ type: 'cubic-radial',
+ state: {
+ active: {
+ lineWidth: 3,
+ stroke: '#009999',
+ },
+ },
+ },
+ layout: [
+ {
+ type: 'dendrogram',
+ radial: true,
+ nodeSep: 30,
+ rankSep: 200,
+ },
+ ],
+ behaviors: [
+ 'drag-canvas',
+ 'zoom-canvas',
+ 'drag-element',
+ {
+ key: 'hover-activate',
+ type: 'hover-activate',
+ degree: 5,
+ direction: 'in',
+ inactiveState: 'inactive',
+ },
+ ],
+ transforms: ['position-radial-labels'],
+ });
+
+ graph.render();
+ });