@easyv/charts 1.1.8 → 1.2.2

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