@semcore/d3-chart 1.8.0 → 2.0.1

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 (179) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/lib/cjs/Area.js +9 -9
  3. package/lib/cjs/Axis.js +17 -19
  4. package/lib/cjs/Axis.js.map +1 -1
  5. package/lib/cjs/Bar.js +11 -11
  6. package/lib/cjs/Bar.js.map +1 -1
  7. package/lib/cjs/Bubble.js +13 -34
  8. package/lib/cjs/Bubble.js.map +1 -1
  9. package/lib/cjs/ClipPath.js +2 -1
  10. package/lib/cjs/ClipPath.js.map +1 -1
  11. package/lib/cjs/Donut.js +8 -8
  12. package/lib/cjs/Donut.js.map +1 -1
  13. package/lib/cjs/Dots.js +9 -9
  14. package/lib/cjs/Dots.js.map +1 -1
  15. package/lib/cjs/GroupBar.js +8 -8
  16. package/lib/cjs/GroupBar.js.map +1 -1
  17. package/lib/cjs/HorizontalBar.js +11 -11
  18. package/lib/cjs/HorizontalBar.js.map +1 -1
  19. package/lib/cjs/Hover.js +4 -4
  20. package/lib/cjs/Line.js +8 -8
  21. package/lib/cjs/Plot.js +15 -1
  22. package/lib/cjs/Plot.js.map +1 -1
  23. package/lib/cjs/RadialTree.js +694 -0
  24. package/lib/cjs/RadialTree.js.map +1 -0
  25. package/lib/cjs/ResponsiveContainer.js +1 -1
  26. package/lib/cjs/ResponsiveContainer.js.map +1 -1
  27. package/lib/cjs/ScatterPlot.js +7 -7
  28. package/lib/cjs/StackBar.js +8 -8
  29. package/lib/cjs/StackBar.js.map +1 -1
  30. package/lib/cjs/StackedArea.js +8 -8
  31. package/lib/cjs/StackedArea.js.map +1 -1
  32. package/lib/cjs/Tooltip.js +11 -15
  33. package/lib/cjs/Tooltip.js.map +1 -1
  34. package/lib/cjs/Venn.js +9 -9
  35. package/lib/cjs/Venn.js.map +1 -1
  36. package/lib/cjs/color.js +122 -30
  37. package/lib/cjs/color.js.map +1 -1
  38. package/lib/cjs/createElement.js +3 -2
  39. package/lib/cjs/createElement.js.map +1 -1
  40. package/lib/cjs/index.js +8 -0
  41. package/lib/cjs/index.js.map +1 -1
  42. package/lib/cjs/style/area.shadow.css +4 -4
  43. package/lib/cjs/style/axis.shadow.css +4 -4
  44. package/lib/cjs/style/bar.shadow.css +2 -2
  45. package/lib/cjs/style/bubble.shadow.css +3 -3
  46. package/lib/cjs/style/donut.shadow.css +2 -2
  47. package/lib/cjs/style/dot.shadow.css +1 -1
  48. package/lib/cjs/style/hover.shadow.css +2 -2
  49. package/lib/cjs/style/line.shadow.css +2 -2
  50. package/lib/cjs/style/plot.shadow.css +6 -0
  51. package/lib/cjs/style/radial-tree.shadow.css +34 -0
  52. package/lib/cjs/style/scatterplot.shadow.css +2 -2
  53. package/lib/cjs/style/tooltip.shadow.css +5 -5
  54. package/lib/cjs/style/var.css +123 -30
  55. package/lib/cjs/style/venn.shadow.css +1 -1
  56. package/lib/cjs/types/Area.d.ts +47 -0
  57. package/lib/cjs/types/Axis.d.ts +64 -0
  58. package/lib/cjs/types/Bar.d.ts +48 -0
  59. package/lib/cjs/types/Bubble.d.ts +27 -0
  60. package/lib/cjs/types/ClipPath.d.ts +26 -0
  61. package/lib/cjs/types/Donut.d.ts +42 -0
  62. package/lib/cjs/types/GroupBar.d.ts +20 -0
  63. package/lib/cjs/types/HorizontalBar.d.ts +29 -0
  64. package/lib/cjs/types/Hover.d.ts +15 -0
  65. package/lib/cjs/types/Line.d.ts +50 -0
  66. package/lib/cjs/types/Plot.d.ts +16 -0
  67. package/lib/cjs/types/ResponsiveContainer.d.ts +20 -0
  68. package/lib/cjs/types/ScatterPlot.d.ts +27 -0
  69. package/lib/cjs/types/StackBar.d.ts +26 -0
  70. package/lib/cjs/types/StackedArea.d.ts +26 -0
  71. package/lib/cjs/types/Tooltip.d.ts +31 -0
  72. package/lib/cjs/types/Venn.d.ts +45 -0
  73. package/lib/cjs/types/context.d.ts +6 -0
  74. package/lib/cjs/types/index.d.ts +53 -0
  75. package/lib/cjs/utils.js +120 -55
  76. package/lib/cjs/utils.js.map +1 -1
  77. package/lib/es6/Area.js +9 -9
  78. package/lib/es6/Axis.js +17 -19
  79. package/lib/es6/Axis.js.map +1 -1
  80. package/lib/es6/Bar.js +11 -11
  81. package/lib/es6/Bar.js.map +1 -1
  82. package/lib/es6/Bubble.js +13 -34
  83. package/lib/es6/Bubble.js.map +1 -1
  84. package/lib/es6/ClipPath.js +2 -1
  85. package/lib/es6/ClipPath.js.map +1 -1
  86. package/lib/es6/Donut.js +8 -8
  87. package/lib/es6/Donut.js.map +1 -1
  88. package/lib/es6/Dots.js +9 -9
  89. package/lib/es6/Dots.js.map +1 -1
  90. package/lib/es6/GroupBar.js +8 -8
  91. package/lib/es6/GroupBar.js.map +1 -1
  92. package/lib/es6/HorizontalBar.js +11 -11
  93. package/lib/es6/HorizontalBar.js.map +1 -1
  94. package/lib/es6/Hover.js +4 -4
  95. package/lib/es6/Line.js +8 -8
  96. package/lib/es6/Plot.js +17 -1
  97. package/lib/es6/Plot.js.map +1 -1
  98. package/lib/es6/RadialTree.js +682 -0
  99. package/lib/es6/RadialTree.js.map +1 -0
  100. package/lib/es6/ResponsiveContainer.js +1 -1
  101. package/lib/es6/ResponsiveContainer.js.map +1 -1
  102. package/lib/es6/ScatterPlot.js +7 -7
  103. package/lib/es6/StackBar.js +8 -8
  104. package/lib/es6/StackBar.js.map +1 -1
  105. package/lib/es6/StackedArea.js +8 -8
  106. package/lib/es6/StackedArea.js.map +1 -1
  107. package/lib/es6/Tooltip.js +11 -15
  108. package/lib/es6/Tooltip.js.map +1 -1
  109. package/lib/es6/Venn.js +9 -9
  110. package/lib/es6/Venn.js.map +1 -1
  111. package/lib/es6/color.js +122 -30
  112. package/lib/es6/color.js.map +1 -1
  113. package/lib/es6/createElement.js +3 -2
  114. package/lib/es6/createElement.js.map +1 -1
  115. package/lib/es6/index.js +1 -0
  116. package/lib/es6/index.js.map +1 -1
  117. package/lib/es6/style/area.shadow.css +4 -4
  118. package/lib/es6/style/axis.shadow.css +4 -4
  119. package/lib/es6/style/bar.shadow.css +2 -2
  120. package/lib/es6/style/bubble.shadow.css +3 -3
  121. package/lib/es6/style/donut.shadow.css +2 -2
  122. package/lib/es6/style/dot.shadow.css +1 -1
  123. package/lib/es6/style/hover.shadow.css +2 -2
  124. package/lib/es6/style/line.shadow.css +2 -2
  125. package/lib/es6/style/plot.shadow.css +6 -0
  126. package/lib/es6/style/radial-tree.shadow.css +34 -0
  127. package/lib/es6/style/scatterplot.shadow.css +2 -2
  128. package/lib/es6/style/tooltip.shadow.css +5 -5
  129. package/lib/es6/style/var.css +123 -30
  130. package/lib/es6/style/venn.shadow.css +1 -1
  131. package/lib/es6/types/Area.d.ts +47 -0
  132. package/lib/es6/types/Axis.d.ts +64 -0
  133. package/lib/es6/types/Bar.d.ts +48 -0
  134. package/lib/es6/types/Bubble.d.ts +27 -0
  135. package/lib/es6/types/ClipPath.d.ts +26 -0
  136. package/lib/es6/types/Donut.d.ts +42 -0
  137. package/lib/es6/types/GroupBar.d.ts +20 -0
  138. package/lib/es6/types/HorizontalBar.d.ts +29 -0
  139. package/lib/es6/types/Hover.d.ts +15 -0
  140. package/lib/es6/types/Line.d.ts +50 -0
  141. package/lib/es6/types/Plot.d.ts +16 -0
  142. package/lib/es6/types/ResponsiveContainer.d.ts +20 -0
  143. package/lib/es6/types/ScatterPlot.d.ts +27 -0
  144. package/lib/es6/types/StackBar.d.ts +26 -0
  145. package/lib/es6/types/StackedArea.d.ts +26 -0
  146. package/lib/es6/types/Tooltip.d.ts +31 -0
  147. package/lib/es6/types/Venn.d.ts +45 -0
  148. package/lib/es6/types/context.d.ts +6 -0
  149. package/lib/es6/types/index.d.ts +53 -0
  150. package/lib/es6/utils.js +95 -44
  151. package/lib/es6/utils.js.map +1 -1
  152. package/lib/types/RadialTree.d.ts +202 -0
  153. package/lib/types/index.d.ts +6 -0
  154. package/lib/types/utils.d.ts +27 -0
  155. package/package.json +22 -10
  156. package/src/Bubble.jsx +1 -21
  157. package/src/ClipPath.jsx +1 -0
  158. package/src/Donut.jsx +7 -9
  159. package/src/Plot.jsx +2 -0
  160. package/src/RadialTree.tsx +767 -0
  161. package/src/createElement.jsx +3 -1
  162. package/src/index.js +1 -0
  163. package/src/style/area.shadow.css +4 -4
  164. package/src/style/axis.shadow.css +4 -4
  165. package/src/style/bar.shadow.css +2 -2
  166. package/src/style/bubble.shadow.css +3 -3
  167. package/src/style/donut.shadow.css +2 -2
  168. package/src/style/dot.shadow.css +1 -1
  169. package/src/style/hover.shadow.css +2 -2
  170. package/src/style/line.shadow.css +2 -2
  171. package/src/style/plot.shadow.css +6 -0
  172. package/src/style/radial-tree.shadow.css +34 -0
  173. package/src/style/scatterplot.shadow.css +2 -2
  174. package/src/style/tooltip.shadow.css +5 -5
  175. package/src/style/var.css +123 -30
  176. package/src/style/venn.shadow.css +1 -1
  177. package/src/types/index.d.ts +6 -0
  178. package/src/utils.ts +227 -0
  179. package/src/utils.js +0 -147
@@ -0,0 +1,767 @@
1
+ import React from 'react';
2
+ import { Component, sstyled, CProps, ReturnEl } from '@semcore/core';
3
+ // @ts-ignore
4
+ import createElement from './createElement';
5
+ import uniqueIDEnhancement from '@semcore/utils/lib/uniqueID';
6
+ import { transition } from 'd3-transition';
7
+ import style from './style/radial-tree.shadow.css';
8
+ import { shade } from '@semcore/utils/lib/color';
9
+ import { measureText } from './utils';
10
+ import assignProps from '@semcore/utils/lib/assignProps';
11
+ import getOriginChildren from '@semcore/utils/lib/getOriginChildren';
12
+
13
+ const baseAngle = -Math.PI / 2; // The top vertical line
14
+
15
+ type RadianData = {
16
+ /**
17
+ * Text label on the end of radian.
18
+ */
19
+ label: string;
20
+ /**
21
+ * Radian key. Radian, which `key` is equal to `activeKey` is displayed as active.
22
+ */
23
+ key: string;
24
+ /**
25
+ * Size of the cap (filled circle on the end of the radian).
26
+ * @default 16
27
+ */
28
+ capSize?: number;
29
+ /**
30
+ * Icon, that displayed in the cap of the active radian (usage example `icon: UserM`).
31
+ */
32
+ icon?: React.FC;
33
+ /**
34
+ * Color of radian's line, cap and label.
35
+ * @default #008FF8
36
+ */
37
+ color?: string;
38
+ /**
39
+ * Color of icon.
40
+ * @default #fff
41
+ */
42
+ iconColor?: string;
43
+ /**
44
+ * Size of icon.
45
+ * @default 16
46
+ */
47
+ iconSize?: number;
48
+ };
49
+
50
+ export interface IRadialTreeProps {
51
+ /**
52
+ * List of radians. `{ label: string; key: string; capSize?: number; icon?: React.FC; color?: string; iconColor?: string; iconSize?: number; }`
53
+ */
54
+ data: RadianData[];
55
+ /**
56
+ * Angel (in rads) that rotates chart. 0 means that first radian is exactly on top vertical line.
57
+ * @default 0
58
+ */
59
+ angleOffset?: number;
60
+ /**
61
+ * Duration of appear and update animation.
62
+ * @default 300
63
+ */
64
+ duration?: number;
65
+ /**
66
+ * Gap between the chart center and radians start point.
67
+ * @default 50
68
+ */
69
+ centralMargin?: number;
70
+ /**
71
+ * Gap around text label.
72
+ * @default 2
73
+ */
74
+ labelMargin?: number;
75
+ /**
76
+ * Color of all radian's line, cap and label.
77
+ * @default #008FF8
78
+ */
79
+ color?: string;
80
+ /**
81
+ * Size of the cap (filled circle on the end of the radian).
82
+ * @default 16
83
+ */
84
+ capSize?: number;
85
+ /**
86
+ * Icon, that displayed in the cap of the active radian (usage example `icon: UserM`).
87
+ */
88
+ icon?: React.FC;
89
+ /**
90
+ * Color of icon.
91
+ * @default #fff
92
+ */
93
+ iconColor?: string;
94
+ /**
95
+ * Size of icon.
96
+ * @default 16
97
+ */
98
+ iconSize?: number;
99
+ /**
100
+ * Text size in radians' labels. 1.5x is used as default text size of center text.
101
+ * @default 14
102
+ */
103
+ textSize?: number;
104
+ /**
105
+ * Used to define the active radian in controlled way. Active radian is highligted with inreased cap size.
106
+ */
107
+ activeKey?: string | null;
108
+ /**
109
+ * Default value for `activeKey` property.
110
+ */
111
+ defaultActiveKey?: string | null;
112
+ }
113
+
114
+ type RootAsProps = IRadialTreeProps & {
115
+ uid: string;
116
+ styles: React.CSSProperties;
117
+ activeKey: string | null;
118
+ Children: React.FC;
119
+ size: [width: number, height: number];
120
+ data: RadianData[];
121
+ duration: number;
122
+ textSize: number;
123
+ capSize: number;
124
+ centralMargin: number;
125
+ angleOffset: number;
126
+ $rootProps: RootAsProps;
127
+ };
128
+
129
+ class RadialTreeBase extends Component<RootAsProps> {
130
+ static displayName = 'RadialTree';
131
+ static style = style;
132
+ static enhance = [uniqueIDEnhancement()];
133
+
134
+ static defaultProps: Partial<IRadialTreeProps> = {
135
+ angleOffset: 0,
136
+ duration: 300,
137
+ centralMargin: 50,
138
+ labelMargin: 2,
139
+ iconColor: '#fff',
140
+ capSize: 8,
141
+ iconSize: 8,
142
+ textSize: 14,
143
+ defaultActiveKey: null,
144
+ };
145
+
146
+ Element!: React.FC<{ render: string }>;
147
+
148
+ constructor(props: any) {
149
+ super(props);
150
+ this.handleRadianClick = this.handleRadianClick.bind(this);
151
+ }
152
+
153
+ uncontrolledProps() {
154
+ return {
155
+ activeKey: null,
156
+ };
157
+ }
158
+
159
+ runAppearAnimation() {
160
+ const { duration, uid } = this.asProps;
161
+ const preferReduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)')?.matches;
162
+
163
+ /** using `!(>)` instead of `<=` to get true on NaN and non numbers stuff */
164
+ if (!(duration > 0)) return;
165
+ if (preferReduceMotion) return;
166
+
167
+ const circlesAnimation = transition()
168
+ .selection()
169
+ .selectAll(`[data-radial-animation=${uid}-cap-circle]`);
170
+ const iconsAnimation = transition()
171
+ .selection()
172
+ .selectAll(`[data-radial-animation=${uid}-cap-icon]`);
173
+ const linesAnimation = transition()
174
+ .selection()
175
+ .selectAll(`[data-radial-animation=${uid}-line]`);
176
+ const labelsAnimation = transition()
177
+ .selection()
178
+ .selectAll(`[data-radial-animation=${uid}-label]`);
179
+ const circlesNodes = circlesAnimation.nodes() as SVGCircleElement[];
180
+ const iconsNodes = iconsAnimation.nodes() as SVGRectElement[];
181
+ const linesNodes = linesAnimation.nodes() as SVGLineElement[];
182
+ const labelsNodes = linesAnimation.nodes() as SVGTextElement[];
183
+
184
+ if (circlesNodes.length > 0) {
185
+ const attrs = circlesNodes.map((node) => {
186
+ const cx = node.cx?.baseVal?.value;
187
+ const cy = node.cy?.baseVal?.value;
188
+ const radianIndex = parseInt(node.dataset.radianIndex!, 10);
189
+ const lineNode = linesNodes[radianIndex];
190
+ return {
191
+ from: {
192
+ cx: lineNode.x1?.baseVal?.value,
193
+ cy: lineNode.y1?.baseVal?.value,
194
+ },
195
+ to: { cx, cy },
196
+ };
197
+ });
198
+
199
+ circlesAnimation
200
+ .attr('opacity', 0)
201
+ .attr('cx', (_, index) => attrs[index].from?.cx)
202
+ .attr('cy', (_, index) => attrs[index].from?.cy);
203
+ circlesAnimation
204
+ .transition()
205
+ .duration(duration)
206
+ .attr('opacity', 1)
207
+ .attr('cx', (_, index) => attrs[index].to?.cx)
208
+ .attr('cy', (_, index) => attrs[index].to?.cy);
209
+ }
210
+ if (iconsNodes.length > 0) {
211
+ const attrs = iconsNodes.map((node) => {
212
+ const x = node.x?.baseVal?.value;
213
+ const y = node.y?.baseVal?.value;
214
+ const width = node.width?.baseVal?.value;
215
+ const height = node.height?.baseVal?.value;
216
+ const radianIndex = parseInt(node.dataset.radianIndex!, 10);
217
+ const lineNode = linesNodes[radianIndex];
218
+ return {
219
+ from: {
220
+ x: lineNode.x1?.baseVal?.value - width / 2,
221
+ y: lineNode.y1?.baseVal?.value - height / 2,
222
+ },
223
+ to: {
224
+ x,
225
+ y,
226
+ },
227
+ };
228
+ });
229
+
230
+ iconsAnimation
231
+ .attr('opacity', 0)
232
+ .attr('x', (_, index) => attrs[index].from?.x)
233
+ .attr('y', (_, index) => attrs[index].from?.y);
234
+ iconsAnimation
235
+ .transition()
236
+ .duration(duration)
237
+ .attr('opacity', 1)
238
+ .attr('x', (_, index) => attrs[index].to?.x)
239
+ .attr('y', (_, index) => attrs[index].to?.y);
240
+ }
241
+ if (linesNodes.length > 0) {
242
+ const attrs = linesNodes.map((node) => {
243
+ const x2 = node.x2?.baseVal?.value;
244
+ const y2 = node.y2?.baseVal?.value;
245
+ return {
246
+ from: {
247
+ x2: node.x1?.baseVal?.value,
248
+ y2: node.y1?.baseVal?.value,
249
+ },
250
+ to: {
251
+ x2,
252
+ y2,
253
+ },
254
+ };
255
+ });
256
+
257
+ linesAnimation
258
+ .attr('opacity', 0)
259
+ .attr('x2', (_, index) => attrs[index].from?.x2)
260
+ .attr('y2', (_, index) => attrs[index].from?.y2);
261
+ linesAnimation
262
+ .transition()
263
+ .duration(duration)
264
+ .attr('opacity', 1)
265
+ .attr('x2', (_, index) => attrs[index].to?.x2)
266
+ .attr('y2', (_, index) => attrs[index].to?.y2);
267
+ }
268
+ if (labelsNodes.length > 0) {
269
+ labelsAnimation.attr('opacity', 0);
270
+ labelsAnimation.transition().duration(duration).attr('opacity', 1);
271
+ }
272
+ }
273
+
274
+ componentDidUpdate(prevProps: RootAsProps) {
275
+ if (prevProps.$rootProps.data?.length !== this.asProps.data?.length) {
276
+ this.runAppearAnimation();
277
+ }
278
+ }
279
+
280
+ componentDidMount() {
281
+ this.runAppearAnimation();
282
+ }
283
+
284
+ handleRadianClick(key: string) {
285
+ return (event: React.MouseEvent) => {
286
+ const newKey = key !== this.asProps.activeKey ? key : null;
287
+ this.handlers.activeKey(newKey, event);
288
+ };
289
+ }
290
+
291
+ computeTextWidth() {
292
+ const { data, textSize } = this.asProps;
293
+ const widths = data.map(({ label }) => measureText(label, textSize));
294
+
295
+ return Math.max(...widths);
296
+ }
297
+
298
+ getTitleProps() {
299
+ const { uid, size, textSize } = this.asProps;
300
+
301
+ const [width, height] = size;
302
+ const center = [width / 2, height / 2];
303
+ const [x, y] = center;
304
+ return {
305
+ x,
306
+ y,
307
+ textSize: textSize * 1.5,
308
+ ['data-radial-animation']: `${uid}-label`,
309
+ } as IRadialTreeTitleProps;
310
+ }
311
+
312
+ getRadianProps() {
313
+ const { data, ...restRootProps } = this.asProps;
314
+ const textWidth = this.computeTextWidth();
315
+
316
+ return {
317
+ ...restRootProps,
318
+ radiansCount: data.length,
319
+ data,
320
+ textWidth,
321
+ onRadianClick: this.handleRadianClick,
322
+ } as IRadialTreeRadianProps;
323
+ }
324
+
325
+ render() {
326
+ const SRadialTree = this.Element;
327
+ const { Children } = this.asProps;
328
+
329
+ return sstyled(this.asProps.styles)(
330
+ <SRadialTree render="g">
331
+ <Children />
332
+ </SRadialTree>,
333
+ );
334
+ }
335
+ }
336
+
337
+ export interface IRadialTreeRadianProps extends IRadialTreeProps {
338
+ radiansCount?: number;
339
+ textWidth?: number;
340
+ onRadianClick?: (key: string) => (event: React.MouseEvent) => void;
341
+ }
342
+
343
+ type RadianAsProps = RootAsProps & {
344
+ radiansCount: number;
345
+ textWidth: number;
346
+ onRadianClick: (key: string) => (event: React.MouseEvent) => void;
347
+ };
348
+
349
+ class RadialTreeRadian extends Component<RadianAsProps> {
350
+ static displayName = 'RadialTreeRadian';
351
+ static style = style;
352
+
353
+ static defaultProps: Partial<IRadialTreeRadianProps> = {
354
+ centralMargin: 50,
355
+ labelMargin: 2,
356
+ iconColor: '#fff',
357
+ capSize: 16,
358
+ iconSize: 16,
359
+ textSize: 14,
360
+ };
361
+ Element!: React.FC<{ render: string }>;
362
+
363
+ constructor(props: any) {
364
+ super(props);
365
+ this.renderRadian = this.renderRadian.bind(this);
366
+ }
367
+
368
+ getInteractiveAreaProps({ $rootProps }: { $rootProps: IRadialTreeProps }, index: number) {
369
+ const data = $rootProps.data![index];
370
+ const { xStart, yStart, xLabelCenter, yLabelCenter, capSize } = this.computeRadianPosition(
371
+ data,
372
+ index,
373
+ );
374
+
375
+ return {
376
+ x1: xStart,
377
+ y1: yStart,
378
+ x2: xLabelCenter,
379
+ y2: yLabelCenter,
380
+ strokeWidth: capSize * 3,
381
+ } as IRadialTreeRadianInteractiveAreaProps;
382
+ }
383
+ getLineProps({ $rootProps }: { $rootProps: IRadialTreeProps }, index: number) {
384
+ const data = $rootProps.data![index];
385
+ const { xStart, yStart, xEnd, yEnd } = this.computeRadianPosition(data, index);
386
+ const { uid } = this.asProps;
387
+ const color = data.color ?? this.asProps.color;
388
+
389
+ return {
390
+ x1: xStart,
391
+ y1: yStart,
392
+ x2: xEnd,
393
+ y2: yEnd,
394
+ stroke: color,
395
+ ['data-radial-animation']: `${uid}-line`,
396
+ ['data-radian-index']: index,
397
+ } as IRadialTreeRadianLineProps;
398
+ }
399
+ getCapProps({ $rootProps }: { $rootProps: IRadialTreeProps }, index: number) {
400
+ const data = $rootProps.data![index];
401
+ const { xEnd, yEnd, capSize } = this.computeRadianPosition(data, index);
402
+ const { uid } = this.asProps;
403
+ const color = data.color ?? this.asProps.color;
404
+
405
+ return {
406
+ x: xEnd,
407
+ y: yEnd,
408
+ radius: capSize,
409
+ color,
410
+ ['data-radial-animation']: `${uid}-cap-circle`,
411
+ ['data-radian-index']: index,
412
+ } as IRadialTreeRadianCapProps;
413
+ }
414
+ getIconProps({ $rootProps }: { $rootProps: IRadialTreeProps }, index: number) {
415
+ const data = $rootProps.data![index];
416
+ const { xEnd, yEnd, isActive } = this.computeRadianPosition(data, index);
417
+ const { uid } = this.asProps;
418
+ const iconColor = data.iconColor ?? this.asProps.iconColor;
419
+ const iconSize = data.iconSize ?? this.asProps.iconSize;
420
+ const icon = data.icon ?? this.asProps.icon;
421
+
422
+ return {
423
+ x: xEnd - iconSize! / 2,
424
+ y: yEnd - iconSize! / 2,
425
+ iconSize,
426
+ color: iconColor ?? '#fff',
427
+ ['data-radial-animation']: `${uid}-cap-icon`,
428
+ ['data-radian-index']: index,
429
+ icon,
430
+ isActive,
431
+ } as IRadialTreeRadianIconProps;
432
+ }
433
+ getLabelProps({ $rootProps }: { $rootProps: IRadialTreeProps }, index: number) {
434
+ const data = $rootProps.data![index];
435
+ const { xLabelCenter, yLabelCenter, labelAngle, isHorizontal } = this.computeRadianPosition(
436
+ data,
437
+ index,
438
+ );
439
+ const { uid, textSize } = this.asProps;
440
+ const { label } = data;
441
+ const color = data.color ?? this.asProps.color;
442
+
443
+ return {
444
+ x: xLabelCenter,
445
+ y: yLabelCenter,
446
+ angle: labelAngle,
447
+ ['data-radial-animation']: `${uid}-label`,
448
+ ['data-radian-index']: index,
449
+ label,
450
+ color,
451
+ isHorizontal,
452
+ textSize,
453
+ } as IRadialTreeRadianLabelProps;
454
+ }
455
+
456
+ getRadianKey(data: RadianData, index: number) {
457
+ return data.key ?? `radian-${index}`;
458
+ }
459
+
460
+ computeRadianPosition(data: RadianData, index: number) {
461
+ const { centralMargin, angleOffset, activeKey, size, radiansCount, textWidth } = this.asProps;
462
+ const [width, height] = size;
463
+ const key = this.getRadianKey(data, index);
464
+ const isActive = activeKey === key;
465
+ const baseCapSize = data.capSize ?? this.asProps.capSize;
466
+ const capSize = baseCapSize * (isActive ? 1 : 0.5);
467
+
468
+ const minDemSize = Math.min(width, height) / 2;
469
+ const length = Math.max(minDemSize - textWidth - baseCapSize - centralMargin, 10);
470
+
471
+ const angle = baseAngle + angleOffset + (index / radiansCount) * (Math.PI * 2);
472
+ const isHorizontal =
473
+ (angle - baseAngle > (1 / 6) * Math.PI && angle - baseAngle < (5 / 6) * Math.PI) ||
474
+ (angle - baseAngle > (7 / 6) * Math.PI && angle - baseAngle < (11 / 6) * Math.PI);
475
+ const topAngle = -Math.PI / 2;
476
+ const labelAngle = ((angle - topAngle) % Math.PI) + topAngle;
477
+
478
+ const center = [width / 2, height / 2];
479
+ const [xCenter, yCenter] = center;
480
+ const start = [
481
+ xCenter + Math.cos(angle) * centralMargin,
482
+ yCenter + Math.sin(angle) * centralMargin,
483
+ ];
484
+ const end = [
485
+ xCenter + Math.cos(angle) * (centralMargin + length),
486
+ yCenter + Math.sin(angle) * (centralMargin + length),
487
+ ];
488
+ const [xStart, yStart] = start;
489
+ const [xEnd, yEnd] = end;
490
+
491
+ const labelCenter = [
492
+ xCenter + Math.cos(angle) * (centralMargin + length + baseCapSize + textWidth / 2),
493
+ yCenter + Math.sin(angle) * (centralMargin + length + baseCapSize + textWidth / 2),
494
+ ];
495
+ const [xLabelCenter, yLabelCenter] = labelCenter;
496
+
497
+ return {
498
+ xStart,
499
+ yStart,
500
+ xEnd,
501
+ yEnd,
502
+ xLabelCenter,
503
+ yLabelCenter,
504
+ labelAngle,
505
+ isHorizontal,
506
+ capSize,
507
+ isActive,
508
+ };
509
+ }
510
+
511
+ renderRadian(data: RadianData, index: number) {
512
+ const { styles, Children, onRadianClick } = this.asProps;
513
+ const key = this.getRadianKey(data, index);
514
+ const SRadian = 'g';
515
+
516
+ let children = getOriginChildren(Children);
517
+
518
+ if (typeof children === 'function') {
519
+ const _child = this.asProps.children;
520
+ const mergedProps = assignProps(children(this.asProps), this.asProps);
521
+ children = mergedProps.children;
522
+ mergedProps.children = _child;
523
+ }
524
+
525
+ // hidden from publicly typed export
526
+ const InteractiveArea = (RadialTree.Radian as any).InteractiveArea;
527
+
528
+ return sstyled(styles)(
529
+ <SRadian key={key} onClick={onRadianClick(key)}>
530
+ <InteractiveArea />
531
+ {children}
532
+ </SRadian>,
533
+ );
534
+ }
535
+
536
+ render() {
537
+ const { data } = this.asProps;
538
+ const SRadianList = 'g';
539
+
540
+ return sstyled(this.asProps.styles)(
541
+ <SRadianList>{data.map((data, index) => this.renderRadian(data, index))}</SRadianList>,
542
+ );
543
+ }
544
+ }
545
+
546
+ export interface IRadialTreeRadianInteractiveAreaProps {
547
+ x1?: number;
548
+ y1?: number;
549
+ x2?: number;
550
+ y2?: number;
551
+ strokeWidth?: number;
552
+ }
553
+ type RadialTreeRadianInteractiveAreaAsProps = IRadialTreeRadianInteractiveAreaProps & {
554
+ Element: React.FC<{ render: string } & React.SVGProps<any>>;
555
+ styles: React.CSSProperties;
556
+ };
557
+ const InteractiveArea: React.FC<RadialTreeRadianInteractiveAreaAsProps> = ({
558
+ Element: SInteractiveArea,
559
+ styles,
560
+ }) => {
561
+ return sstyled(styles)(
562
+ <SInteractiveArea stroke="transparent" render="line" />,
563
+ ) as React.ReactElement;
564
+ };
565
+
566
+ export interface IRadialTreeRadianLineProps {
567
+ x1?: number;
568
+ y1?: number;
569
+ x2?: number;
570
+ y2?: number;
571
+ stroke?: string;
572
+ ['data-radial-animation']?: `${string}-line`;
573
+ ['data-radian-index']?: number;
574
+ }
575
+ type RadialTreeRadianLineAsProps = IRadialTreeRadianLineProps & {
576
+ Element: React.FC<{ render: string } & React.SVGProps<any>>;
577
+ styles: React.CSSProperties;
578
+ };
579
+ const Line: React.FC<RadialTreeRadianLineAsProps> = ({ Element: SLine, styles, stroke }) => {
580
+ return sstyled(styles)(<SLine render="line" stroke={stroke} />) as React.ReactElement;
581
+ };
582
+
583
+ export interface IRadialTreeRadianCapProps {
584
+ x?: number;
585
+ y?: number;
586
+ radius?: number;
587
+ color?: string;
588
+ ['data-radial-animation']?: `${string}-cap-circle`;
589
+ ['data-radian-index']?: number;
590
+ }
591
+ type RadialTreeRadianCapAsProps = IRadialTreeRadianCapProps & {
592
+ Element: React.FC<{ render: string } & React.SVGProps<any>>;
593
+ styles: React.CSSProperties;
594
+ };
595
+ const Cap: React.FC<RadialTreeRadianCapAsProps> = ({
596
+ Element: SCap,
597
+ styles,
598
+ x,
599
+ y,
600
+ radius,
601
+ color,
602
+ }) => {
603
+ return sstyled(styles)(
604
+ <SCap render="circle" cx={x} cy={y} r={radius} fill={color} />,
605
+ ) as React.ReactElement;
606
+ };
607
+
608
+ export interface IRadialTreeRadianIconProps {
609
+ x?: number;
610
+ y?: number;
611
+ iconSize?: number;
612
+ color?: string;
613
+ ['data-radial-animation']?: `${string}-cap-icon`;
614
+ ['data-radian-index']?: number;
615
+ tag?: React.FC;
616
+ isActive?: boolean;
617
+ }
618
+ type RadialTreeRadianIconAsProps = IRadialTreeRadianIconProps & {
619
+ Element: React.FC<{ render: string | React.FC } & React.SVGProps<any>>;
620
+ x: number;
621
+ y: number;
622
+ iconSize: number;
623
+ styles: React.CSSProperties;
624
+ };
625
+ const Icon: React.FC<RadialTreeRadianIconAsProps> = ({
626
+ Element: SIcon,
627
+ styles,
628
+ isActive,
629
+ tag,
630
+ x,
631
+ y,
632
+ iconSize,
633
+ }) => {
634
+ if (!(isActive && tag)) return null;
635
+ const width = iconSize;
636
+ const height = iconSize;
637
+ return sstyled(styles)(
638
+ <SIcon x={x} y={y} width={width} height={height} render={tag} />,
639
+ ) as React.ReactElement;
640
+ };
641
+
642
+ export interface IRadialTreeRadianLabelProps {
643
+ x?: number;
644
+ y?: number;
645
+ color?: string;
646
+ textSize?: number;
647
+ ['data-radial-animation']?: `${string}-label`;
648
+ ['data-radian-index']?: number;
649
+ label?: string;
650
+ isHorizontal?: boolean;
651
+ angle?: number;
652
+ }
653
+ type RadialTreeRadianLabelAsProps = IRadialTreeRadianLabelProps & {
654
+ Element: React.FC<{ render: string } & React.SVGProps<any>>;
655
+ Children: React.FC;
656
+ styles: React.CSSProperties;
657
+ x: number;
658
+ y: number;
659
+ angle: number;
660
+ textSize: number;
661
+ };
662
+ const Label: React.FC<RadialTreeRadianLabelAsProps> = ({
663
+ Element: SLabel,
664
+ Children,
665
+ styles,
666
+ label,
667
+ color,
668
+ isHorizontal,
669
+ x,
670
+ y,
671
+ textSize,
672
+ angle,
673
+ }) => {
674
+ const lines = String(label).split('\n');
675
+ const linesCount = lines.length;
676
+ const SLabelLine = 'tspan';
677
+
678
+ const sstyles = sstyled(styles);
679
+ const sLabelStyles = sstyles.cn('SLabel', {
680
+ color,
681
+ 'color-hovered': shade(color, -0.12),
682
+ 'text-cursor': isHorizontal ? 'text' : 'vertical-text',
683
+ 'transform-origin': `${x.toFixed(2)}px ${y.toFixed(2)}px`,
684
+ });
685
+
686
+ return (
687
+ <SLabel
688
+ render="text"
689
+ textAnchor="middle"
690
+ dominantBaseline="central"
691
+ className={sLabelStyles.className}
692
+ style={sLabelStyles.style}
693
+ x={x.toFixed(2)}
694
+ y={y.toFixed(2)}
695
+ transform={`rotate(${((angle / Math.PI) * 180).toFixed(2)})`}
696
+ >
697
+ {lines.map((lineText, lineIndex) => (
698
+ <SLabelLine
699
+ x={x}
700
+ y={y + (lineIndex - (linesCount - 1) / 2) * textSize}
701
+ key={`#${lineIndex}-${lineText}`}
702
+ >
703
+ {lineText}
704
+ </SLabelLine>
705
+ ))}
706
+ <Children />
707
+ </SLabel>
708
+ ) as React.ReactElement;
709
+ };
710
+
711
+ const Radian = createElement(RadialTreeRadian, {
712
+ InteractiveArea,
713
+ Line,
714
+ Cap,
715
+ Icon,
716
+ Label,
717
+ });
718
+
719
+ export interface IRadialTreeTitleProps {
720
+ x?: number;
721
+ y?: number;
722
+ textSize?: number;
723
+ ['data-radial-animation']?: `${string}-label`;
724
+ color?: string;
725
+ }
726
+ type RadialTreeTitleAsProps = IRadialTreeTitleProps & {
727
+ Element: React.FC<{ render: string } & React.SVGProps<any>>;
728
+ Children: React.FC;
729
+ styles: React.CSSProperties;
730
+ };
731
+ const Title: React.FC<RadialTreeTitleAsProps> = ({
732
+ Element: STitle,
733
+ Children,
734
+ styles,
735
+ textSize,
736
+ color,
737
+ x,
738
+ y,
739
+ }) => {
740
+ return sstyled(styles)(
741
+ <STitle
742
+ render="text"
743
+ textAnchor="middle"
744
+ dominantBaseline="central"
745
+ fontSize={textSize}
746
+ fill={color}
747
+ x={x}
748
+ y={y}
749
+ >
750
+ <Children />
751
+ </STitle>,
752
+ ) as React.ReactElement;
753
+ };
754
+
755
+ const RadialTree = createElement(RadialTreeBase, { Title, Radian }) as (<T>(
756
+ props: CProps<IRadialTreeProps & T>,
757
+ ) => ReturnEl) & {
758
+ Title: <T>(props: CProps<IRadialTreeTitleProps & T>) => ReturnEl;
759
+ Radian: (<T>(props: CProps<IRadialTreeRadianProps & T>) => ReturnEl) & {
760
+ Line: <T>(props: CProps<IRadialTreeRadianLineProps & T>) => ReturnEl;
761
+ Cap: <T>(props: CProps<IRadialTreeRadianCapProps & T>) => ReturnEl;
762
+ Icon: <T>(props: CProps<IRadialTreeRadianIconProps & T>) => ReturnEl;
763
+ Label: <T>(props: CProps<IRadialTreeRadianLabelProps & T>) => ReturnEl;
764
+ };
765
+ };
766
+
767
+ export default RadialTree;