@antv/infographic 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/infographic.min.js +110 -105
- package/dist/infographic.min.js.map +1 -1
- package/esm/constants/element.d.ts +1 -1
- package/esm/constants/index.d.ts +1 -0
- package/esm/constants/index.js +1 -0
- package/esm/constants/service.d.ts +1 -0
- package/esm/constants/service.js +1 -0
- package/esm/designs/components/Illus.js +1 -1
- package/esm/designs/structures/chart-wordcloud.d.ts +11 -0
- package/esm/designs/structures/chart-wordcloud.js +156 -0
- package/esm/designs/structures/hierarchy-tree.d.ts +2 -0
- package/esm/designs/structures/hierarchy-tree.js +179 -50
- package/esm/designs/structures/index.d.ts +2 -0
- package/esm/designs/structures/index.js +2 -0
- package/esm/designs/structures/sequence-stairs-front.d.ts +8 -0
- package/esm/designs/structures/sequence-stairs-front.js +116 -0
- package/esm/designs/types.d.ts +8 -0
- package/esm/editor/managers/state.js +1 -1
- package/esm/index.d.ts +2 -0
- package/esm/index.js +1 -0
- package/esm/options/parser.d.ts +1 -1
- package/esm/options/parser.js +33 -15
- package/esm/renderer/composites/icon.js +1 -1
- package/esm/renderer/composites/illus.js +1 -1
- package/esm/resource/loader.d.ts +2 -2
- package/esm/resource/loader.js +22 -11
- package/esm/resource/loaders/index.d.ts +1 -0
- package/esm/resource/loaders/index.js +1 -0
- package/esm/resource/loaders/remote.d.ts +1 -1
- package/esm/resource/loaders/remote.js +10 -2
- package/esm/resource/loaders/search.d.ts +1 -0
- package/esm/resource/loaders/search.js +51 -0
- package/esm/resource/types/index.d.ts +1 -0
- package/esm/resource/types/resource.d.ts +8 -1
- package/esm/resource/types/scene.d.ts +1 -0
- package/esm/resource/utils/data-uri.js +20 -11
- package/esm/resource/utils/parser.js +92 -1
- package/esm/resource/utils/ref.js +2 -2
- package/esm/runtime/Infographic.d.ts +7 -6
- package/esm/runtime/Infographic.js +48 -17
- package/esm/runtime/utils.d.ts +4 -2
- package/esm/runtime/utils.js +33 -13
- package/esm/syntax/index.d.ts +3 -0
- package/esm/syntax/index.js +101 -0
- package/esm/syntax/mapper.d.ts +3 -0
- package/esm/syntax/mapper.js +238 -0
- package/esm/syntax/parser.d.ts +14 -0
- package/esm/syntax/parser.js +142 -0
- package/esm/syntax/schema.d.ts +6 -0
- package/esm/syntax/schema.js +74 -0
- package/esm/syntax/types.d.ts +61 -0
- package/esm/syntax/types.js +1 -0
- package/esm/templates/built-in.js +4 -0
- package/esm/templates/hierarchy-tree.js +25 -11
- package/esm/templates/sequence-stairs.d.ts +2 -0
- package/esm/templates/sequence-stairs.js +42 -0
- package/esm/templates/word-cloud.d.ts +2 -0
- package/esm/templates/word-cloud.js +19 -0
- package/esm/themes/types.d.ts +1 -1
- package/esm/utils/design.d.ts +2 -0
- package/esm/utils/design.js +10 -0
- package/esm/utils/font.js +11 -1
- package/esm/utils/index.d.ts +1 -0
- package/esm/utils/index.js +1 -0
- package/lib/constants/element.d.ts +1 -1
- package/lib/constants/index.d.ts +1 -0
- package/lib/constants/index.js +1 -0
- package/lib/constants/service.d.ts +1 -0
- package/lib/constants/service.js +4 -0
- package/lib/designs/components/Illus.js +1 -1
- package/lib/designs/structures/chart-wordcloud.d.ts +11 -0
- package/lib/designs/structures/chart-wordcloud.js +160 -0
- package/lib/designs/structures/hierarchy-tree.d.ts +2 -0
- package/lib/designs/structures/hierarchy-tree.js +179 -50
- package/lib/designs/structures/index.d.ts +2 -0
- package/lib/designs/structures/index.js +2 -0
- package/lib/designs/structures/sequence-stairs-front.d.ts +8 -0
- package/lib/designs/structures/sequence-stairs-front.js +120 -0
- package/lib/designs/types.d.ts +8 -0
- package/lib/editor/managers/state.js +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +4 -1
- package/lib/options/parser.d.ts +1 -1
- package/lib/options/parser.js +32 -14
- package/lib/renderer/composites/icon.js +1 -1
- package/lib/renderer/composites/illus.js +1 -1
- package/lib/resource/loader.d.ts +2 -2
- package/lib/resource/loader.js +21 -10
- package/lib/resource/loaders/index.d.ts +1 -0
- package/lib/resource/loaders/index.js +1 -0
- package/lib/resource/loaders/remote.d.ts +1 -1
- package/lib/resource/loaders/remote.js +10 -2
- package/lib/resource/loaders/search.d.ts +1 -0
- package/lib/resource/loaders/search.js +54 -0
- package/lib/resource/types/index.d.ts +1 -0
- package/lib/resource/types/resource.d.ts +8 -1
- package/lib/resource/types/scene.d.ts +1 -0
- package/lib/resource/utils/data-uri.js +20 -11
- package/lib/resource/utils/parser.js +92 -1
- package/lib/resource/utils/ref.js +2 -2
- package/lib/runtime/Infographic.d.ts +7 -6
- package/lib/runtime/Infographic.js +47 -16
- package/lib/runtime/utils.d.ts +4 -2
- package/lib/runtime/utils.js +35 -13
- package/lib/syntax/index.d.ts +3 -0
- package/lib/syntax/index.js +104 -0
- package/lib/syntax/mapper.d.ts +3 -0
- package/lib/syntax/mapper.js +242 -0
- package/lib/syntax/parser.d.ts +14 -0
- package/lib/syntax/parser.js +146 -0
- package/lib/syntax/schema.d.ts +6 -0
- package/lib/syntax/schema.js +77 -0
- package/lib/syntax/types.d.ts +61 -0
- package/lib/syntax/types.js +2 -0
- package/lib/templates/built-in.js +4 -0
- package/lib/templates/hierarchy-tree.js +25 -11
- package/lib/templates/sequence-stairs.d.ts +2 -0
- package/lib/templates/sequence-stairs.js +45 -0
- package/lib/templates/word-cloud.d.ts +2 -0
- package/lib/templates/word-cloud.js +22 -0
- package/lib/themes/types.d.ts +1 -1
- package/lib/utils/design.d.ts +2 -0
- package/lib/utils/design.js +13 -0
- package/lib/utils/font.js +11 -1
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/package.json +1 -1
- package/src/constants/element.ts +1 -1
- package/src/constants/index.ts +1 -0
- package/src/constants/service.ts +1 -0
- package/src/designs/components/Illus.tsx +1 -1
- package/src/designs/structures/chart-wordcloud.tsx +278 -0
- package/src/designs/structures/hierarchy-tree.tsx +212 -59
- package/src/designs/structures/index.ts +2 -0
- package/src/designs/structures/sequence-stairs-front.tsx +291 -0
- package/src/designs/types.ts +9 -0
- package/src/editor/managers/state.ts +1 -1
- package/src/index.ts +2 -0
- package/src/options/parser.ts +57 -28
- package/src/renderer/composites/icon.ts +1 -1
- package/src/renderer/composites/illus.ts +1 -1
- package/src/resource/loader.ts +22 -8
- package/src/resource/loaders/index.ts +1 -0
- package/src/resource/loaders/remote.ts +9 -2
- package/src/resource/loaders/search.ts +52 -0
- package/src/resource/types/index.ts +2 -1
- package/src/resource/types/resource.ts +12 -1
- package/src/resource/types/scene.ts +1 -0
- package/src/resource/utils/data-uri.ts +20 -11
- package/src/resource/utils/parser.ts +103 -2
- package/src/resource/utils/ref.ts +2 -2
- package/src/runtime/Infographic.tsx +74 -22
- package/src/runtime/utils.ts +38 -16
- package/src/syntax/index.ts +124 -0
- package/src/syntax/mapper.ts +362 -0
- package/src/syntax/parser.ts +171 -0
- package/src/syntax/schema.ts +98 -0
- package/src/syntax/types.ts +89 -0
- package/src/templates/built-in.ts +4 -0
- package/src/templates/hierarchy-tree.ts +34 -11
- package/src/templates/sequence-stairs.ts +44 -0
- package/src/templates/word-cloud.ts +21 -0
- package/src/themes/types.ts +1 -1
- package/src/utils/design.ts +14 -0
- package/src/utils/font.ts +11 -1
- package/src/utils/index.ts +1 -0
- package/esm/resource/types/font.d.ts +0 -12
- package/lib/resource/types/font.d.ts +0 -12
- package/src/resource/types/font.ts +0 -23
- /package/esm/resource/types/{font.js → scene.js} +0 -0
- /package/lib/resource/types/{font.js → scene.js} +0 -0
|
@@ -11,7 +11,7 @@ export declare const enum ElementTypeEnum {
|
|
|
11
11
|
ItemLabel = "item-label",
|
|
12
12
|
ItemDesc = "item-desc",
|
|
13
13
|
ItemValue = "item-value",
|
|
14
|
-
|
|
14
|
+
ItemIllus = "item-illus",
|
|
15
15
|
BtnAdd = "btn-add",
|
|
16
16
|
BtnRemove = "btn-remove",
|
|
17
17
|
IllusGroup = "illus-group",
|
package/esm/constants/index.d.ts
CHANGED
package/esm/constants/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ICON_SERVICE_URL = "https://www.weavefox.cn/api/open/v1/icon";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ICON_SERVICE_URL = 'https://www.weavefox.cn/api/open/v1/icon';
|
|
@@ -7,7 +7,7 @@ export const Illus = ({ indexes, ...props }) => {
|
|
|
7
7
|
const finalProps = { ...defaultProps, ...props };
|
|
8
8
|
if (indexes) {
|
|
9
9
|
finalProps['data-indexes'] = indexes;
|
|
10
|
-
finalProps['data-element-type'] = "
|
|
10
|
+
finalProps['data-element-type'] = "item-illus" /* ElementTypeEnum.ItemIllus */;
|
|
11
11
|
}
|
|
12
12
|
else {
|
|
13
13
|
finalProps['data-element-type'] = "illus" /* ElementTypeEnum.Illus */;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ComponentType } from '../../jsx';
|
|
2
|
+
import type { BaseStructureProps } from './types';
|
|
3
|
+
export interface ChartWordCloudProps extends BaseStructureProps {
|
|
4
|
+
minFontSize?: number;
|
|
5
|
+
maxFontSize?: number;
|
|
6
|
+
enableRotate?: boolean;
|
|
7
|
+
padding?: number;
|
|
8
|
+
spiralStep?: number;
|
|
9
|
+
radiusStep?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const ChartWordCloud: ComponentType<ChartWordCloudProps>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@antv/infographic/jsx-runtime";
|
|
2
|
+
import { getElementBounds, Group, Text } from '../../jsx';
|
|
3
|
+
import { ItemsGroup } from '../components';
|
|
4
|
+
import { FlexLayout } from '../layouts';
|
|
5
|
+
import { getColorPrimary, getPaletteColor } from '../utils';
|
|
6
|
+
import { registerStructure } from './registry';
|
|
7
|
+
const DEFAULT_ROTATE_ANGLES = [0, 30, -30, 60, -60];
|
|
8
|
+
const GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));
|
|
9
|
+
function getRotatedSize(width, height, angle) {
|
|
10
|
+
const rad = (Math.PI / 180) * angle;
|
|
11
|
+
const cos = Math.cos(rad);
|
|
12
|
+
const sin = Math.sin(rad);
|
|
13
|
+
return {
|
|
14
|
+
width: Math.abs(width * cos) + Math.abs(height * sin),
|
|
15
|
+
height: Math.abs(width * sin) + Math.abs(height * cos),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function hasCollision(x, y, width, height, placed, padding) {
|
|
19
|
+
const left = x - padding;
|
|
20
|
+
const right = x + width + padding;
|
|
21
|
+
const top = y - padding;
|
|
22
|
+
const bottom = y + height + padding;
|
|
23
|
+
return placed.some((word) => {
|
|
24
|
+
const wLeft = word.box.x - padding;
|
|
25
|
+
const wRight = word.box.x + word.box.width + padding;
|
|
26
|
+
const wTop = word.box.y - padding;
|
|
27
|
+
const wBottom = word.box.y + word.box.height + padding;
|
|
28
|
+
return !(right <= wLeft ||
|
|
29
|
+
left >= wRight ||
|
|
30
|
+
bottom <= wTop ||
|
|
31
|
+
top >= wBottom);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function placeWords(words, enableRotate, padding, spiralStep, radiusStep) {
|
|
35
|
+
const placed = [];
|
|
36
|
+
const rotationAngles = enableRotate ? DEFAULT_ROTATE_ANGLES : [0];
|
|
37
|
+
const maxAttempts = Math.max(1600, words.length * 28);
|
|
38
|
+
words.forEach((word, wordIndex) => {
|
|
39
|
+
const sizeBias = Math.max(word.width, word.height);
|
|
40
|
+
const angleOffset = wordIndex * GOLDEN_ANGLE;
|
|
41
|
+
let extraRadius = 0;
|
|
42
|
+
let placedWord = null;
|
|
43
|
+
for (let attempt = 0; attempt < maxAttempts && !placedWord; attempt++) {
|
|
44
|
+
if (attempt === Math.floor(maxAttempts * 0.6)) {
|
|
45
|
+
// Gradually expand the search radius for dense layouts
|
|
46
|
+
extraRadius = sizeBias;
|
|
47
|
+
}
|
|
48
|
+
const theta = angleOffset + attempt * spiralStep;
|
|
49
|
+
const radius = radiusStep * Math.sqrt(attempt + 1) + extraRadius + sizeBias * 0.25;
|
|
50
|
+
const centerX = radius * Math.cos(theta);
|
|
51
|
+
const centerY = radius * Math.sin(theta);
|
|
52
|
+
for (const angle of rotationAngles) {
|
|
53
|
+
const rotated = getRotatedSize(word.width, word.height, angle);
|
|
54
|
+
const x = centerX - rotated.width / 2;
|
|
55
|
+
const y = centerY - rotated.height / 2;
|
|
56
|
+
if (!hasCollision(x, y, rotated.width, rotated.height, placed, padding)) {
|
|
57
|
+
placedWord = {
|
|
58
|
+
...word,
|
|
59
|
+
angle,
|
|
60
|
+
centerX,
|
|
61
|
+
centerY,
|
|
62
|
+
box: { x, y, width: rotated.width, height: rotated.height },
|
|
63
|
+
};
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!placedWord) {
|
|
69
|
+
const fallbackAngle = rotationAngles[wordIndex % rotationAngles.length];
|
|
70
|
+
const farRadius = radiusStep * Math.sqrt(maxAttempts + 1) + sizeBias;
|
|
71
|
+
const theta = angleOffset;
|
|
72
|
+
const centerX = farRadius * Math.cos(theta);
|
|
73
|
+
const centerY = farRadius * Math.sin(theta);
|
|
74
|
+
const rotated = getRotatedSize(word.width, word.height, fallbackAngle);
|
|
75
|
+
placedWord = {
|
|
76
|
+
...word,
|
|
77
|
+
angle: fallbackAngle,
|
|
78
|
+
centerX,
|
|
79
|
+
centerY,
|
|
80
|
+
box: {
|
|
81
|
+
x: centerX - rotated.width / 2,
|
|
82
|
+
y: centerY - rotated.height / 2,
|
|
83
|
+
width: rotated.width,
|
|
84
|
+
height: rotated.height,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
placed.push(placedWord);
|
|
89
|
+
});
|
|
90
|
+
return placed;
|
|
91
|
+
}
|
|
92
|
+
export const ChartWordCloud = (props) => {
|
|
93
|
+
const { data, options, minFontSize = 16, maxFontSize = 48, enableRotate = true, padding = 6, spiralStep = 0.45, radiusStep = 10, } = props;
|
|
94
|
+
const { items = [] } = data;
|
|
95
|
+
const validItems = items
|
|
96
|
+
.map((datum, index) => ({ datum, index }))
|
|
97
|
+
.filter(({ datum }) => datum.label);
|
|
98
|
+
if (validItems.length === 0) {
|
|
99
|
+
return (_jsx(FlexLayout, { id: "infographic-container", flexDirection: "column", justifyContent: "center", alignItems: "center", children: _jsx(Group, { children: _jsx(ItemsGroup, {}) }) }));
|
|
100
|
+
}
|
|
101
|
+
const values = validItems
|
|
102
|
+
.map(({ datum }) => datum.value)
|
|
103
|
+
.filter((v) => typeof v === 'number');
|
|
104
|
+
const hasValues = values.length > 0;
|
|
105
|
+
const minValue = hasValues ? Math.min(...values) : 0;
|
|
106
|
+
const maxValue = hasValues ? Math.max(...values) : 0;
|
|
107
|
+
const sameValue = hasValues && minValue === maxValue;
|
|
108
|
+
const uniformSize = (minFontSize + maxFontSize) / 2;
|
|
109
|
+
const mapFontSize = (value) => {
|
|
110
|
+
if (!hasValues || sameValue)
|
|
111
|
+
return uniformSize;
|
|
112
|
+
if (value == null)
|
|
113
|
+
return minFontSize;
|
|
114
|
+
const ratio = (value - minValue) / (maxValue - minValue || 1);
|
|
115
|
+
return minFontSize + ratio * (maxFontSize - minFontSize);
|
|
116
|
+
};
|
|
117
|
+
const words = validItems
|
|
118
|
+
.map(({ datum, index }) => {
|
|
119
|
+
const fontSize = mapFontSize(datum.value);
|
|
120
|
+
const measured = getElementBounds(_jsx(Text, { fontSize: fontSize, fontWeight: "bold", children: datum.label }));
|
|
121
|
+
const color = getPaletteColor(options, [index]) ||
|
|
122
|
+
getColorPrimary(options) ||
|
|
123
|
+
'#333333';
|
|
124
|
+
return {
|
|
125
|
+
label: datum.label,
|
|
126
|
+
value: datum.value,
|
|
127
|
+
color,
|
|
128
|
+
fontSize,
|
|
129
|
+
width: measured.width * 1.05,
|
|
130
|
+
height: measured.height,
|
|
131
|
+
};
|
|
132
|
+
})
|
|
133
|
+
.sort((a, b) => b.fontSize - a.fontSize);
|
|
134
|
+
const placedWords = placeWords(words, enableRotate, padding, spiralStep, radiusStep);
|
|
135
|
+
const minX = Math.min(...placedWords.map((w) => w.box.x));
|
|
136
|
+
const minY = Math.min(...placedWords.map((w) => w.box.y));
|
|
137
|
+
const maxX = Math.max(...placedWords.map((w) => w.box.x + w.box.width));
|
|
138
|
+
const maxY = Math.max(...placedWords.map((w) => w.box.y + w.box.height));
|
|
139
|
+
const offsetX = -minX + padding;
|
|
140
|
+
const offsetY = -minY + padding;
|
|
141
|
+
const containerWidth = maxX - minX + padding * 2;
|
|
142
|
+
const containerHeight = maxY - minY + padding * 2;
|
|
143
|
+
const wordElements = placedWords.map((word, index) => {
|
|
144
|
+
const translateX = word.centerX - word.width / 2 + offsetX;
|
|
145
|
+
const translateY = word.centerY - word.height / 2 + offsetY;
|
|
146
|
+
const rotationOriginX = word.width / 2;
|
|
147
|
+
const rotationOriginY = word.height / 2;
|
|
148
|
+
const transform = `translate(${translateX}, ${translateY}) rotate(${word.angle}, ${rotationOriginX}, ${rotationOriginY})`;
|
|
149
|
+
return (_jsx(Group, { transform: transform, children: _jsx(Text, { width: word.width, height: word.height, fontSize: word.fontSize, fontWeight: "bold", alignHorizontal: "center", alignVertical: "middle", fill: word.color, "data-indexes": index, "data-element-type": "item-label" /* ElementTypeEnum.ItemLabel */, children: word.label }) }));
|
|
150
|
+
});
|
|
151
|
+
return (_jsx(ItemsGroup, { id: "infographic-container", width: containerWidth, height: containerHeight, children: wordElements }));
|
|
152
|
+
};
|
|
153
|
+
registerStructure('chart-wordcloud', {
|
|
154
|
+
component: ChartWordCloud,
|
|
155
|
+
composites: ['item'],
|
|
156
|
+
});
|
|
@@ -6,6 +6,8 @@ export interface HierarchyTreeProps extends BaseStructureProps {
|
|
|
6
6
|
levelGap?: number;
|
|
7
7
|
/** 节点间距:同级节点之间的水平距离,默认 60 */
|
|
8
8
|
nodeGap?: number;
|
|
9
|
+
/** 布局方向:'top-bottom' 自上而下 | 'bottom-top' 自下而上 | 'left-right' 自左向右 | 'right-left' 自右向左,默认 'top-bottom' */
|
|
10
|
+
orientation?: 'top-bottom' | 'bottom-top' | 'left-right' | 'right-left';
|
|
9
11
|
/** 连接线类型:'straight' 直线 | 'curved' 曲线,默认 'curved' */
|
|
10
12
|
edgeType?: 'straight' | 'curved';
|
|
11
13
|
/** 连接线颜色模式:'solid' 单色 | 'gradient' 渐变色,默认 'gradient' */
|
|
@@ -5,6 +5,10 @@ import { BtnAdd, BtnRemove, BtnsGroup, ItemsGroup, ShapesGroup, } from '../compo
|
|
|
5
5
|
import { FlexLayout } from '../layouts';
|
|
6
6
|
import { getColorPrimary, getHierarchyColorIndexes, getItemComponent, getPaletteColor, getThemeColors, } from '../utils';
|
|
7
7
|
import { registerStructure } from './registry';
|
|
8
|
+
const distributedPadding = (rawPadding, size) => {
|
|
9
|
+
const maxPadding = Math.max(0, size / 2 - 1);
|
|
10
|
+
return Math.min(rawPadding, maxPadding);
|
|
11
|
+
};
|
|
8
12
|
export const HierarchyTree = (props) => {
|
|
9
13
|
const { Title, Items, data,
|
|
10
14
|
// 布局配置
|
|
@@ -16,7 +20,11 @@ export const HierarchyTree = (props) => {
|
|
|
16
20
|
// 装饰元素配置
|
|
17
21
|
edgeMarker = 'none', markerSize = 12,
|
|
18
22
|
// 着色模式配置
|
|
19
|
-
colorMode = 'branch',
|
|
23
|
+
colorMode = 'branch',
|
|
24
|
+
// 布局方向
|
|
25
|
+
orientation = 'top-bottom', options, } = props;
|
|
26
|
+
const isHorizontal = orientation === 'left-right' || orientation === 'right-left';
|
|
27
|
+
const mainSign = orientation === 'bottom-top' || orientation === 'right-left' ? -1 : 1;
|
|
20
28
|
const { title, desc } = data;
|
|
21
29
|
const colorPrimary = getColorPrimary(options);
|
|
22
30
|
// 内置工具方法:数据预处理
|
|
@@ -29,19 +37,52 @@ export const HierarchyTree = (props) => {
|
|
|
29
37
|
return list;
|
|
30
38
|
};
|
|
31
39
|
// 内置工具方法:生成圆角路径
|
|
32
|
-
const createRoundedPath = (x1, y1, x2, y2, radius) => {
|
|
33
|
-
const
|
|
34
|
-
const
|
|
40
|
+
const createRoundedPath = (x1, y1, x2, y2, radius, direction = 'vertical') => {
|
|
41
|
+
const isVertical = direction === 'vertical';
|
|
42
|
+
const deltaMain = isVertical ? y2 - y1 : x2 - x1;
|
|
43
|
+
const deltaCross = isVertical ? x2 - x1 : y2 - y1;
|
|
44
|
+
const signMain = deltaMain === 0 ? 1 : Math.sign(deltaMain);
|
|
45
|
+
const signCross = deltaCross === 0 ? 1 : Math.sign(deltaCross);
|
|
46
|
+
const midMain = isVertical ? y1 + deltaMain / 2 : x1 + deltaMain / 2;
|
|
47
|
+
const effectiveRadius = Math.min(radius, Math.abs(deltaMain) / 2, Math.abs(deltaCross) / 2);
|
|
35
48
|
if (effectiveRadius === 0) {
|
|
36
|
-
return
|
|
49
|
+
return isVertical
|
|
50
|
+
? `M ${x1} ${y1} L ${x1} ${midMain} L ${x2} ${midMain} L ${x2} ${y2}`
|
|
51
|
+
: `M ${x1} ${y1} L ${midMain} ${y1} L ${midMain} ${y2} L ${x2} ${y2}`;
|
|
52
|
+
}
|
|
53
|
+
if (isVertical) {
|
|
54
|
+
return `M ${x1} ${y1}
|
|
55
|
+
L ${x1} ${midMain - signMain * effectiveRadius}
|
|
56
|
+
Q ${x1} ${midMain} ${x1 + signCross * effectiveRadius} ${midMain}
|
|
57
|
+
L ${x2 - signCross * effectiveRadius} ${midMain}
|
|
58
|
+
Q ${x2} ${midMain} ${x2} ${midMain + signMain * effectiveRadius}
|
|
59
|
+
L ${x2} ${y2}`;
|
|
37
60
|
}
|
|
38
61
|
return `M ${x1} ${y1}
|
|
39
|
-
L ${
|
|
40
|
-
Q ${
|
|
41
|
-
L ${
|
|
42
|
-
Q ${
|
|
62
|
+
L ${midMain - signMain * effectiveRadius} ${y1}
|
|
63
|
+
Q ${midMain} ${y1} ${midMain} ${y1 + signCross * effectiveRadius}
|
|
64
|
+
L ${midMain} ${y2 - signCross * effectiveRadius}
|
|
65
|
+
Q ${midMain} ${y2} ${midMain + signMain * effectiveRadius} ${y2}
|
|
43
66
|
L ${x2} ${y2}`;
|
|
44
67
|
};
|
|
68
|
+
const getLayoutPoint = (node) => {
|
|
69
|
+
const { x, y } = node;
|
|
70
|
+
return isHorizontal ? { x: y * mainSign, y: x } : { x, y: y * mainSign };
|
|
71
|
+
};
|
|
72
|
+
const getNodeRect = (node, bounds, offsets) => {
|
|
73
|
+
const { x, y } = getLayoutPoint(node);
|
|
74
|
+
const centerX = x + offsets.x;
|
|
75
|
+
const top = y + offsets.y;
|
|
76
|
+
const centerY = top + bounds.height / 2;
|
|
77
|
+
return {
|
|
78
|
+
centerX,
|
|
79
|
+
centerY,
|
|
80
|
+
left: centerX - bounds.width / 2,
|
|
81
|
+
right: centerX + bounds.width / 2,
|
|
82
|
+
top,
|
|
83
|
+
bottom: top + bounds.height,
|
|
84
|
+
};
|
|
85
|
+
};
|
|
45
86
|
// 内置工具方法:构建层级数据
|
|
46
87
|
const buildHierarchyData = (list) => {
|
|
47
88
|
if (!list.length)
|
|
@@ -94,12 +135,13 @@ export const HierarchyTree = (props) => {
|
|
|
94
135
|
};
|
|
95
136
|
// 内置工具方法:渲染单个节点
|
|
96
137
|
const renderNode = (node, levelBounds, btnBounds, offsets, gradientDefs, allNodes) => {
|
|
97
|
-
const {
|
|
138
|
+
const { depth, data: nodeData, parent } = node;
|
|
98
139
|
const indexes = nodeData._originalIndex;
|
|
99
140
|
const NodeComponent = getItemComponent(Items, depth);
|
|
100
141
|
const bounds = levelBounds.get(depth);
|
|
101
|
-
const
|
|
102
|
-
const
|
|
142
|
+
const nodeRect = getNodeRect(node, bounds, offsets);
|
|
143
|
+
const nodeX = nodeRect.left;
|
|
144
|
+
const nodeY = nodeRect.top;
|
|
103
145
|
const elements = {
|
|
104
146
|
items: [],
|
|
105
147
|
btns: [],
|
|
@@ -122,45 +164,94 @@ export const HierarchyTree = (props) => {
|
|
|
122
164
|
// 父子连线
|
|
123
165
|
if (parent) {
|
|
124
166
|
const parentBounds = levelBounds.get(parent.depth);
|
|
167
|
+
const parentRect = getNodeRect(parent, parentBounds, offsets);
|
|
125
168
|
// 计算父节点的子节点数量和当前节点在兄弟中的索引
|
|
126
169
|
const siblings = allNodes.filter((n) => n.parent === parent);
|
|
127
170
|
const siblingIndex = siblings.findIndex((s) => s === node);
|
|
128
171
|
const siblingCount = siblings.length;
|
|
129
172
|
// 计算连接线起点
|
|
130
173
|
let parentX;
|
|
174
|
+
let parentY;
|
|
131
175
|
if (edgeOrigin === 'distributed' && siblingCount > 1) {
|
|
132
176
|
// 分布式起点:根据子节点数量分配起点位置
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
177
|
+
if (isHorizontal) {
|
|
178
|
+
const padding = distributedPadding(edgeOriginPadding, parentBounds.height);
|
|
179
|
+
const startY = parentRect.top + padding;
|
|
180
|
+
const endY = parentRect.bottom - padding;
|
|
181
|
+
const segmentHeight = (endY - startY) / siblingCount;
|
|
182
|
+
parentY = startY + segmentHeight * siblingIndex + segmentHeight / 2;
|
|
183
|
+
parentX =
|
|
184
|
+
(mainSign > 0 ? parentRect.right : parentRect.left) +
|
|
185
|
+
edgeOffset * mainSign;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
const padding = distributedPadding(edgeOriginPadding, parentBounds.width);
|
|
189
|
+
const startX = parentRect.left + padding;
|
|
190
|
+
const endX = parentRect.right - padding;
|
|
191
|
+
const segmentWidth = (endX - startX) / siblingCount;
|
|
192
|
+
parentX = startX + segmentWidth * siblingIndex + segmentWidth / 2;
|
|
193
|
+
parentY =
|
|
194
|
+
(mainSign > 0 ? parentRect.bottom : parentRect.top) +
|
|
195
|
+
edgeOffset * mainSign;
|
|
196
|
+
}
|
|
137
197
|
}
|
|
138
198
|
else {
|
|
139
199
|
// 中心起点:所有线从节点中心出发
|
|
140
|
-
parentX =
|
|
200
|
+
parentX = isHorizontal
|
|
201
|
+
? (mainSign > 0 ? parentRect.right : parentRect.left) +
|
|
202
|
+
edgeOffset * mainSign
|
|
203
|
+
: parentRect.centerX;
|
|
204
|
+
parentY = isHorizontal
|
|
205
|
+
? parentRect.centerY
|
|
206
|
+
: (mainSign > 0 ? parentRect.bottom : parentRect.top) +
|
|
207
|
+
edgeOffset * mainSign;
|
|
141
208
|
}
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
209
|
+
const baseChildX = isHorizontal
|
|
210
|
+
? (mainSign > 0 ? nodeRect.left : nodeRect.right) -
|
|
211
|
+
edgeOffset * mainSign
|
|
212
|
+
: nodeRect.centerX;
|
|
213
|
+
const baseChildY = isHorizontal
|
|
214
|
+
? nodeRect.centerY
|
|
215
|
+
: (mainSign > 0 ? nodeRect.top : nodeRect.bottom) -
|
|
216
|
+
edgeOffset * mainSign;
|
|
217
|
+
let childX = baseChildX;
|
|
218
|
+
let childY = baseChildY;
|
|
145
219
|
// 调整终点位置(为箭头留出空间)
|
|
146
220
|
if (edgeMarker === 'arrow') {
|
|
147
|
-
|
|
221
|
+
if (isHorizontal) {
|
|
222
|
+
childX -= markerSize * mainSign;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
childY -= markerSize * mainSign;
|
|
226
|
+
}
|
|
148
227
|
}
|
|
149
228
|
// 生成路径
|
|
150
229
|
let pathD;
|
|
151
230
|
if (edgeType === 'curved') {
|
|
152
231
|
// 使用贝塞尔曲线绘制曲线连接
|
|
153
|
-
|
|
154
|
-
|
|
232
|
+
if (isHorizontal) {
|
|
233
|
+
const midX = parentX + (childX - parentX) / 2;
|
|
234
|
+
pathD = `M ${parentX} ${parentY} C ${midX} ${parentY}, ${midX} ${childY}, ${childX} ${childY}`;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const midY = parentY + (childY - parentY) / 2;
|
|
238
|
+
pathD = `M ${parentX} ${parentY} C ${parentX} ${midY}, ${childX} ${midY}, ${childX} ${childY}`;
|
|
239
|
+
}
|
|
155
240
|
}
|
|
156
241
|
else if (edgeCornerRadius > 0) {
|
|
157
242
|
// 使用圆角路径
|
|
158
|
-
pathD = createRoundedPath(parentX, parentY, childX, childY, edgeCornerRadius);
|
|
243
|
+
pathD = createRoundedPath(parentX, parentY, childX, childY, edgeCornerRadius, isHorizontal ? 'horizontal' : 'vertical');
|
|
159
244
|
}
|
|
160
245
|
else {
|
|
161
246
|
// 使用直角折线
|
|
162
|
-
|
|
163
|
-
|
|
247
|
+
if (isHorizontal) {
|
|
248
|
+
const midX = parentX + (childX - parentX) / 2;
|
|
249
|
+
pathD = `M ${parentX} ${parentY} L ${midX} ${parentY} L ${midX} ${childY} L ${childX} ${childY}`;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const midY = parentY + (childY - parentY) / 2;
|
|
253
|
+
pathD = `M ${parentX} ${parentY} L ${parentX} ${midY} L ${childX} ${midY} L ${childX} ${childY}`;
|
|
254
|
+
}
|
|
164
255
|
}
|
|
165
256
|
// 确定连接线颜色
|
|
166
257
|
let strokeColor = colorPrimary;
|
|
@@ -192,17 +283,29 @@ export const HierarchyTree = (props) => {
|
|
|
192
283
|
? getPaletteColor(options, colorIndexes)
|
|
193
284
|
: getColorPrimary(options);
|
|
194
285
|
// 三角形箭头
|
|
195
|
-
const arrowPoints =
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
286
|
+
const arrowPoints = isHorizontal
|
|
287
|
+
? [
|
|
288
|
+
{ x: baseChildX, y: baseChildY },
|
|
289
|
+
{
|
|
290
|
+
x: baseChildX - markerSize * mainSign,
|
|
291
|
+
y: baseChildY - markerSize / 2,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
x: baseChildX - markerSize * mainSign,
|
|
295
|
+
y: baseChildY + markerSize / 2,
|
|
296
|
+
},
|
|
297
|
+
]
|
|
298
|
+
: [
|
|
299
|
+
{ x: baseChildX, y: baseChildY },
|
|
300
|
+
{
|
|
301
|
+
x: baseChildX - markerSize / 2,
|
|
302
|
+
y: baseChildY - markerSize * mainSign,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
x: baseChildX + markerSize / 2,
|
|
306
|
+
y: baseChildY - markerSize * mainSign,
|
|
307
|
+
},
|
|
308
|
+
];
|
|
206
309
|
elements.deco.push(_jsx(Polygon, { points: arrowPoints, fill: arrowColor, width: markerSize, height: markerSize }));
|
|
207
310
|
}
|
|
208
311
|
// 添加连接点装饰
|
|
@@ -216,16 +319,20 @@ export const HierarchyTree = (props) => {
|
|
|
216
319
|
? getPaletteColor(options, parentColorIndexes)
|
|
217
320
|
: getColorPrimary(options);
|
|
218
321
|
// 父节点连接点
|
|
219
|
-
elements.deco.push(_jsx(Ellipse, { x:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
322
|
+
elements.deco.push(_jsx(Ellipse, { x: (isHorizontal
|
|
323
|
+
? mainSign > 0
|
|
324
|
+
? parentRect.right + edgeOffset
|
|
325
|
+
: parentRect.left - edgeOffset
|
|
326
|
+
: parentX) - markerSize, y: (isHorizontal
|
|
327
|
+
? parentY
|
|
328
|
+
: mainSign > 0
|
|
329
|
+
? parentRect.bottom + edgeOffset
|
|
330
|
+
: parentRect.top - edgeOffset) - markerSize, width: markerSize * 2, height: markerSize * 2, fill: parentDotColor }));
|
|
224
331
|
// 子节点连接点
|
|
225
332
|
const childDotColor = edgeColorMode === 'gradient'
|
|
226
333
|
? getPaletteColor(options, colorIndexes)
|
|
227
334
|
: getColorPrimary(options);
|
|
228
|
-
elements.deco.push(_jsx(Ellipse, { x:
|
|
335
|
+
elements.deco.push(_jsx(Ellipse, { x: baseChildX - markerSize, y: baseChildY - markerSize, width: markerSize * 2, height: markerSize * 2, fill: childDotColor }));
|
|
229
336
|
}
|
|
230
337
|
}
|
|
231
338
|
return elements;
|
|
@@ -243,13 +350,30 @@ export const HierarchyTree = (props) => {
|
|
|
243
350
|
nodesByParent.forEach((siblings) => {
|
|
244
351
|
if (siblings.length <= 1)
|
|
245
352
|
return;
|
|
246
|
-
const sorted = siblings
|
|
247
|
-
|
|
353
|
+
const sorted = siblings
|
|
354
|
+
.slice()
|
|
355
|
+
.sort((a, b) => isHorizontal
|
|
356
|
+
? getLayoutPoint(a).y - getLayoutPoint(b).y
|
|
357
|
+
: getLayoutPoint(a).x - getLayoutPoint(b).x);
|
|
358
|
+
if (sorted.length === 0)
|
|
359
|
+
return;
|
|
248
360
|
for (let i = 0; i < sorted.length - 1; i++) {
|
|
249
|
-
const
|
|
361
|
+
const current = getLayoutPoint(sorted[i]);
|
|
362
|
+
const next = getLayoutPoint(sorted[i + 1]);
|
|
250
363
|
const parentIndexes = sorted[i].data._originalIndex.slice(0, -1);
|
|
251
364
|
const insertIndex = sorted[i].data._originalIndex.at(-1) + 1;
|
|
252
|
-
|
|
365
|
+
if (isHorizontal) {
|
|
366
|
+
const btnX = current.x +
|
|
367
|
+
offsets.x +
|
|
368
|
+
(mainSign > 0 ? -btnBounds.width - 5 : btnBounds.width + 5);
|
|
369
|
+
const btnY = (current.y + next.y) / 2 + offsets.y - btnBounds.height / 2;
|
|
370
|
+
btns.push(_jsx(BtnAdd, { indexes: [...parentIndexes, insertIndex], x: btnX, y: btnY }));
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
const siblingY = current.y + offsets.y - btnBounds.height - 5;
|
|
374
|
+
const btnX = (current.x + next.x) / 2 + offsets.x - btnBounds.width / 2;
|
|
375
|
+
btns.push(_jsx(BtnAdd, { indexes: [...parentIndexes, insertIndex], x: btnX, y: siblingY }));
|
|
376
|
+
}
|
|
253
377
|
}
|
|
254
378
|
});
|
|
255
379
|
return btns;
|
|
@@ -268,12 +392,15 @@ export const HierarchyTree = (props) => {
|
|
|
268
392
|
const { levelBounds, maxWidth, maxHeight } = computeLevelBounds(root);
|
|
269
393
|
const treeLayout = d3
|
|
270
394
|
.tree()
|
|
271
|
-
.nodeSize(
|
|
395
|
+
.nodeSize(isHorizontal
|
|
396
|
+
? [maxHeight + nodeGap, maxWidth + levelGap]
|
|
397
|
+
: [maxWidth + nodeGap, maxHeight + levelGap])
|
|
272
398
|
.separation(() => 1);
|
|
273
399
|
const nodes = treeLayout(root).descendants();
|
|
274
400
|
// 计算偏移量
|
|
275
|
-
const
|
|
276
|
-
const
|
|
401
|
+
const layoutPoints = nodes.map((d) => getLayoutPoint(d));
|
|
402
|
+
const minX = Math.min(...layoutPoints.map((d) => d.x));
|
|
403
|
+
const minY = Math.min(...layoutPoints.map((d) => d.y));
|
|
277
404
|
const offsets = {
|
|
278
405
|
x: Math.max(0, -minX + maxWidth / 2),
|
|
279
406
|
y: Math.max(0, -minY + btnBounds.height + 10),
|
|
@@ -287,6 +414,8 @@ export const HierarchyTree = (props) => {
|
|
|
287
414
|
nodes.forEach((node, index) => {
|
|
288
415
|
// 将扁平索引附加到节点数据上,用于 node-flat 模式
|
|
289
416
|
node.data._flatIndex = index;
|
|
417
|
+
const { x, y } = getLayoutPoint(node);
|
|
418
|
+
node.__layout = { x, y };
|
|
290
419
|
});
|
|
291
420
|
nodes.forEach((node) => {
|
|
292
421
|
const { items, btns, deco } = renderNode(node, levelBounds, btnBounds, offsets, gradientDefs, nodes);
|
|
@@ -2,6 +2,7 @@ export * from './chart-bar';
|
|
|
2
2
|
export * from './chart-column';
|
|
3
3
|
export * from './chart-line';
|
|
4
4
|
export * from './chart-pie';
|
|
5
|
+
export * from './chart-wordcloud';
|
|
5
6
|
export * from './compare-binary-horizontal';
|
|
6
7
|
export * from './compare-hierarchy-left-right';
|
|
7
8
|
export * from './compare-hierarchy-row';
|
|
@@ -30,6 +31,7 @@ export * from './sequence-mountain';
|
|
|
30
31
|
export * from './sequence-pyramid';
|
|
31
32
|
export * from './sequence-roadmap-vertical';
|
|
32
33
|
export * from './sequence-snake-steps';
|
|
34
|
+
export * from './sequence-stairs-front';
|
|
33
35
|
export * from './sequence-steps';
|
|
34
36
|
export * from './sequence-timeline';
|
|
35
37
|
export * from './sequence-zigzag-pucks-3d';
|
|
@@ -2,6 +2,7 @@ export * from './chart-bar';
|
|
|
2
2
|
export * from './chart-column';
|
|
3
3
|
export * from './chart-line';
|
|
4
4
|
export * from './chart-pie';
|
|
5
|
+
export * from './chart-wordcloud';
|
|
5
6
|
export * from './compare-binary-horizontal';
|
|
6
7
|
export * from './compare-hierarchy-left-right';
|
|
7
8
|
export * from './compare-hierarchy-row';
|
|
@@ -30,6 +31,7 @@ export * from './sequence-mountain';
|
|
|
30
31
|
export * from './sequence-pyramid';
|
|
31
32
|
export * from './sequence-roadmap-vertical';
|
|
32
33
|
export * from './sequence-snake-steps';
|
|
34
|
+
export * from './sequence-stairs-front';
|
|
33
35
|
export * from './sequence-steps';
|
|
34
36
|
export * from './sequence-timeline';
|
|
35
37
|
export * from './sequence-zigzag-pucks-3d';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ComponentType } from '../../jsx';
|
|
2
|
+
import type { BaseStructureProps } from './types';
|
|
3
|
+
export interface SequenceStairsFrontProps extends BaseStructureProps {
|
|
4
|
+
gap?: number;
|
|
5
|
+
perspectiveFactor?: number;
|
|
6
|
+
width?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const SequenceStairsFront: ComponentType<SequenceStairsFrontProps>;
|