@easyv/charts 1.3.0 → 1.3.3

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