@easyv/charts 1.2.7 → 1.2.8

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 (92) hide show
  1. package/.babelrc +8 -8
  2. package/.husky/commit-msg +3 -3
  3. package/CHANGELOG.md +18 -18
  4. package/lib/components/AnimateData.js +11 -7
  5. package/lib/components/Axis.js +18 -14
  6. package/lib/components/Background.js +2 -2
  7. package/lib/components/Band.js +8 -4
  8. package/lib/components/BaseLine.js +23 -12
  9. package/lib/components/Brush.js +2 -2
  10. package/lib/components/Carousel.js +15 -8
  11. package/lib/components/CartesianChart.js +14 -7
  12. package/lib/components/Chart.js +9 -6
  13. package/lib/components/ChartContainer.js +8 -4
  14. package/lib/components/ConicalGradient.js +27 -23
  15. package/lib/components/ExtentData.js +7 -4
  16. package/lib/components/FilterData.js +7 -4
  17. package/lib/components/Indicator.js +2 -2
  18. package/lib/components/Label.js +2 -2
  19. package/lib/components/Legend.js +11 -5
  20. package/lib/components/Lighter.js +179 -179
  21. package/lib/components/Line.js +49 -37
  22. package/lib/components/LinearGradient.js +2 -2
  23. package/lib/components/Mapping.js +8 -5
  24. package/lib/components/Marquee.js +5 -5
  25. package/lib/components/PieChart.js +17 -8
  26. package/lib/components/StackData.js +7 -4
  27. package/lib/components/StereoBar.js +8 -4
  28. package/lib/components/TextOverflow.js +5 -5
  29. package/lib/components/Tooltip.js +2 -2
  30. package/lib/components/index.js +49 -49
  31. package/lib/css/index.module.css +41 -41
  32. package/lib/css/piechart.module.css +26 -26
  33. package/lib/element/ConicGradient.js +72 -72
  34. package/lib/formatter/legend.js +2 -2
  35. package/lib/hooks/index.js +14 -14
  36. package/lib/hooks/useAnimateData.js +7 -7
  37. package/lib/hooks/useAxes.js +7 -7
  38. package/lib/hooks/useCarouselAxisX.js +7 -7
  39. package/lib/hooks/useExtentData.js +9 -8
  40. package/lib/hooks/useFilterData.js +8 -8
  41. package/lib/hooks/useStackData.js +10 -8
  42. package/lib/hooks/useTooltip.js +10 -10
  43. package/lib/index.js +6 -2
  44. package/lib/utils/index.js +8 -5
  45. package/package.json +53 -53
  46. package/src/components/AnimateData.tsx +24 -24
  47. package/src/components/Axis.tsx +354 -354
  48. package/src/components/Background.tsx +45 -45
  49. package/src/components/Band.tsx +173 -173
  50. package/src/components/BaseLine.js +83 -76
  51. package/src/components/Brush.js +159 -159
  52. package/src/components/Carousel.tsx +144 -144
  53. package/src/components/CartesianChart.js +3 -0
  54. package/src/components/Chart.js +99 -99
  55. package/src/components/ChartContainer.tsx +63 -63
  56. package/src/components/ConicalGradient.js +258 -258
  57. package/src/components/ExtentData.js +17 -17
  58. package/src/components/FilterData.js +23 -23
  59. package/src/components/Indicator.js +13 -13
  60. package/src/components/Label.js +206 -206
  61. package/src/components/Legend.js +158 -158
  62. package/src/components/Lighter.jsx +173 -173
  63. package/src/components/Line.js +145 -144
  64. package/src/components/LinearGradient.js +29 -29
  65. package/src/components/Mapping.js +71 -71
  66. package/src/components/Marquee.js +93 -93
  67. package/src/components/PieChart.js +1277 -1287
  68. package/src/components/StackData.js +20 -20
  69. package/src/components/StereoBar.tsx +310 -310
  70. package/src/components/TextOverflow.js +51 -51
  71. package/src/components/Tooltip.js +169 -169
  72. package/src/components/index.js +55 -55
  73. package/src/context/index.js +2 -2
  74. package/src/css/index.module.css +41 -41
  75. package/src/css/piechart.module.css +26 -26
  76. package/src/element/ConicGradient.jsx +55 -55
  77. package/src/element/Line.tsx +33 -33
  78. package/src/element/index.ts +3 -3
  79. package/src/formatter/index.js +1 -1
  80. package/src/formatter/legend.js +90 -90
  81. package/src/hooks/index.js +17 -17
  82. package/src/hooks/useAnimateData.ts +67 -67
  83. package/src/hooks/useAxes.js +144 -144
  84. package/src/hooks/useCarouselAxisX.js +163 -163
  85. package/src/hooks/useExtentData.js +88 -88
  86. package/src/hooks/useFilterData.js +72 -72
  87. package/src/hooks/useStackData.js +100 -100
  88. package/src/hooks/useTooltip.ts +96 -96
  89. package/src/index.js +6 -6
  90. package/src/types/index.d.ts +67 -67
  91. package/src/utils/index.js +714 -714
  92. package/tsconfig.json +22 -22
@@ -1,1287 +1,1277 @@
1
- /**
2
- * 饼环图
3
- */
4
- import React, {
5
- memo,
6
- useMemo,
7
- useCallback,
8
- useRef,
9
- useState,
10
- useContext,
11
- useLayoutEffect,
12
- Fragment,
13
- } from 'react';
14
- import { ChartContainer, Carousel, Legend, ConicalGradient, Mapping } from '.';
15
- import { chartContext } from '../context';
16
- import {
17
- getFontStyle,
18
- sortPie,
19
- getDataWithPercent,
20
- getColorList,
21
- } from '../utils';
22
- import { pie, arc, extent, scaleLinear } from 'd3v7';
23
- import { animate, linear } from 'popmotion';
24
- import LinearGradient from './LinearGradient';
25
- import { pieLegendFormatter as legendFormatter } from '../formatter';
26
- import ringCss from '../css/piechart.module.css';
27
-
28
- const PI = Math.PI;
29
-
30
- const defaultChart = {
31
- outerRadius: 1,
32
- innerRadius: 0,
33
- cornerRadius: 0,
34
- rose: false,
35
- roseType: 'radius',
36
- baseRadius: 0,
37
- padAngle: 0,
38
- };
39
- const defaultAngle = { startAngle: 0, endAngle: 360, antiClockwise: false };
40
-
41
- const nameDy = (showValue, showPercent, mode, dir) => {
42
- if (showValue || showPercent) {
43
- if (mode == 'vertical') {
44
- return dir == 1 ? '1.1em' : '-2.6em';
45
- } else {
46
- return 0;
47
- }
48
- } else {
49
- if (mode == 'vertical') {
50
- return dir * 1.1 + 'em';
51
- } else {
52
- return 0;
53
- }
54
- }
55
- };
56
- const valueDy = (value1, mode, dir) => {
57
- if (value1) {
58
- if (mode == 'vertical') {
59
- return '1.5em';
60
- } else {
61
- return 0;
62
- }
63
- } else {
64
- if (mode == 'vertical') {
65
- return dir == 1 ? '1.1em' : '-1.1em';
66
- } else {
67
- return 0;
68
- }
69
- }
70
- };
71
-
72
- const percentDy_ = (showName, showValue, mode, dir) => {
73
- if (showValue) {
74
- return 0;
75
- }
76
- if (showName) {
77
- if (mode == 'vertical') {
78
- return '1.5em';
79
- } else {
80
- return 0;
81
- }
82
- } else {
83
- if (mode == 'vertical') {
84
- return dir * 1.1 + 'em';
85
- } else {
86
- return 0;
87
- }
88
- }
89
- };
90
-
91
- const percentX = (showName, showValue, mode, x) => {
92
- if (showValue) {
93
- return '';
94
- }
95
- if (showName) {
96
- if (mode == 'vertical') {
97
- return x;
98
- } else {
99
- return '';
100
- }
101
- } else {
102
- return x;
103
- }
104
- };
105
-
106
- const percentDx = (showName, showValue, mode) => {
107
- if (showValue) {
108
- return '0.5em';
109
- }
110
- if (showName) {
111
- if (mode == 'vertical') {
112
- return 0;
113
- } else {
114
- return '0.5em';
115
- }
116
- } else {
117
- return 0;
118
- }
119
- };
120
-
121
- const percentDy = (showName, showValue, mode) => {
122
- if (showValue) {
123
- return 0;
124
- }
125
- if (showName) {
126
- if (mode == 'vertical') {
127
- return '1.5em';
128
- } else {
129
- return 0;
130
- }
131
- } else {
132
- return 0;
133
- }
134
- };
135
-
136
- const valueDx = (showName, mode) => {
137
- if (!showName) {
138
- return '';
139
- }
140
- if (mode == 'vertical') {
141
- return '';
142
- } else {
143
- return '0.5em';
144
- }
145
- };
146
-
147
- const getCoord = (deg, radius) => {
148
- var x = Math.cos(deg) * radius,
149
- y = Math.sin(deg) * radius;
150
- return [x, y];
151
- };
152
-
153
- const getRoseRadius = ({ innerRadius, baseRadius }) =>
154
- innerRadius + (1 - innerRadius) * baseRadius;
155
-
156
- const getAngle = ({ startAngle, endAngle, antiClockwise, ...rest }) => {
157
- if (antiClockwise)
158
- return {
159
- ...rest,
160
- startAngle: endAngle - 180,
161
- endAngle: startAngle - 180,
162
- };
163
- return { ...rest, startAngle, endAngle };
164
- };
165
-
166
- const getArc = (
167
- radius,
168
- {
169
- padAngle = 0,
170
- innerRadius = 0,
171
- outerRadius = 1,
172
- cornerRadius = 0,
173
- startAngle = 0,
174
- endAngle = 360,
175
- ...rest
176
- },
177
- series
178
- ) => ({
179
- ...rest,
180
- type: 'pie',
181
- fieldName: series.fieldName,
182
- displayName: series.displayName || rest.data.s,
183
- series: series,
184
- innerRadius: innerRadius * radius,
185
- outerRadius: outerRadius * radius,
186
- arc: arc()
187
- .innerRadius(innerRadius * radius)
188
- .outerRadius(outerRadius * radius)
189
- .cornerRadius(cornerRadius)
190
- .startAngle(startAngle)
191
- .endAngle(endAngle)
192
- .padAngle(padAngle),
193
- });
194
-
195
- const getCircleScale = ({ count, color, width, length } = tick, radius) => {
196
- let data = [],
197
- arcs = [],
198
- centroids = [];
199
- for (let i = 0; i < count; i++) {
200
- data.push(1);
201
- }
202
- let scaleData = pie()(data);
203
- scaleData.map((data) => {
204
- let _arc = arc()
205
- .innerRadius(radius + length / 2)
206
- .outerRadius(radius + length / 2)
207
- .startAngle(data.startAngle)
208
- .endAngle(data.endAngle);
209
- arcs.push(_arc());
210
- centroids.push(_arc.centroid());
211
- });
212
- return (
213
- <g>
214
- {centroids.map((center, index) => {
215
- let x = center[0],
216
- y = center[1];
217
- let rate = length / Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
218
- return (
219
- <path
220
- key={index}
221
- d={`M${x},${y}l${x * rate},${y * rate}`}
222
- strokeWidth={width}
223
- stroke={color}
224
- />
225
- );
226
- })}
227
- </g>
228
- );
229
- };
230
-
231
- const Component = memo(
232
- ({
233
- config: {
234
- chart: {
235
- dimension: {
236
- chartDimension: { width, height },
237
- },
238
- label,
239
- legend: { formatter = legendFormatter, ...legend },
240
- margin: { marginLeft, marginTop },
241
- },
242
- fan: {
243
- chart = defaultChart,
244
- chart: { outerRadius = defaultChart.outerRadius, padAngle },
245
- angle = defaultAngle,
246
- stroke: { show, strokeWidth, color } = { show: false },
247
- decorate,
248
- decorate2,
249
- categoryText,
250
- outerDecorate,
251
- current,
252
- } = {},
253
- order,
254
- series,
255
- animation: {
256
- on,
257
- current: {
258
- heighten = 0,
259
- opacity = 0,
260
- width: radiusWidthAdd = 0,
261
- color: animateColor,
262
- textStyle: animateCTS,
263
- gap = 0,
264
- },
265
- rotate = 0,
266
- },
267
- },
268
- state: { currentIndex, trigger },
269
- onEvent,
270
- data = [],
271
- }) => {
272
- const prevIndex = useRef(null);
273
- const { precision: legendPrecision } = legend.config.percent;
274
- const {
275
- id,
276
- width: chartWidth,
277
- height: chartHeight,
278
- triggerOnRelative,
279
- onEmit,
280
- } = useContext(chartContext);
281
-
282
- const [y, setY] = useState(1);
283
- const radius = (Math.min(chartWidth, chartHeight) / 2) * outerRadius;
284
-
285
- const arcsFunc = useMemo(() => {
286
- const { startAngle = 0, endAngle = 360 } = getAngle(angle);
287
- const arcsFunc = pie()
288
- .startAngle((startAngle * PI) / 180)
289
- .endAngle((endAngle * PI) / 180)
290
- .value((d) => d.y);
291
- return arcsFunc;
292
- }, [angle]);
293
-
294
- const arcs = useMemo(() => {
295
- const _chart = Object.assign(defaultChart, chart);
296
- const {
297
- innerRadius,
298
- outerRadius,
299
- rose,
300
- cornerRadius,
301
- padAngle,
302
- roseType,
303
- } = _chart;
304
- const _padAngle = (padAngle * Math.PI) / 180;
305
-
306
- switch (order) {
307
- case '':
308
- arcsFunc.sort(null);
309
- break;
310
- case 'desc':
311
- arcsFunc.sort((a, b) => b.y - a.y);
312
- break;
313
- case 'asc':
314
- arcsFunc.sort((a, b) => a.y - b.y);
315
- break;
316
- }
317
-
318
- const arcs = arcsFunc(data);
319
- const legendDataWithPercent = getDataWithPercent(arcs, legendPrecision);
320
- const _legendDataWithPercent = sortPie(legendDataWithPercent, order);
321
-
322
- if (rose) {
323
- const domain = extent(_legendDataWithPercent, (d) => d.value);
324
- const roseRadius = getRoseRadius(_chart);
325
- const scaler = scaleLinear().domain(domain).range([roseRadius, 1]);
326
-
327
- const angle = (PI * 2) / _legendDataWithPercent.length;
328
- return _legendDataWithPercent.map(
329
- ({ startAngle, endAngle, ...arc }, index) => ({
330
- ...arc,
331
- id: id + '_linear_' + index,
332
- startAngle: roseType == 'area' ? angle * index : startAngle,
333
- endAngle: roseType == 'area' ? angle * (index + 1) : endAngle,
334
- cornerRadius,
335
- padAngle: _padAngle,
336
- innerRadius,
337
- outerRadius: scaler(arc.value),
338
- })
339
- );
340
- }
341
- return _legendDataWithPercent.map((arc, index) => ({
342
- ...arc,
343
- id: id + '_linear_' + index,
344
- cornerRadius,
345
- padAngle: _padAngle,
346
- innerRadius,
347
- outerRadius,
348
- }));
349
- }, [data, arcsFunc, chart, legendPrecision]);
350
-
351
- const _arcs = useMemo(() => {
352
- const seriesLength = series.size;
353
- if (!seriesLength) return [];
354
- const _series = [...series.values()];
355
- return arcs.map((arc, index) => getArc(radius, arc, _series[index]));
356
- }, [series, arcs, radius]);
357
- const onClick = useCallback(
358
- (e) =>
359
- onEvent({
360
- currentIndex: +e.currentTarget.dataset.index,
361
- type: 'onClick',
362
- }),
363
- [onEvent]
364
- );
365
-
366
- const onMouseEnter = useCallback(
367
- (e) =>
368
- onEvent({
369
- currentIndex: +e.currentTarget.dataset.index,
370
- type: 'onMouseEnter',
371
- }),
372
- [onEvent]
373
- );
374
-
375
- const onMouseLeave = useCallback(
376
- (e) =>
377
- onEvent({
378
- currentIndex: +e.currentTarget.dataset.index,
379
- type: 'onMouseLeave',
380
- }),
381
- [onEvent]
382
- );
383
-
384
- useLayoutEffect(() => {
385
- let animation;
386
- if (!!on) {
387
- animation = animate({
388
- from: 0,
389
- to: 1,
390
- duration: 500,
391
- ease: linear,
392
- onPlay: () => {},
393
- onUpdate: (v) => {
394
- setY(v);
395
- },
396
- onComplete: () => {
397
- const _data = arcs[+currentIndex].data;
398
- triggerOnRelative(_data);
399
- onEmit('carousel', _data);
400
- },
401
- });
402
- } else {
403
- if (currentIndex !== null && trigger === 'onClick') {
404
- const _data = arcs[+currentIndex].data;
405
- triggerOnRelative(_data);
406
- onEmit(trigger, _data);
407
- }
408
- }
409
- return () => {
410
- prevIndex.current = currentIndex;
411
- animation && animation.stop();
412
- animation = null;
413
- };
414
- }, [
415
- JSON.stringify(arcs),
416
- on,
417
- currentIndex,
418
- trigger,
419
- onEmit,
420
- triggerOnRelative,
421
- ]);
422
-
423
- const halfChartWidth = chartWidth / 2;
424
- const halfChartHeight = chartHeight / 2;
425
-
426
- const rotate_ = decorate2
427
- ? (-(arcs[+currentIndex].startAngle + arcs[+currentIndex].endAngle) *
428
- 90) /
429
- Math.PI +
430
- rotate
431
- : 0;
432
- let maxRadius = 0;
433
- _arcs.map((d) => {
434
- maxRadius = Math.max(maxRadius, d.outerRadius);
435
- });
436
- let centerRadius = 0.5 * maxRadius + 0.5 * _arcs[0].innerRadius;
437
- return outerDecorate ? (
438
- <>
439
- <ChartContainer //用于生成甜甜圈图,判断依据是外环装饰这个配置项(outerDecorate)
440
- width={width}
441
- height={height}
442
- marginLeft={marginLeft}
443
- marginTop={marginTop}
444
- >
445
- <g
446
- style={{
447
- transition: 'transform ease-in-out 0.3s',
448
- transform:
449
- 'translate(' +
450
- halfChartWidth +
451
- 'px, ' +
452
- halfChartHeight +
453
- 'px) rotate(' +
454
- rotate_ +
455
- 'deg)',
456
- }}
457
- >
458
- {
459
- //用于生成外环装饰的刻度
460
- outerDecorate.tick.show &&
461
- getCircleScale(outerDecorate.tick, maxRadius)
462
- }
463
- <circle //外环装饰
464
- cx='0'
465
- cy='0'
466
- r={maxRadius + 2}
467
- fill='none'
468
- stroke={outerDecorate.color}
469
- strokeWidth={outerDecorate.width}
470
- />
471
- {_arcs.map(
472
- (
473
- {
474
- id,
475
- value,
476
- series,
477
- arc,
478
- innerRadius,
479
- outerRadius,
480
- },
481
- index
482
- ) => {
483
- const arcWidth = outerRadius - innerRadius;
484
- const path = arc
485
- .innerRadius(centerRadius)
486
- .outerRadius(centerRadius)(value);
487
- const dashLength = Math.ceil(
488
- (Math.PI * centerRadius * 2) / _arcs.length
489
- );
490
- const pie = getColorList(series.color);
491
- return (
492
- <Fragment key={index}>
493
- <path
494
- className={ringCss['inner-arc']}
495
- style={{
496
- strokeDasharray: `${dashLength},${2 * dashLength}`,
497
- strokeDashoffset: dashLength,
498
- animationDelay: `${index * 2000}ms`,
499
- }}
500
- data-index={index}
501
- onClick={onClick}
502
- onMouseEnter={onMouseEnter}
503
- onMouseLeave={onMouseLeave}
504
- d={path.split('L')[0]}
505
- stroke={'url(#' + id + ')'}
506
- strokeWidth={arcWidth}
507
- fill='none'
508
- />
509
- <defs>
510
- <LinearGradient
511
- id={id}
512
- colors={pie}
513
- rotate={series.color.linear.angle + 180}
514
- // gradientUnits='objectBoundingBox'
515
- />
516
- </defs>
517
- </Fragment>
518
- );
519
- }
520
- )}
521
- {label && (
522
- <RingLabel
523
- config={{ ...label, maxRadius: maxRadius + 2 }}
524
- arcs={_arcs}
525
- />
526
- )}
527
- </g>
528
- </ChartContainer>
529
- <Legend {...legend} series={_arcs} formatter={formatter} />
530
- </>
531
- ) : (
532
- <>
533
- <ChartContainer
534
- width={width}
535
- height={height}
536
- marginLeft={marginLeft}
537
- marginTop={marginTop}
538
- >
539
- <g
540
- style={{
541
- transition: 'transform ease-in-out 0.3s',
542
- transform:
543
- 'translate(' +
544
- halfChartWidth +
545
- 'px, ' +
546
- halfChartHeight +
547
- 'px) rotate(' +
548
- rotate_ +
549
- 'deg)',
550
- }}
551
- >
552
- {_arcs.map(
553
- (
554
- {
555
- id,
556
- value,
557
- series,
558
- arc,
559
- innerRadius,
560
- outerRadius,
561
- index: dataIndex,
562
- },
563
- index
564
- ) => {
565
- const current = index == currentIndex;
566
- const prev = index == prevIndex.current;
567
- const offset = current ? y : prev ? 1 - y : 0;
568
-
569
- const fillOpacity = animateColor
570
- ? 1
571
- : current
572
- ? opacity / 100
573
- : 1;
574
- const deltaHeighten = offset * heighten;
575
- const path = arc
576
- .innerRadius(innerRadius + deltaHeighten)
577
- .outerRadius(outerRadius + deltaHeighten)(value);
578
- const pie = getColorList(series.color);
579
- const currentPie = animateColor
580
- ? getColorList(animateColor)
581
- : getColorList(series.color);
582
- let textPath = '',
583
- categoryTextStyle = {};
584
- if (categoryText && categoryText.show) {
585
- //如果有类目文本,则需要计算文字路径
586
- //let offsetWidth=decorate2.radiusWidth/2 + radiusWidthAdd/2; //当前文字需生成在装饰物内,故而半径需要减小
587
- let textArc = arc
588
- .innerRadius(
589
- outerRadius + (current ? gap : categoryText.gap)
590
- )
591
- .outerRadius(
592
- outerRadius + (current ? gap : categoryText.gap)
593
- )(value);
594
- let lastA = textArc.lastIndexOf('A');
595
- textPath = textArc.slice(
596
- 0,
597
- lastA > 0 ? lastA : textArc.length
598
- ); //文字路径
599
- categoryTextStyle = current
600
- ? animateCTS
601
- : categoryText.textStyle; //这里把textstyle拿出来
602
- }
603
-
604
- return (
605
- <Fragment key={index}>
606
- <path
607
- data-index={index}
608
- onClick={onClick}
609
- onMouseEnter={onMouseEnter}
610
- onMouseLeave={onMouseLeave}
611
- d={path}
612
- stroke={show ? color : 'none'}
613
- strokeWidth={show ? strokeWidth : '0'}
614
- fill={'url(#' + id + ')'}
615
- fillOpacity={fillOpacity}
616
- />
617
- {
618
- //装饰物2,产生于每个弧的外部
619
- decorate2 && decorate2.show && (
620
- <path
621
- data-index={index}
622
- onClick={onClick}
623
- onMouseEnter={onMouseEnter}
624
- onMouseLeave={onMouseLeave}
625
- d={arc
626
- .innerRadius(outerRadius)
627
- .outerRadius(
628
- outerRadius +
629
- decorate2.radiusWidth +
630
- (current ? radiusWidthAdd : 0)
631
- )(value)}
632
- stroke={show ? color : 'none'}
633
- strokeWidth={show ? strokeWidth : '0'}
634
- fill={'url(#' + id + ')'}
635
- fillOpacity={decorate2.opacity / 100}
636
- />
637
- )
638
- }
639
- {
640
- //类目文本
641
- categoryText && categoryText.show && (
642
- <g>
643
- <path
644
- onClick={onClick}
645
- onMouseEnter={onMouseEnter}
646
- onMouseLeave={onMouseLeave}
647
- id={id + '_text_' + index}
648
- d={textPath}
649
- fill='none'
650
- stroke='none'
651
- />
652
- <text
653
- textAnchor='middle'
654
- style={{
655
- ...categoryTextStyle,
656
- fontWeight: categoryTextStyle.bold
657
- ? 'bold'
658
- : 'normal',
659
- fontStyle: categoryTextStyle.italic
660
- ? 'italic'
661
- : 'normal',
662
- pointerEvents: 'none',
663
- }}
664
- fill={categoryTextStyle.color}
665
- >
666
- <textPath
667
- startOffset='50%'
668
- href={'#' + id + '_text_' + index}
669
- >
670
- {_arcs[index].displayName ||
671
- _arcs[index].fieldName}
672
- </textPath>
673
- </text>
674
- </g>
675
- )
676
- }
677
- <defs>
678
- <LinearGradient
679
- id={id}
680
- colors={current ? currentPie : pie}
681
- rotate={
682
- current
683
- ? animateColor
684
- ? animateColor.linear.angle + 180
685
- : series.color.linear.angle + 180
686
- : series.color.linear.angle + 180
687
- }
688
- // gradientUnits='objectBoundingBox'
689
- />
690
- </defs>
691
- </Fragment>
692
- );
693
- }
694
- )}
695
- {label && <Label config={label} arcs={_arcs} />}
696
- {current && (
697
- <g
698
- fillOpacity={y}
699
- style={{ transform: 'rotate(' + -rotate_ + 'deg)' }}
700
- >
701
- <Current
702
- config={current}
703
- width={width}
704
- height={height}
705
- data={_arcs}
706
- currentIndex={+currentIndex}
707
- />
708
- </g>
709
- )}
710
- </g>
711
- </ChartContainer>
712
- {decorate && (
713
- <ConicalGradient
714
- width={width}
715
- height={height}
716
- centerX={halfChartWidth + marginLeft}
717
- centerY={halfChartHeight + marginTop}
718
- config={decorate}
719
- arcs={_arcs}
720
- radius={radius}
721
- />
722
- )}
723
- <Legend {...legend} series={_arcs} formatter={formatter} />
724
- </>
725
- );
726
- }
727
- );
728
-
729
- const Current = ({
730
- config: {
731
- show,
732
- gap,
733
- name: { show: showName, sameColor: nameColor, font: nameFont, textBreak },
734
- percent: {
735
- show: showPercent,
736
- sameColor: percentColor,
737
- font: percentFont,
738
- precision,
739
- translate: { x: translatePercentX, y: translatePercentY },
740
- },
741
- value: {
742
- show: showValue,
743
- sameColor: valueColor,
744
- font: valueFont,
745
- translate: { x: translateValueX, y: translateValueY },
746
- suffix: {
747
- show: showSuffix,
748
- fontSize,
749
- text,
750
- translate: { x: translateSuffixX, y: translateSuffixY },
751
- },
752
- },
753
- },
754
- width,
755
- height,
756
- data,
757
- currentIndex,
758
- }) => {
759
- const _data = useMemo(() => {
760
- const legendDataWithPercent = getDataWithPercent(data, precision);
761
- return sortPie(legendDataWithPercent, '');
762
- }, [data, precision]);
763
- const currentData = _data[currentIndex];
764
-
765
- if (!currentData) return null;
766
-
767
- const { displayName, fieldName, value, percent } = currentData;
768
- let nameTemp = displayName ? displayName : fieldName; //类目名
769
- let nameList = [];
770
- if (textBreak) {
771
- //如果限制了首行字符,则切割组件
772
- while (nameTemp.length > textBreak) {
773
- nameList.push(nameTemp.slice(0, textBreak));
774
- nameTemp = nameTemp.slice(textBreak);
775
- }
776
- }
777
- nameList.push(nameTemp);
778
- let foreignStyle = {
779
- //foreignObject标签样式,
780
- width,
781
- height,
782
- transform: `translate(-${width / 2}px,-${height / 2}px)`,
783
- pointerEvents: 'none',
784
- },
785
- boxStyle = {
786
- //弹性盒子样式,用于当前值的上下居中对齐等
787
- width,
788
- height,
789
- display: 'flex',
790
- flexDirection: 'column',
791
- justifyContent: 'center',
792
- alignItems: 'center',
793
- };
794
- let seriesColor = currentData.series.color;
795
- seriesColor =
796
- seriesColor.type == 'pure'
797
- ? seriesColor.pure
798
- : seriesColor.linear.stops[0].color;
799
- return (
800
- show && (
801
- <foreignObject style={foreignStyle}>
802
- <div style={boxStyle}>
803
- {
804
- //类目名称
805
- showName && (
806
- <div
807
- style={{
808
- ...getFontStyle(nameFont),
809
- margin: gap / 2 + 'px 0',
810
- display: 'flex',
811
- flexDirection: 'column',
812
- alignItems: 'center',
813
- color: nameColor ? seriesColor : nameFont.color,
814
- }}
815
- >
816
- {nameList.map((d, i) => {
817
- return <span key={i}>{d}</span>;
818
- })}
819
- </div>
820
- )
821
- }
822
- {
823
- //真实值
824
- showValue && (
825
- <span
826
- style={{
827
- ...getFontStyle(valueFont),
828
- transform:
829
- 'translate(' +
830
- translateValueX +
831
- 'px,' +
832
- translateValueY +
833
- 'px)',
834
- margin: gap / 2 + 'px 0',
835
- color: valueColor ? seriesColor : valueFont.color,
836
- }}
837
- >
838
- {value}
839
- {showSuffix && text && (
840
- <span
841
- style={{
842
- transform:
843
- 'translate(' +
844
- translateSuffixX +
845
- 'px,' +
846
- translateSuffixY +
847
- 'px)',
848
- fontSize: fontSize,
849
- }}
850
- >
851
- {text}
852
- </span>
853
- )}
854
- </span>
855
- )
856
- }
857
- {
858
- //百分比值
859
- showPercent && (
860
- <span
861
- style={{
862
- transform:
863
- 'translate(' +
864
- translatePercentX +
865
- 'px,' +
866
- translatePercentY +
867
- 'px)',
868
- ...getFontStyle(percentFont),
869
- margin: gap / 2 + 'px 0',
870
- color: percentColor ? seriesColor : percentFont.color,
871
- }}
872
- >
873
- {percent + '%'}
874
- </span>
875
- )
876
- }
877
- </div>
878
- </foreignObject>
879
- )
880
- );
881
- };
882
-
883
- const Label = ({
884
- config: {
885
- maxRadius = 0,
886
- lineLength,
887
- lineColor,
888
- distance,
889
- mode,
890
- show,
891
- translate: { x: translateX, y: translateY },
892
- name: { show: showName, font: nameFont },
893
- value: {
894
- show: showValue,
895
- font: valueFont,
896
- suffix: {
897
- show: showSuffix,
898
- text,
899
- fontSize: suffixFontSize,
900
- translate: { x: suffixTranslateX, y: suffixTranslateY },
901
- },
902
- sameColor: valueSameColor = false,
903
- },
904
- percent: {
905
- show: showPercent,
906
- font: percentFont,
907
- precision,
908
- sameColor: percentSameColor = false,
909
- },
910
- },
911
- arcs,
912
- animation,
913
- }) => {
914
- // const [labels, setLabels] = useState(null);
915
- // const [opacity, setOpacity] = useState(0);
916
-
917
- const _arcs = useMemo(
918
- () => getDataWithPercent(arcs, precision),
919
- [arcs, precision]
920
- );
921
- // useEffect(() => {
922
- // if (labels) {
923
- // const children = [...labels.children];
924
- // const bbox = children.reduce(
925
- // (prev, current) => {
926
- // const { topRight, bottomRight, bottomLeft, topLeft } = prev;
927
- // const { x, y, height } = current.getBBox();
928
-
929
- // current._y1 = y;
930
- // current._y2 = y + height;
931
- // current._delta = 0;
932
-
933
- // if (x > 0) {
934
- // if (y > 0) {
935
- // bottomRight.push(current);
936
- // } else {
937
- // topRight.push(current);
938
- // }
939
- // } else {
940
- // if (y > 0) {
941
- // bottomLeft.push(current);
942
- // } else {
943
- // topLeft.push(current);
944
- // }
945
- // }
946
- // return prev;
947
- // },
948
- // {
949
- // topRight: [],
950
- // bottomRight: [],
951
- // bottomLeft: [],
952
- // topLeft: [],
953
- // }
954
- // );
955
- // console.log('bbox: ', bbox);
956
- // }
957
- // }, [labels]);
958
-
959
- return (
960
- <g
961
- // style={{ opacity }} ref={setLabels}
962
- >
963
- {_arcs.map(
964
- (
965
- {
966
- series: {
967
- color: {
968
- type,
969
- pure,
970
- linear: { stops },
971
- },
972
- },
973
- displayName,
974
- value,
975
- percent,
976
- arc,
977
- outerRadius,
978
- index: actualIndex,
979
- },
980
- index
981
- ) => {
982
- const [x, y] = arc.centroid();
983
- const midAngle = Math.atan2(y, x);
984
-
985
- const [x1, y1] = getCoord(
986
- midAngle,
987
- maxRadius ? maxRadius : outerRadius
988
- );
989
-
990
- const radius = (maxRadius ? maxRadius : outerRadius) + distance;
991
- const [x2, y2] = getCoord(midAngle, radius);
992
-
993
- const direction = x2 < 0 ? -1 : 1;
994
- const x3 = x2 + lineLength * direction;
995
-
996
- const _x = x3 + (translateX + 6) * direction;
997
-
998
- const _showName = showName && displayName;
999
- const _showValue = showValue && (value || showSuffix);
1000
-
1001
- return (
1002
- show &&
1003
- (_showName || showPercent || showValue) && (
1004
- <g key={index}>
1005
- <path
1006
- className={animation ? ringCss['label-line'] : ''}
1007
- style={{
1008
- animationDelay: `${
1009
- animation ? (actualIndex + 1) * 2000 - 800 : 0
1010
- }ms`,
1011
- }}
1012
- d={
1013
- 'M' +
1014
- x1 +
1015
- ', ' +
1016
- y1 +
1017
- 'L' +
1018
- x2 +
1019
- ', ' +
1020
- y2 +
1021
- 'L' +
1022
- x3 +
1023
- ', ' +
1024
- y2
1025
- }
1026
- stroke={
1027
- lineColor
1028
- ? lineColor
1029
- : type == 'pure'
1030
- ? pure
1031
- : stops[0].color
1032
- }
1033
- fill='none'
1034
- />
1035
- <text
1036
- className={animation ? ringCss['label-text'] : ''}
1037
- style={{
1038
- animationDelay: `${
1039
- animation ? (actualIndex + 1) * 2000 - 800 : 0
1040
- }ms`,
1041
- }}
1042
- x={_x}
1043
- y={y2 + translateY}
1044
- dominantBaseline='middle'
1045
- textAnchor={x3 >= 0 ? 'start' : 'end'}
1046
- >
1047
- {_showName && (
1048
- <tspan style={getFontStyle(nameFont, 'svg')}>
1049
- {displayName + (showValue || showPercent ? ':' : '')}
1050
- </tspan>
1051
- )}
1052
- {showValue && (
1053
- <>
1054
- <tspan
1055
- x={!!(_showName && mode == 'vertical') ? _x : ''}
1056
- dx={valueDx(_showName, mode)}
1057
- dy={!!(_showName && mode == 'vertical') ? '1.5em' : ''}
1058
- style={getFontStyle(valueFont, 'svg')}
1059
- >
1060
- {value}
1061
- </tspan>
1062
- {showSuffix && (
1063
- <tspan
1064
- style={{
1065
- ...getFontStyle(valueFont, 'svg'),
1066
- fontSize: suffixFontSize,
1067
- }}
1068
- dx={suffixTranslateX}
1069
- dy={suffixTranslateY}
1070
- >
1071
- {text}
1072
- </tspan>
1073
- )}
1074
- </>
1075
- )}
1076
- {showPercent && (
1077
- <tspan
1078
- x={percentX(_showName, _showValue, mode, _x)}
1079
- dx={percentDx(_showName, _showValue, mode)}
1080
- dy={
1081
- percentDy(_showName, _showValue, mode) +
1082
- (_showValue && showSuffix ? suffixTranslateY * -1 : '')
1083
- }
1084
- style={getFontStyle(percentFont, 'svg')}
1085
- >
1086
- {(_showValue ? '(' : '') +
1087
- percent +
1088
- '%' +
1089
- (_showValue ? ')' : '')}
1090
- </tspan>
1091
- )}
1092
- </text>
1093
- </g>
1094
- )
1095
- );
1096
- }
1097
- )}
1098
- </g>
1099
- );
1100
- };
1101
-
1102
- const RingLabel = ({
1103
- config: {
1104
- maxRadius = 0,
1105
- lineLength,
1106
- lineColor,
1107
- distance,
1108
- mode,
1109
- show,
1110
- translate: { x: translateX, y: translateY },
1111
- name: { show: showName, font: nameFont },
1112
- value: {
1113
- show: showValue,
1114
- font: valueFont,
1115
- suffix: {
1116
- show: showSuffix,
1117
- text,
1118
- fontSize: suffixFontSize,
1119
- translate: { x: suffixTranslateX, y: suffixTranslateY },
1120
- },
1121
- sameColor: valueSameColor = false,
1122
- },
1123
- percent: {
1124
- show: showPercent,
1125
- font: percentFont,
1126
- precision,
1127
- sameColor: percentSameColor = false,
1128
- },
1129
- },
1130
- arcs,
1131
- }) => {
1132
- const _arcs = useMemo(
1133
- () => getDataWithPercent(arcs, precision),
1134
- [arcs, precision]
1135
- );
1136
-
1137
- return (
1138
- <g>
1139
- {_arcs.map(
1140
- (
1141
- {
1142
- series: {
1143
- color: {
1144
- type,
1145
- pure,
1146
- linear: { stops },
1147
- },
1148
- },
1149
- data: realData,
1150
- displayName,
1151
- value,
1152
- percent,
1153
- arc,
1154
- outerRadius,
1155
- index: actualIndex,
1156
- },
1157
- index
1158
- ) => {
1159
- const [x, y] = arc.centroid();
1160
-
1161
- const midAngle = Math.atan2(y, x);
1162
-
1163
- const [x1, y1] = getCoord(
1164
- midAngle,
1165
- maxRadius ? maxRadius : outerRadius
1166
- );
1167
-
1168
- const radius = (maxRadius ? maxRadius : outerRadius) + distance;
1169
- const [x2, y2] = getCoord(midAngle, radius);
1170
-
1171
- const directionX = x2 < 0 ? -1 : 1;
1172
- const directionY = y2 < 0 ? -1 : 1;
1173
- const x3 = x2 + lineLength * directionX;
1174
- const _x = x3 + (translateX + 6) * directionX;
1175
-
1176
- const _showName = showName && displayName;
1177
- const _showValue = showValue && (value || showSuffix);
1178
-
1179
- return (
1180
- show &&
1181
- (_showName || showPercent || _showValue) && (
1182
- <g key={index}>
1183
- <path
1184
- className={ringCss['label-line']}
1185
- style={{
1186
- animationDelay: `${(actualIndex + 1) * 2000 - 800}ms`,
1187
- }}
1188
- d={
1189
- 'M' +
1190
- x1 +
1191
- ', ' +
1192
- y1 +
1193
- 'L' +
1194
- x2 +
1195
- ', ' +
1196
- y2 +
1197
- 'L' +
1198
- x3 +
1199
- ', ' +
1200
- y2
1201
- }
1202
- stroke={
1203
- lineColor
1204
- ? lineColor
1205
- : type == 'pure'
1206
- ? pure
1207
- : stops[0].color
1208
- }
1209
- fill='none'
1210
- />
1211
- <text
1212
- className={ringCss['label-text']}
1213
- style={{
1214
- animationDelay: `${(actualIndex + 1) * 2000 - 800}ms`,
1215
- }}
1216
- x={mode == 'horizontal' ? _x : x2}
1217
- y={y2 + translateY}
1218
- dominantBaseline='middle'
1219
- textAnchor={x3 >= 0 ? 'start' : 'end'}
1220
- >
1221
- {_showName && (
1222
- <tspan
1223
- dy={nameDy(_showValue, showPercent, mode, directionY)}
1224
- style={getFontStyle(nameFont, 'svg')}
1225
- >
1226
- {displayName + (showValue || showPercent ? ':' : '')}
1227
- </tspan>
1228
- )}
1229
- {_showValue && (
1230
- <>
1231
- <tspan
1232
- x={_showName ? (mode == 'horizontal' ? '' : x2) : ''}
1233
- dx={valueDx(_showName, mode)}
1234
- dy={valueDy(_showName, mode, directionY)}
1235
- style={getFontStyle(valueFont, 'svg')}
1236
- >
1237
- {realData.y}
1238
- </tspan>
1239
- {showSuffix && (
1240
- <tspan
1241
- style={{
1242
- ...getFontStyle(valueFont, 'svg'),
1243
- fontSize: suffixFontSize,
1244
- }}
1245
- dx={suffixTranslateX}
1246
- dy={suffixTranslateY}
1247
- >
1248
- {text}
1249
- </tspan>
1250
- )}
1251
- </>
1252
- )}
1253
- {showPercent && (
1254
- <tspan
1255
- x={
1256
- _showName
1257
- ? _showValue
1258
- ? ''
1259
- : mode == 'vertical'
1260
- ? x2
1261
- : ''
1262
- : ''
1263
- }
1264
- dx={percentDx(_showName, _showValue, mode)}
1265
- dy={
1266
- percentDy_(_showName, _showValue, mode, directionY) +
1267
- (_showValue && showSuffix ? suffixTranslateY * -1 : '')
1268
- }
1269
- style={getFontStyle(percentFont, 'svg')}
1270
- >
1271
- {(_showValue ? '(' : '') +
1272
- percent +
1273
- '%' +
1274
- (_showValue ? ')' : '')}
1275
- </tspan>
1276
- )}
1277
- </text>
1278
- </g>
1279
- )
1280
- );
1281
- }
1282
- )}
1283
- </g>
1284
- );
1285
- };
1286
-
1287
- export default Mapping(Carousel(Component));
1
+ /**
2
+ * 饼环图
3
+ */
4
+ import React, {
5
+ memo,
6
+ useMemo,
7
+ useCallback,
8
+ useRef,
9
+ useState,
10
+ useContext,
11
+ useLayoutEffect,
12
+ Fragment,
13
+ } from 'react';
14
+ import { ChartContainer, Carousel, Legend, ConicalGradient, Mapping } from '.';
15
+ import { chartContext } from '../context';
16
+ import {
17
+ getFontStyle,
18
+ sortPie,
19
+ getDataWithPercent,
20
+ getColorList,
21
+ } from '../utils';
22
+ import { pie, arc, extent, scaleLinear } from 'd3v7';
23
+ import { animate, linear } from 'popmotion';
24
+ import LinearGradient from './LinearGradient';
25
+ import { pieLegendFormatter as legendFormatter } from '../formatter';
26
+ import ringCss from '../css/piechart.module.css';
27
+
28
+ const PI = Math.PI;
29
+
30
+ const defaultChart = {
31
+ outerRadius: 1,
32
+ innerRadius: 0,
33
+ cornerRadius: 0,
34
+ rose: false,
35
+ roseType: 'radius',
36
+ baseRadius: 0,
37
+ padAngle: 0,
38
+ };
39
+ const defaultAngle = { startAngle: 0, endAngle: 360, antiClockwise: false };
40
+
41
+ const nameDy = (showValue, showPercent, mode, dir) => {
42
+ if (showValue || showPercent) {
43
+ if (mode == 'vertical') {
44
+ return dir == 1 ? '1.1em' : '-2.6em';
45
+ } else {
46
+ return 0;
47
+ }
48
+ } else {
49
+ if (mode == 'vertical') {
50
+ return dir * 1.1 + 'em';
51
+ } else {
52
+ return 0;
53
+ }
54
+ }
55
+ };
56
+ const valueDy = (value1, mode, dir) => {
57
+ if (value1) {
58
+ if (mode == 'vertical') {
59
+ return '1.5em';
60
+ } else {
61
+ return 0;
62
+ }
63
+ } else {
64
+ if (mode == 'vertical') {
65
+ return dir == 1 ? '1.1em' : '-1.1em';
66
+ } else {
67
+ return 0;
68
+ }
69
+ }
70
+ };
71
+
72
+ const percentDy_ = (showName, showValue, mode, dir) => {
73
+ if (showValue) {
74
+ return 0;
75
+ }
76
+ if (showName) {
77
+ if (mode == 'vertical') {
78
+ return '1.5em';
79
+ } else {
80
+ return 0;
81
+ }
82
+ } else {
83
+ if (mode == 'vertical') {
84
+ return dir * 1.1 + 'em';
85
+ } else {
86
+ return 0;
87
+ }
88
+ }
89
+ };
90
+
91
+ const percentX = (showName, showValue, mode, x) => {
92
+ if (showValue) {
93
+ return '';
94
+ }
95
+ if (showName) {
96
+ if (mode == 'vertical') {
97
+ return x;
98
+ } else {
99
+ return '';
100
+ }
101
+ } else {
102
+ return x;
103
+ }
104
+ };
105
+
106
+ const percentDx = (showName, showValue, mode) => {
107
+ if (showValue) {
108
+ return '0.5em';
109
+ }
110
+ if (showName) {
111
+ if (mode == 'vertical') {
112
+ return 0;
113
+ } else {
114
+ return '0.5em';
115
+ }
116
+ } else {
117
+ return 0;
118
+ }
119
+ };
120
+
121
+ const percentDy = (showName, showValue, mode) => {
122
+ if (showValue) {
123
+ return 0;
124
+ }
125
+ if (showName) {
126
+ if (mode == 'vertical') {
127
+ return '1.5em';
128
+ } else {
129
+ return 0;
130
+ }
131
+ } else {
132
+ return 0;
133
+ }
134
+ };
135
+
136
+ const valueDx = (showName, mode) => {
137
+ if (!showName) {
138
+ return '';
139
+ }
140
+ if (mode == 'vertical') {
141
+ return '';
142
+ } else {
143
+ return '0.5em';
144
+ }
145
+ };
146
+
147
+ const getCoord = (deg, radius) => {
148
+ var x = Math.cos(deg) * radius,
149
+ y = Math.sin(deg) * radius;
150
+ return [x, y];
151
+ };
152
+
153
+ const getRoseRadius = ({ innerRadius, baseRadius }) =>
154
+ innerRadius + (1 - innerRadius) * baseRadius;
155
+
156
+ const getAngle = ({ startAngle, endAngle, antiClockwise, ...rest }) => {
157
+ if (antiClockwise)
158
+ return {
159
+ ...rest,
160
+ startAngle: endAngle - 180,
161
+ endAngle: startAngle - 180,
162
+ };
163
+ return { ...rest, startAngle, endAngle };
164
+ };
165
+
166
+ const getArc = (
167
+ radius,
168
+ {
169
+ padAngle = 0,
170
+ innerRadius = 0,
171
+ outerRadius = 1,
172
+ cornerRadius = 0,
173
+ startAngle = 0,
174
+ endAngle = 360,
175
+ ...rest
176
+ },
177
+ series
178
+ ) => ({
179
+ ...rest,
180
+ type: 'pie',
181
+ fieldName: series.fieldName,
182
+ displayName: series.displayName || rest.data.s,
183
+ series: series,
184
+ innerRadius: innerRadius * radius,
185
+ outerRadius: outerRadius * radius,
186
+ arc: arc()
187
+ .innerRadius(innerRadius * radius)
188
+ .outerRadius(outerRadius * radius)
189
+ .cornerRadius(cornerRadius)
190
+ .startAngle(startAngle)
191
+ .endAngle(endAngle)
192
+ .padAngle(padAngle),
193
+ });
194
+
195
+ const getCircleScale = ({ count, color, width, length } = tick, radius) => {
196
+ let data = [],
197
+ arcs = [],
198
+ centroids = [];
199
+ for (let i = 0; i < count; i++) {
200
+ data.push(1);
201
+ }
202
+ let scaleData = pie()(data);
203
+ scaleData.map((data) => {
204
+ let _arc = arc()
205
+ .innerRadius(radius + length / 2)
206
+ .outerRadius(radius + length / 2)
207
+ .startAngle(data.startAngle)
208
+ .endAngle(data.endAngle);
209
+ arcs.push(_arc());
210
+ centroids.push(_arc.centroid());
211
+ });
212
+ return (
213
+ <g>
214
+ {centroids.map((center, index) => {
215
+ let x = center[0],
216
+ y = center[1];
217
+ let rate = length / Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
218
+ return (
219
+ <path
220
+ key={index}
221
+ d={`M${x},${y}l${x * rate},${y * rate}`}
222
+ strokeWidth={width}
223
+ stroke={color}
224
+ />
225
+ );
226
+ })}
227
+ </g>
228
+ );
229
+ };
230
+
231
+ const Component = memo(
232
+ ({
233
+ config: {
234
+ chart: {
235
+ dimension: {
236
+ chartDimension: { width, height },
237
+ },
238
+ label,
239
+ legend: { formatter = legendFormatter, ...legend },
240
+ margin: { marginLeft, marginTop },
241
+ },
242
+ fan: {
243
+ chart = defaultChart,
244
+ chart: { outerRadius = defaultChart.outerRadius, padAngle },
245
+ angle = defaultAngle,
246
+ stroke: { show, strokeWidth, color } = { show: false },
247
+ decorate,
248
+ decorate2,
249
+ categoryText,
250
+ outerDecorate,
251
+ current,
252
+ } = {},
253
+ order,
254
+ series,
255
+ animation: {
256
+ on,
257
+ current: {
258
+ heighten = 0,
259
+ opacity = 0,
260
+ width: radiusWidthAdd = 0,
261
+ color: animateColor,
262
+ textStyle: animateCTS,
263
+ gap = 0,
264
+ },
265
+ rotate = 0,
266
+ },
267
+ },
268
+ state: { currentIndex, trigger },
269
+ onEvent,
270
+ data = [],
271
+ }) => {
272
+ const prevIndex = useRef(null);
273
+ const { precision: legendPrecision } = legend.config.percent;
274
+ const {
275
+ id,
276
+ width: chartWidth,
277
+ height: chartHeight,
278
+ triggerOnRelative,
279
+ onEmit,
280
+ } = useContext(chartContext);
281
+
282
+ const [y, setY] = useState(1);
283
+ const radius = (Math.min(chartWidth, chartHeight) / 2) * outerRadius;
284
+
285
+ const arcsFunc = useMemo(() => {
286
+ const { startAngle = 0, endAngle = 360 } = getAngle(angle);
287
+ const arcsFunc = pie()
288
+ .startAngle((startAngle * PI) / 180)
289
+ .endAngle((endAngle * PI) / 180)
290
+ .value((d) => d.y);
291
+ return arcsFunc;
292
+ }, [angle]);
293
+
294
+ const arcs = useMemo(() => {
295
+ const _chart = Object.assign(defaultChart, chart);
296
+ const {
297
+ innerRadius,
298
+ outerRadius,
299
+ rose,
300
+ cornerRadius,
301
+ padAngle,
302
+ roseType,
303
+ } = _chart;
304
+ const _padAngle = (padAngle * Math.PI) / 180;
305
+
306
+ switch (order) {
307
+ case '':
308
+ arcsFunc.sort(null);
309
+ break;
310
+ case 'desc':
311
+ arcsFunc.sort((a, b) => b.y - a.y);
312
+ break;
313
+ case 'asc':
314
+ arcsFunc.sort((a, b) => a.y - b.y);
315
+ break;
316
+ }
317
+
318
+ const arcs = arcsFunc(data);
319
+ const legendDataWithPercent = getDataWithPercent(arcs, legendPrecision);
320
+ const _legendDataWithPercent = sortPie(legendDataWithPercent, order);
321
+
322
+ if (rose) {
323
+ const domain = extent(_legendDataWithPercent, (d) => d.value);
324
+ const roseRadius = getRoseRadius(_chart);
325
+ const scaler = scaleLinear().domain(domain).range([roseRadius, 1]);
326
+
327
+ const angle = (PI * 2) / _legendDataWithPercent.length;
328
+ return _legendDataWithPercent.map(
329
+ ({ startAngle, endAngle, ...arc }, index) => ({
330
+ ...arc,
331
+ id: id + '_linear_' + index,
332
+ startAngle: roseType == 'area' ? angle * index : startAngle,
333
+ endAngle: roseType == 'area' ? angle * (index + 1) : endAngle,
334
+ cornerRadius,
335
+ padAngle: _padAngle,
336
+ innerRadius,
337
+ outerRadius: scaler(arc.value),
338
+ })
339
+ );
340
+ }
341
+ return _legendDataWithPercent.map((arc, index) => ({
342
+ ...arc,
343
+ id: id + '_linear_' + index,
344
+ cornerRadius,
345
+ padAngle: _padAngle,
346
+ innerRadius,
347
+ outerRadius,
348
+ }));
349
+ }, [data, arcsFunc, chart, legendPrecision]);
350
+
351
+ const _arcs = useMemo(() => {
352
+ const seriesLength = series.size;
353
+ if (!seriesLength) return [];
354
+ const _series = [...series.values()];
355
+ return arcs.map((arc, index) => getArc(radius, arc, _series[index]));
356
+ }, [series, arcs, radius]);
357
+ const onClick = useCallback(
358
+ (e) =>
359
+ onEvent({
360
+ currentIndex: +e.currentTarget.dataset.index,
361
+ type: 'onClick',
362
+ }),
363
+ [onEvent]
364
+ );
365
+
366
+ const onMouseEnter = useCallback(
367
+ (e) =>
368
+ onEvent({
369
+ currentIndex: +e.currentTarget.dataset.index,
370
+ type: 'onMouseEnter',
371
+ }),
372
+ [onEvent]
373
+ );
374
+
375
+ const onMouseLeave = useCallback(
376
+ (e) =>
377
+ onEvent({
378
+ currentIndex: +e.currentTarget.dataset.index,
379
+ type: 'onMouseLeave',
380
+ }),
381
+ [onEvent]
382
+ );
383
+
384
+ useLayoutEffect(() => {
385
+ let animation;
386
+ if (!!on) {
387
+ animation = animate({
388
+ from: 0,
389
+ to: 1,
390
+ duration: 500,
391
+ ease: linear,
392
+ onPlay: () => {},
393
+ onUpdate: (v) => {
394
+ setY(v);
395
+ },
396
+ onComplete: () => {
397
+ const _data = arcs[+currentIndex].data;
398
+ triggerOnRelative(_data);
399
+ onEmit('carousel', _data);
400
+ },
401
+ });
402
+ } else {
403
+ if (currentIndex !== null && trigger === 'onClick') {
404
+ const _data = arcs[+currentIndex].data;
405
+ triggerOnRelative(_data);
406
+ onEmit(trigger, _data);
407
+ }
408
+ }
409
+ return () => {
410
+ prevIndex.current = currentIndex;
411
+ animation && animation.stop();
412
+ animation = null;
413
+ };
414
+ }, [
415
+ JSON.stringify(arcs),
416
+ on,
417
+ currentIndex,
418
+ trigger,
419
+ onEmit,
420
+ triggerOnRelative,
421
+ ]);
422
+
423
+ const halfChartWidth = chartWidth / 2;
424
+ const halfChartHeight = chartHeight / 2;
425
+
426
+ const rotate_ = decorate2
427
+ ? (-(arcs[+currentIndex].startAngle + arcs[+currentIndex].endAngle) *
428
+ 90) /
429
+ Math.PI +
430
+ rotate
431
+ : 0;
432
+ let maxRadius = 0;
433
+ _arcs.map((d) => {
434
+ maxRadius = Math.max(maxRadius, d.outerRadius);
435
+ });
436
+ let centerRadius = 0.5 * maxRadius + 0.5 * _arcs[0].innerRadius;
437
+ return outerDecorate ? (
438
+ <>
439
+ <ChartContainer //用于生成甜甜圈图,判断依据是外环装饰这个配置项(outerDecorate)
440
+ width={width}
441
+ height={height}
442
+ marginLeft={marginLeft}
443
+ marginTop={marginTop}
444
+ >
445
+ <g
446
+ style={{
447
+ transition: 'transform ease-in-out 0.3s',
448
+ transform:
449
+ 'translate(' +
450
+ halfChartWidth +
451
+ 'px, ' +
452
+ halfChartHeight +
453
+ 'px) rotate(' +
454
+ rotate_ +
455
+ 'deg)',
456
+ }}
457
+ >
458
+ {
459
+ //用于生成外环装饰的刻度
460
+ outerDecorate.tick.show &&
461
+ getCircleScale(outerDecorate.tick, maxRadius)
462
+ }
463
+ <circle //外环装饰
464
+ cx='0'
465
+ cy='0'
466
+ r={maxRadius + 2}
467
+ fill='none'
468
+ stroke={outerDecorate.color}
469
+ strokeWidth={outerDecorate.width}
470
+ />
471
+ {_arcs.map(
472
+ ({ id, value, series, arc, innerRadius, outerRadius }, index) => {
473
+ const arcWidth = outerRadius - innerRadius;
474
+ const path = arc
475
+ .innerRadius(centerRadius)
476
+ .outerRadius(centerRadius)(value);
477
+ const dashLength = Math.ceil(
478
+ (Math.PI * centerRadius * 2) / _arcs.length
479
+ );
480
+ const pie = getColorList(series.color);
481
+ return (
482
+ <Fragment key={index}>
483
+ <path
484
+ className={ringCss['inner-arc']}
485
+ style={{
486
+ strokeDasharray: `${dashLength},${2 * dashLength}`,
487
+ strokeDashoffset: dashLength,
488
+ animationDelay: `${index * 2000}ms`,
489
+ }}
490
+ data-index={index}
491
+ onClick={onClick}
492
+ onMouseEnter={onMouseEnter}
493
+ onMouseLeave={onMouseLeave}
494
+ d={path.split('L')[0]}
495
+ stroke={'url(#' + id + ')'}
496
+ strokeWidth={arcWidth}
497
+ fill='none'
498
+ />
499
+ <defs>
500
+ <LinearGradient
501
+ id={id}
502
+ colors={pie}
503
+ rotate={series.color.linear.angle + 180}
504
+ // gradientUnits='objectBoundingBox'
505
+ />
506
+ </defs>
507
+ </Fragment>
508
+ );
509
+ }
510
+ )}
511
+ {label && (
512
+ <RingLabel
513
+ config={{ ...label, maxRadius: maxRadius + 2 }}
514
+ arcs={_arcs}
515
+ />
516
+ )}
517
+ </g>
518
+ </ChartContainer>
519
+ <Legend {...legend} series={_arcs} formatter={formatter} />
520
+ </>
521
+ ) : (
522
+ <>
523
+ <ChartContainer
524
+ width={width}
525
+ height={height}
526
+ marginLeft={marginLeft}
527
+ marginTop={marginTop}
528
+ >
529
+ <g
530
+ style={{
531
+ transition: 'transform ease-in-out 0.3s',
532
+ transform:
533
+ 'translate(' +
534
+ halfChartWidth +
535
+ 'px, ' +
536
+ halfChartHeight +
537
+ 'px) rotate(' +
538
+ rotate_ +
539
+ 'deg)',
540
+ }}
541
+ >
542
+ {_arcs.map(
543
+ (
544
+ {
545
+ id,
546
+ value,
547
+ series,
548
+ arc,
549
+ innerRadius,
550
+ outerRadius,
551
+ index: dataIndex,
552
+ },
553
+ index
554
+ ) => {
555
+ const current = index == currentIndex;
556
+ const prev = index == prevIndex.current;
557
+ const offset = current ? y : prev ? 1 - y : 0;
558
+
559
+ const fillOpacity = animateColor
560
+ ? 1
561
+ : current
562
+ ? opacity / 100
563
+ : 1;
564
+ const deltaHeighten = offset * heighten;
565
+ const path = arc
566
+ .innerRadius(innerRadius + deltaHeighten)
567
+ .outerRadius(outerRadius + deltaHeighten)(value);
568
+ const pie = getColorList(series.color);
569
+ const currentPie = animateColor
570
+ ? getColorList(animateColor)
571
+ : getColorList(series.color);
572
+ let textPath = '',
573
+ categoryTextStyle = {};
574
+ if (categoryText && categoryText.show) {
575
+ //如果有类目文本,则需要计算文字路径
576
+ //let offsetWidth=decorate2.radiusWidth/2 + radiusWidthAdd/2; //当前文字需生成在装饰物内,故而半径需要减小
577
+ let textArc = arc
578
+ .innerRadius(
579
+ outerRadius + (current ? gap : categoryText.gap)
580
+ )
581
+ .outerRadius(
582
+ outerRadius + (current ? gap : categoryText.gap)
583
+ )(value);
584
+ let lastA = textArc.lastIndexOf('A');
585
+ textPath = textArc.slice(
586
+ 0,
587
+ lastA > 0 ? lastA : textArc.length
588
+ ); //文字路径
589
+ categoryTextStyle = current
590
+ ? animateCTS
591
+ : categoryText.textStyle; //这里把textstyle拿出来
592
+ }
593
+
594
+ return (
595
+ <Fragment key={index}>
596
+ <path
597
+ data-index={index}
598
+ onClick={onClick}
599
+ onMouseEnter={onMouseEnter}
600
+ onMouseLeave={onMouseLeave}
601
+ d={path}
602
+ stroke={show ? color : 'none'}
603
+ strokeWidth={show ? strokeWidth : '0'}
604
+ fill={'url(#' + id + ')'}
605
+ fillOpacity={fillOpacity}
606
+ />
607
+ {
608
+ //装饰物2,产生于每个弧的外部
609
+ decorate2 && decorate2.show && (
610
+ <path
611
+ data-index={index}
612
+ onClick={onClick}
613
+ onMouseEnter={onMouseEnter}
614
+ onMouseLeave={onMouseLeave}
615
+ d={arc
616
+ .innerRadius(outerRadius)
617
+ .outerRadius(
618
+ outerRadius +
619
+ decorate2.radiusWidth +
620
+ (current ? radiusWidthAdd : 0)
621
+ )(value)}
622
+ stroke={show ? color : 'none'}
623
+ strokeWidth={show ? strokeWidth : '0'}
624
+ fill={'url(#' + id + ')'}
625
+ fillOpacity={decorate2.opacity / 100}
626
+ />
627
+ )
628
+ }
629
+ {
630
+ //类目文本
631
+ categoryText && categoryText.show && (
632
+ <g>
633
+ <path
634
+ onClick={onClick}
635
+ onMouseEnter={onMouseEnter}
636
+ onMouseLeave={onMouseLeave}
637
+ id={id + '_text_' + index}
638
+ d={textPath}
639
+ fill='none'
640
+ stroke='none'
641
+ />
642
+ <text
643
+ textAnchor='middle'
644
+ style={{
645
+ ...categoryTextStyle,
646
+ fontWeight: categoryTextStyle.bold
647
+ ? 'bold'
648
+ : 'normal',
649
+ fontStyle: categoryTextStyle.italic
650
+ ? 'italic'
651
+ : 'normal',
652
+ pointerEvents: 'none',
653
+ }}
654
+ fill={categoryTextStyle.color}
655
+ >
656
+ <textPath
657
+ startOffset='50%'
658
+ href={'#' + id + '_text_' + index}
659
+ >
660
+ {_arcs[index].displayName ||
661
+ _arcs[index].fieldName}
662
+ </textPath>
663
+ </text>
664
+ </g>
665
+ )
666
+ }
667
+ <defs>
668
+ <LinearGradient
669
+ id={id}
670
+ colors={current ? currentPie : pie}
671
+ rotate={
672
+ current
673
+ ? animateColor
674
+ ? animateColor.linear.angle + 180
675
+ : series.color.linear.angle + 180
676
+ : series.color.linear.angle + 180
677
+ }
678
+ // gradientUnits='objectBoundingBox'
679
+ />
680
+ </defs>
681
+ </Fragment>
682
+ );
683
+ }
684
+ )}
685
+ {label && <Label config={label} arcs={_arcs} />}
686
+ {current && (
687
+ <g
688
+ fillOpacity={y}
689
+ style={{ transform: 'rotate(' + -rotate_ + 'deg)' }}
690
+ >
691
+ <Current
692
+ config={current}
693
+ width={width}
694
+ height={height}
695
+ data={_arcs}
696
+ currentIndex={+currentIndex}
697
+ />
698
+ </g>
699
+ )}
700
+ </g>
701
+ </ChartContainer>
702
+ {decorate && (
703
+ <ConicalGradient
704
+ width={width}
705
+ height={height}
706
+ centerX={halfChartWidth + marginLeft}
707
+ centerY={halfChartHeight + marginTop}
708
+ config={decorate}
709
+ arcs={_arcs}
710
+ radius={radius}
711
+ />
712
+ )}
713
+ <Legend {...legend} series={_arcs} formatter={formatter} />
714
+ </>
715
+ );
716
+ }
717
+ );
718
+
719
+ const Current = ({
720
+ config: {
721
+ show,
722
+ gap,
723
+ name: { show: showName, sameColor: nameColor, font: nameFont, textBreak },
724
+ percent: {
725
+ show: showPercent,
726
+ sameColor: percentColor,
727
+ font: percentFont,
728
+ precision,
729
+ translate: { x: translatePercentX, y: translatePercentY },
730
+ },
731
+ value: {
732
+ show: showValue,
733
+ sameColor: valueColor,
734
+ font: valueFont,
735
+ translate: { x: translateValueX, y: translateValueY },
736
+ suffix: {
737
+ show: showSuffix,
738
+ fontSize,
739
+ text,
740
+ translate: { x: translateSuffixX, y: translateSuffixY },
741
+ },
742
+ },
743
+ },
744
+ width,
745
+ height,
746
+ data,
747
+ currentIndex,
748
+ }) => {
749
+ const _data = useMemo(() => {
750
+ const legendDataWithPercent = getDataWithPercent(data, precision);
751
+ return sortPie(legendDataWithPercent, '');
752
+ }, [data, precision]);
753
+ const currentData = _data[currentIndex];
754
+
755
+ if (!currentData) return null;
756
+
757
+ const { displayName, fieldName, value, percent } = currentData;
758
+ let nameTemp = displayName ? displayName : fieldName; //类目名
759
+ let nameList = [];
760
+ if (textBreak) {
761
+ //如果限制了首行字符,则切割组件
762
+ while (nameTemp.length > textBreak) {
763
+ nameList.push(nameTemp.slice(0, textBreak));
764
+ nameTemp = nameTemp.slice(textBreak);
765
+ }
766
+ }
767
+ nameList.push(nameTemp);
768
+ let foreignStyle = {
769
+ //foreignObject标签样式,
770
+ width,
771
+ height,
772
+ transform: `translate(-${width / 2}px,-${height / 2}px)`,
773
+ pointerEvents: 'none',
774
+ },
775
+ boxStyle = {
776
+ //弹性盒子样式,用于当前值的上下居中对齐等
777
+ width,
778
+ height,
779
+ display: 'flex',
780
+ flexDirection: 'column',
781
+ justifyContent: 'center',
782
+ alignItems: 'center',
783
+ };
784
+ let seriesColor = currentData.series.color;
785
+ seriesColor =
786
+ seriesColor.type == 'pure'
787
+ ? seriesColor.pure
788
+ : seriesColor.linear.stops[0].color;
789
+ return (
790
+ show && (
791
+ <foreignObject style={foreignStyle}>
792
+ <div style={boxStyle}>
793
+ {
794
+ //类目名称
795
+ showName && (
796
+ <div
797
+ style={{
798
+ ...getFontStyle(nameFont),
799
+ margin: gap / 2 + 'px 0',
800
+ display: 'flex',
801
+ flexDirection: 'column',
802
+ alignItems: 'center',
803
+ color: nameColor ? seriesColor : nameFont.color,
804
+ }}
805
+ >
806
+ {nameList.map((d, i) => {
807
+ return <span key={i}>{d}</span>;
808
+ })}
809
+ </div>
810
+ )
811
+ }
812
+ {
813
+ //真实值
814
+ showValue && (
815
+ <span
816
+ style={{
817
+ ...getFontStyle(valueFont),
818
+ transform:
819
+ 'translate(' +
820
+ translateValueX +
821
+ 'px,' +
822
+ translateValueY +
823
+ 'px)',
824
+ margin: gap / 2 + 'px 0',
825
+ color: valueColor ? seriesColor : valueFont.color,
826
+ }}
827
+ >
828
+ {value}
829
+ {showSuffix && text && (
830
+ <span
831
+ style={{
832
+ transform:
833
+ 'translate(' +
834
+ translateSuffixX +
835
+ 'px,' +
836
+ translateSuffixY +
837
+ 'px)',
838
+ fontSize: fontSize,
839
+ }}
840
+ >
841
+ {text}
842
+ </span>
843
+ )}
844
+ </span>
845
+ )
846
+ }
847
+ {
848
+ //百分比值
849
+ showPercent && (
850
+ <span
851
+ style={{
852
+ transform:
853
+ 'translate(' +
854
+ translatePercentX +
855
+ 'px,' +
856
+ translatePercentY +
857
+ 'px)',
858
+ ...getFontStyle(percentFont),
859
+ margin: gap / 2 + 'px 0',
860
+ color: percentColor ? seriesColor : percentFont.color,
861
+ }}
862
+ >
863
+ {percent + '%'}
864
+ </span>
865
+ )
866
+ }
867
+ </div>
868
+ </foreignObject>
869
+ )
870
+ );
871
+ };
872
+
873
+ const Label = ({
874
+ config: {
875
+ maxRadius = 0,
876
+ lineLength,
877
+ lineColor,
878
+ distance,
879
+ mode,
880
+ show,
881
+ translate: { x: translateX, y: translateY },
882
+ name: { show: showName, font: nameFont },
883
+ value: {
884
+ show: showValue,
885
+ font: valueFont,
886
+ suffix: {
887
+ show: showSuffix,
888
+ text,
889
+ fontSize: suffixFontSize,
890
+ translate: { x: suffixTranslateX, y: suffixTranslateY },
891
+ },
892
+ sameColor: valueSameColor = false,
893
+ },
894
+ percent: {
895
+ show: showPercent,
896
+ font: percentFont,
897
+ precision,
898
+ sameColor: percentSameColor = false,
899
+ },
900
+ },
901
+ arcs,
902
+ animation,
903
+ }) => {
904
+ // const [labels, setLabels] = useState(null);
905
+ // const [opacity, setOpacity] = useState(0);
906
+
907
+ const _arcs = useMemo(
908
+ () => getDataWithPercent(arcs, precision),
909
+ [arcs, precision]
910
+ );
911
+ // useEffect(() => {
912
+ // if (labels) {
913
+ // const children = [...labels.children];
914
+ // const bbox = children.reduce(
915
+ // (prev, current) => {
916
+ // const { topRight, bottomRight, bottomLeft, topLeft } = prev;
917
+ // const { x, y, height } = current.getBBox();
918
+
919
+ // current._y1 = y;
920
+ // current._y2 = y + height;
921
+ // current._delta = 0;
922
+
923
+ // if (x > 0) {
924
+ // if (y > 0) {
925
+ // bottomRight.push(current);
926
+ // } else {
927
+ // topRight.push(current);
928
+ // }
929
+ // } else {
930
+ // if (y > 0) {
931
+ // bottomLeft.push(current);
932
+ // } else {
933
+ // topLeft.push(current);
934
+ // }
935
+ // }
936
+ // return prev;
937
+ // },
938
+ // {
939
+ // topRight: [],
940
+ // bottomRight: [],
941
+ // bottomLeft: [],
942
+ // topLeft: [],
943
+ // }
944
+ // );
945
+ // console.log('bbox: ', bbox);
946
+ // }
947
+ // }, [labels]);
948
+
949
+ return (
950
+ <g
951
+ // style={{ opacity }} ref={setLabels}
952
+ >
953
+ {_arcs.map(
954
+ (
955
+ {
956
+ series: {
957
+ color: {
958
+ type,
959
+ pure,
960
+ linear: { stops },
961
+ },
962
+ },
963
+ displayName,
964
+ value,
965
+ percent,
966
+ arc,
967
+ outerRadius,
968
+ index: actualIndex,
969
+ },
970
+ index
971
+ ) => {
972
+ const [x, y] = arc.centroid();
973
+ const midAngle = Math.atan2(y, x);
974
+
975
+ const [x1, y1] = getCoord(
976
+ midAngle,
977
+ maxRadius ? maxRadius : outerRadius
978
+ );
979
+
980
+ const radius = (maxRadius ? maxRadius : outerRadius) + distance;
981
+ const [x2, y2] = getCoord(midAngle, radius);
982
+
983
+ const direction = x2 < 0 ? -1 : 1;
984
+ const x3 = x2 + lineLength * direction;
985
+
986
+ const _x = x3 + (translateX + 6) * direction;
987
+
988
+ const _showName = showName && displayName;
989
+ const _showValue = showValue && (value || showSuffix);
990
+
991
+ return (
992
+ show &&
993
+ (_showName || showPercent || showValue) && (
994
+ <g key={index}>
995
+ <path
996
+ className={animation ? ringCss['label-line'] : ''}
997
+ style={{
998
+ animationDelay: `${
999
+ animation ? (actualIndex + 1) * 2000 - 800 : 0
1000
+ }ms`,
1001
+ }}
1002
+ d={
1003
+ 'M' +
1004
+ x1 +
1005
+ ', ' +
1006
+ y1 +
1007
+ 'L' +
1008
+ x2 +
1009
+ ', ' +
1010
+ y2 +
1011
+ 'L' +
1012
+ x3 +
1013
+ ', ' +
1014
+ y2
1015
+ }
1016
+ stroke={
1017
+ lineColor
1018
+ ? lineColor
1019
+ : type == 'pure'
1020
+ ? pure
1021
+ : stops[0].color
1022
+ }
1023
+ fill='none'
1024
+ />
1025
+ <text
1026
+ className={animation ? ringCss['label-text'] : ''}
1027
+ style={{
1028
+ animationDelay: `${
1029
+ animation ? (actualIndex + 1) * 2000 - 800 : 0
1030
+ }ms`,
1031
+ }}
1032
+ x={_x}
1033
+ y={y2 + translateY}
1034
+ dominantBaseline='middle'
1035
+ textAnchor={x3 >= 0 ? 'start' : 'end'}
1036
+ >
1037
+ {_showName && (
1038
+ <tspan style={getFontStyle(nameFont, 'svg')}>
1039
+ {displayName + (showValue || showPercent ? ':' : '')}
1040
+ </tspan>
1041
+ )}
1042
+ {showValue && (
1043
+ <>
1044
+ <tspan
1045
+ x={!!(_showName && mode == 'vertical') ? _x : ''}
1046
+ dx={valueDx(_showName, mode)}
1047
+ dy={!!(_showName && mode == 'vertical') ? '1.5em' : ''}
1048
+ style={getFontStyle(valueFont, 'svg')}
1049
+ >
1050
+ {value}
1051
+ </tspan>
1052
+ {showSuffix && (
1053
+ <tspan
1054
+ style={{
1055
+ ...getFontStyle(valueFont, 'svg'),
1056
+ fontSize: suffixFontSize,
1057
+ }}
1058
+ dx={suffixTranslateX}
1059
+ dy={suffixTranslateY}
1060
+ >
1061
+ {text}
1062
+ </tspan>
1063
+ )}
1064
+ </>
1065
+ )}
1066
+ {showPercent && (
1067
+ <tspan
1068
+ x={percentX(_showName, _showValue, mode, _x)}
1069
+ dx={percentDx(_showName, _showValue, mode)}
1070
+ dy={
1071
+ percentDy(_showName, _showValue, mode) +
1072
+ (_showValue && showSuffix ? suffixTranslateY * -1 : '')
1073
+ }
1074
+ style={getFontStyle(percentFont, 'svg')}
1075
+ >
1076
+ {(_showValue ? '(' : '') +
1077
+ percent +
1078
+ '%' +
1079
+ (_showValue ? ')' : '')}
1080
+ </tspan>
1081
+ )}
1082
+ </text>
1083
+ </g>
1084
+ )
1085
+ );
1086
+ }
1087
+ )}
1088
+ </g>
1089
+ );
1090
+ };
1091
+
1092
+ const RingLabel = ({
1093
+ config: {
1094
+ maxRadius = 0,
1095
+ lineLength,
1096
+ lineColor,
1097
+ distance,
1098
+ mode,
1099
+ show,
1100
+ translate: { x: translateX, y: translateY },
1101
+ name: { show: showName, font: nameFont },
1102
+ value: {
1103
+ show: showValue,
1104
+ font: valueFont,
1105
+ suffix: {
1106
+ show: showSuffix,
1107
+ text,
1108
+ fontSize: suffixFontSize,
1109
+ translate: { x: suffixTranslateX, y: suffixTranslateY },
1110
+ },
1111
+ sameColor: valueSameColor = false,
1112
+ },
1113
+ percent: {
1114
+ show: showPercent,
1115
+ font: percentFont,
1116
+ precision,
1117
+ sameColor: percentSameColor = false,
1118
+ },
1119
+ },
1120
+ arcs,
1121
+ }) => {
1122
+ const _arcs = useMemo(
1123
+ () => getDataWithPercent(arcs, precision),
1124
+ [arcs, precision]
1125
+ );
1126
+
1127
+ return (
1128
+ <g>
1129
+ {_arcs.map(
1130
+ (
1131
+ {
1132
+ series: {
1133
+ color: {
1134
+ type,
1135
+ pure,
1136
+ linear: { stops },
1137
+ },
1138
+ },
1139
+ data: realData,
1140
+ displayName,
1141
+ value,
1142
+ percent,
1143
+ arc,
1144
+ outerRadius,
1145
+ index: actualIndex,
1146
+ },
1147
+ index
1148
+ ) => {
1149
+ const [x, y] = arc.centroid();
1150
+
1151
+ const midAngle = Math.atan2(y, x);
1152
+
1153
+ const [x1, y1] = getCoord(
1154
+ midAngle,
1155
+ maxRadius ? maxRadius : outerRadius
1156
+ );
1157
+
1158
+ const radius = (maxRadius ? maxRadius : outerRadius) + distance;
1159
+ const [x2, y2] = getCoord(midAngle, radius);
1160
+
1161
+ const directionX = x2 < 0 ? -1 : 1;
1162
+ const directionY = y2 < 0 ? -1 : 1;
1163
+ const x3 = x2 + lineLength * directionX;
1164
+ const _x = x3 + (translateX + 6) * directionX;
1165
+
1166
+ const _showName = showName && displayName;
1167
+ const _showValue = showValue && (value || showSuffix);
1168
+
1169
+ return (
1170
+ show &&
1171
+ (_showName || showPercent || _showValue) && (
1172
+ <g key={index}>
1173
+ <path
1174
+ className={ringCss['label-line']}
1175
+ style={{
1176
+ animationDelay: `${(actualIndex + 1) * 2000 - 800}ms`,
1177
+ }}
1178
+ d={
1179
+ 'M' +
1180
+ x1 +
1181
+ ', ' +
1182
+ y1 +
1183
+ 'L' +
1184
+ x2 +
1185
+ ', ' +
1186
+ y2 +
1187
+ 'L' +
1188
+ x3 +
1189
+ ', ' +
1190
+ y2
1191
+ }
1192
+ stroke={
1193
+ lineColor
1194
+ ? lineColor
1195
+ : type == 'pure'
1196
+ ? pure
1197
+ : stops[0].color
1198
+ }
1199
+ fill='none'
1200
+ />
1201
+ <text
1202
+ className={ringCss['label-text']}
1203
+ style={{
1204
+ animationDelay: `${(actualIndex + 1) * 2000 - 800}ms`,
1205
+ }}
1206
+ x={mode == 'horizontal' ? _x : x2}
1207
+ y={y2 + translateY}
1208
+ dominantBaseline='middle'
1209
+ textAnchor={x3 >= 0 ? 'start' : 'end'}
1210
+ >
1211
+ {_showName && (
1212
+ <tspan
1213
+ dy={nameDy(_showValue, showPercent, mode, directionY)}
1214
+ style={getFontStyle(nameFont, 'svg')}
1215
+ >
1216
+ {displayName + (showValue || showPercent ? ':' : '')}
1217
+ </tspan>
1218
+ )}
1219
+ {_showValue && (
1220
+ <>
1221
+ <tspan
1222
+ x={_showName ? (mode == 'horizontal' ? '' : x2) : ''}
1223
+ dx={valueDx(_showName, mode)}
1224
+ dy={valueDy(_showName, mode, directionY)}
1225
+ style={getFontStyle(valueFont, 'svg')}
1226
+ >
1227
+ {realData.y}
1228
+ </tspan>
1229
+ {showSuffix && (
1230
+ <tspan
1231
+ style={{
1232
+ ...getFontStyle(valueFont, 'svg'),
1233
+ fontSize: suffixFontSize,
1234
+ }}
1235
+ dx={suffixTranslateX}
1236
+ dy={suffixTranslateY}
1237
+ >
1238
+ {text}
1239
+ </tspan>
1240
+ )}
1241
+ </>
1242
+ )}
1243
+ {showPercent && (
1244
+ <tspan
1245
+ x={
1246
+ _showName
1247
+ ? _showValue
1248
+ ? ''
1249
+ : mode == 'vertical'
1250
+ ? x2
1251
+ : ''
1252
+ : ''
1253
+ }
1254
+ dx={percentDx(_showName, _showValue, mode)}
1255
+ dy={
1256
+ percentDy_(_showName, _showValue, mode, directionY) +
1257
+ (_showValue && showSuffix ? suffixTranslateY * -1 : '')
1258
+ }
1259
+ style={getFontStyle(percentFont, 'svg')}
1260
+ >
1261
+ {(_showValue ? '(' : '') +
1262
+ percent +
1263
+ '%' +
1264
+ (_showValue ? ')' : '')}
1265
+ </tspan>
1266
+ )}
1267
+ </text>
1268
+ </g>
1269
+ )
1270
+ );
1271
+ }
1272
+ )}
1273
+ </g>
1274
+ );
1275
+ };
1276
+
1277
+ export default Mapping(Carousel(Component));