@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.
Files changed (171) hide show
  1. package/dist/infographic.min.js +110 -105
  2. package/dist/infographic.min.js.map +1 -1
  3. package/esm/constants/element.d.ts +1 -1
  4. package/esm/constants/index.d.ts +1 -0
  5. package/esm/constants/index.js +1 -0
  6. package/esm/constants/service.d.ts +1 -0
  7. package/esm/constants/service.js +1 -0
  8. package/esm/designs/components/Illus.js +1 -1
  9. package/esm/designs/structures/chart-wordcloud.d.ts +11 -0
  10. package/esm/designs/structures/chart-wordcloud.js +156 -0
  11. package/esm/designs/structures/hierarchy-tree.d.ts +2 -0
  12. package/esm/designs/structures/hierarchy-tree.js +179 -50
  13. package/esm/designs/structures/index.d.ts +2 -0
  14. package/esm/designs/structures/index.js +2 -0
  15. package/esm/designs/structures/sequence-stairs-front.d.ts +8 -0
  16. package/esm/designs/structures/sequence-stairs-front.js +116 -0
  17. package/esm/designs/types.d.ts +8 -0
  18. package/esm/editor/managers/state.js +1 -1
  19. package/esm/index.d.ts +2 -0
  20. package/esm/index.js +1 -0
  21. package/esm/options/parser.d.ts +1 -1
  22. package/esm/options/parser.js +33 -15
  23. package/esm/renderer/composites/icon.js +1 -1
  24. package/esm/renderer/composites/illus.js +1 -1
  25. package/esm/resource/loader.d.ts +2 -2
  26. package/esm/resource/loader.js +22 -11
  27. package/esm/resource/loaders/index.d.ts +1 -0
  28. package/esm/resource/loaders/index.js +1 -0
  29. package/esm/resource/loaders/remote.d.ts +1 -1
  30. package/esm/resource/loaders/remote.js +10 -2
  31. package/esm/resource/loaders/search.d.ts +1 -0
  32. package/esm/resource/loaders/search.js +51 -0
  33. package/esm/resource/types/index.d.ts +1 -0
  34. package/esm/resource/types/resource.d.ts +8 -1
  35. package/esm/resource/types/scene.d.ts +1 -0
  36. package/esm/resource/utils/data-uri.js +20 -11
  37. package/esm/resource/utils/parser.js +92 -1
  38. package/esm/resource/utils/ref.js +2 -2
  39. package/esm/runtime/Infographic.d.ts +7 -6
  40. package/esm/runtime/Infographic.js +48 -17
  41. package/esm/runtime/utils.d.ts +4 -2
  42. package/esm/runtime/utils.js +33 -13
  43. package/esm/syntax/index.d.ts +3 -0
  44. package/esm/syntax/index.js +101 -0
  45. package/esm/syntax/mapper.d.ts +3 -0
  46. package/esm/syntax/mapper.js +238 -0
  47. package/esm/syntax/parser.d.ts +14 -0
  48. package/esm/syntax/parser.js +142 -0
  49. package/esm/syntax/schema.d.ts +6 -0
  50. package/esm/syntax/schema.js +74 -0
  51. package/esm/syntax/types.d.ts +61 -0
  52. package/esm/syntax/types.js +1 -0
  53. package/esm/templates/built-in.js +4 -0
  54. package/esm/templates/hierarchy-tree.js +25 -11
  55. package/esm/templates/sequence-stairs.d.ts +2 -0
  56. package/esm/templates/sequence-stairs.js +42 -0
  57. package/esm/templates/word-cloud.d.ts +2 -0
  58. package/esm/templates/word-cloud.js +19 -0
  59. package/esm/themes/types.d.ts +1 -1
  60. package/esm/utils/design.d.ts +2 -0
  61. package/esm/utils/design.js +10 -0
  62. package/esm/utils/font.js +11 -1
  63. package/esm/utils/index.d.ts +1 -0
  64. package/esm/utils/index.js +1 -0
  65. package/lib/constants/element.d.ts +1 -1
  66. package/lib/constants/index.d.ts +1 -0
  67. package/lib/constants/index.js +1 -0
  68. package/lib/constants/service.d.ts +1 -0
  69. package/lib/constants/service.js +4 -0
  70. package/lib/designs/components/Illus.js +1 -1
  71. package/lib/designs/structures/chart-wordcloud.d.ts +11 -0
  72. package/lib/designs/structures/chart-wordcloud.js +160 -0
  73. package/lib/designs/structures/hierarchy-tree.d.ts +2 -0
  74. package/lib/designs/structures/hierarchy-tree.js +179 -50
  75. package/lib/designs/structures/index.d.ts +2 -0
  76. package/lib/designs/structures/index.js +2 -0
  77. package/lib/designs/structures/sequence-stairs-front.d.ts +8 -0
  78. package/lib/designs/structures/sequence-stairs-front.js +120 -0
  79. package/lib/designs/types.d.ts +8 -0
  80. package/lib/editor/managers/state.js +1 -1
  81. package/lib/index.d.ts +2 -0
  82. package/lib/index.js +4 -1
  83. package/lib/options/parser.d.ts +1 -1
  84. package/lib/options/parser.js +32 -14
  85. package/lib/renderer/composites/icon.js +1 -1
  86. package/lib/renderer/composites/illus.js +1 -1
  87. package/lib/resource/loader.d.ts +2 -2
  88. package/lib/resource/loader.js +21 -10
  89. package/lib/resource/loaders/index.d.ts +1 -0
  90. package/lib/resource/loaders/index.js +1 -0
  91. package/lib/resource/loaders/remote.d.ts +1 -1
  92. package/lib/resource/loaders/remote.js +10 -2
  93. package/lib/resource/loaders/search.d.ts +1 -0
  94. package/lib/resource/loaders/search.js +54 -0
  95. package/lib/resource/types/index.d.ts +1 -0
  96. package/lib/resource/types/resource.d.ts +8 -1
  97. package/lib/resource/types/scene.d.ts +1 -0
  98. package/lib/resource/utils/data-uri.js +20 -11
  99. package/lib/resource/utils/parser.js +92 -1
  100. package/lib/resource/utils/ref.js +2 -2
  101. package/lib/runtime/Infographic.d.ts +7 -6
  102. package/lib/runtime/Infographic.js +47 -16
  103. package/lib/runtime/utils.d.ts +4 -2
  104. package/lib/runtime/utils.js +35 -13
  105. package/lib/syntax/index.d.ts +3 -0
  106. package/lib/syntax/index.js +104 -0
  107. package/lib/syntax/mapper.d.ts +3 -0
  108. package/lib/syntax/mapper.js +242 -0
  109. package/lib/syntax/parser.d.ts +14 -0
  110. package/lib/syntax/parser.js +146 -0
  111. package/lib/syntax/schema.d.ts +6 -0
  112. package/lib/syntax/schema.js +77 -0
  113. package/lib/syntax/types.d.ts +61 -0
  114. package/lib/syntax/types.js +2 -0
  115. package/lib/templates/built-in.js +4 -0
  116. package/lib/templates/hierarchy-tree.js +25 -11
  117. package/lib/templates/sequence-stairs.d.ts +2 -0
  118. package/lib/templates/sequence-stairs.js +45 -0
  119. package/lib/templates/word-cloud.d.ts +2 -0
  120. package/lib/templates/word-cloud.js +22 -0
  121. package/lib/themes/types.d.ts +1 -1
  122. package/lib/utils/design.d.ts +2 -0
  123. package/lib/utils/design.js +13 -0
  124. package/lib/utils/font.js +11 -1
  125. package/lib/utils/index.d.ts +1 -0
  126. package/lib/utils/index.js +1 -0
  127. package/package.json +1 -1
  128. package/src/constants/element.ts +1 -1
  129. package/src/constants/index.ts +1 -0
  130. package/src/constants/service.ts +1 -0
  131. package/src/designs/components/Illus.tsx +1 -1
  132. package/src/designs/structures/chart-wordcloud.tsx +278 -0
  133. package/src/designs/structures/hierarchy-tree.tsx +212 -59
  134. package/src/designs/structures/index.ts +2 -0
  135. package/src/designs/structures/sequence-stairs-front.tsx +291 -0
  136. package/src/designs/types.ts +9 -0
  137. package/src/editor/managers/state.ts +1 -1
  138. package/src/index.ts +2 -0
  139. package/src/options/parser.ts +57 -28
  140. package/src/renderer/composites/icon.ts +1 -1
  141. package/src/renderer/composites/illus.ts +1 -1
  142. package/src/resource/loader.ts +22 -8
  143. package/src/resource/loaders/index.ts +1 -0
  144. package/src/resource/loaders/remote.ts +9 -2
  145. package/src/resource/loaders/search.ts +52 -0
  146. package/src/resource/types/index.ts +2 -1
  147. package/src/resource/types/resource.ts +12 -1
  148. package/src/resource/types/scene.ts +1 -0
  149. package/src/resource/utils/data-uri.ts +20 -11
  150. package/src/resource/utils/parser.ts +103 -2
  151. package/src/resource/utils/ref.ts +2 -2
  152. package/src/runtime/Infographic.tsx +74 -22
  153. package/src/runtime/utils.ts +38 -16
  154. package/src/syntax/index.ts +124 -0
  155. package/src/syntax/mapper.ts +362 -0
  156. package/src/syntax/parser.ts +171 -0
  157. package/src/syntax/schema.ts +98 -0
  158. package/src/syntax/types.ts +89 -0
  159. package/src/templates/built-in.ts +4 -0
  160. package/src/templates/hierarchy-tree.ts +34 -11
  161. package/src/templates/sequence-stairs.ts +44 -0
  162. package/src/templates/word-cloud.ts +21 -0
  163. package/src/themes/types.ts +1 -1
  164. package/src/utils/design.ts +14 -0
  165. package/src/utils/font.ts +11 -1
  166. package/src/utils/index.ts +1 -0
  167. package/esm/resource/types/font.d.ts +0 -12
  168. package/lib/resource/types/font.d.ts +0 -12
  169. package/src/resource/types/font.ts +0 -23
  170. /package/esm/resource/types/{font.js → scene.js} +0 -0
  171. /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
- ItemsIllus = "items-illus",
14
+ ItemIllus = "item-illus",
15
15
  BtnAdd = "btn-add",
16
16
  BtnRemove = "btn-remove",
17
17
  IllusGroup = "illus-group",
@@ -1,3 +1,4 @@
1
1
  export * from './components';
2
2
  export * from './element';
3
+ export * from './service';
3
4
  export * from './shape';
@@ -1,3 +1,4 @@
1
1
  export * from './components';
2
2
  export * from './element';
3
+ export * from './service';
3
4
  export * from './shape';
@@ -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'] = "items-illus" /* ElementTypeEnum.ItemsIllus */;
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', options, } = props;
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 midY = y1 + (y2 - y1) / 2;
34
- const effectiveRadius = Math.min(radius, Math.abs(y2 - y1) / 2, Math.abs(x2 - x1) / 2);
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 `M ${x1} ${y1} L ${x1} ${midY} L ${x2} ${midY} L ${x2} ${y2}`;
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 ${x1} ${midY - effectiveRadius}
40
- Q ${x1} ${midY} ${x1 + (x2 > x1 ? effectiveRadius : -effectiveRadius)} ${midY}
41
- L ${x2 - (x2 > x1 ? effectiveRadius : -effectiveRadius)} ${midY}
42
- Q ${x2} ${midY} ${x2} ${midY + effectiveRadius}
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 { x, y, depth, data: nodeData, parent } = node;
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 nodeX = x + offsets.x - bounds.width / 2;
102
- const nodeY = y + offsets.y;
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
- const startX = parent.x + offsets.x - parentBounds.width / 2 + edgeOriginPadding;
134
- const endX = parent.x + offsets.x + parentBounds.width / 2 - edgeOriginPadding;
135
- const segmentWidth = (endX - startX) / siblingCount;
136
- parentX = startX + segmentWidth * siblingIndex + segmentWidth / 2;
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 = parent.x + offsets.x;
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 parentY = parent.y + offsets.y + parentBounds.height + edgeOffset;
143
- const childX = x + offsets.x;
144
- let childY = y + offsets.y - edgeOffset;
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
- childY -= markerSize;
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
- const midY = parentY + (childY - parentY) / 2;
154
- pathD = `M ${parentX} ${parentY} C ${parentX} ${midY}, ${childX} ${midY}, ${childX} ${childY}`;
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
- const midY = parentY + (childY - parentY) / 2;
163
- pathD = `M ${parentX} ${parentY} L ${parentX} ${midY} L ${childX} ${midY} L ${childX} ${childY}`;
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
- { x: childX, y: y + offsets.y - edgeOffset },
197
- {
198
- x: childX - markerSize / 2,
199
- y: y + offsets.y - edgeOffset - markerSize,
200
- },
201
- {
202
- x: childX + markerSize / 2,
203
- y: y + offsets.y - edgeOffset - markerSize,
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: parentX - markerSize, y: parent.y +
220
- offsets.y +
221
- parentBounds.height +
222
- edgeOffset -
223
- markerSize, width: markerSize * 2, height: markerSize * 2, fill: parentDotColor }));
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: childX - markerSize, y: y + offsets.y - edgeOffset - markerSize, width: markerSize * 2, height: markerSize * 2, fill: childDotColor }));
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.slice().sort((a, b) => a.x - b.x);
247
- const siblingY = sorted[0].y + offsets.y - btnBounds.height - 5;
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 btnX = (sorted[i].x + sorted[i + 1].x) / 2 + offsets.x - btnBounds.width / 2;
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
- btns.push(_jsx(BtnAdd, { indexes: [...parentIndexes, insertIndex], x: btnX, y: siblingY }));
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([maxWidth + nodeGap, maxHeight + levelGap])
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 minX = Math.min(...nodes.map((d) => d.x));
276
- const minY = Math.min(...nodes.map((d) => d.y));
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>;