@easyv/charts 1.10.25 → 1.10.26

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.
@@ -1,1629 +1,1629 @@
1
- /**
2
- * 饼环图
3
- */
4
- import React, {
5
- memo,
6
- useMemo,
7
- useCallback,
8
- useRef,
9
- useState,
10
- useEffect,
11
- useContext,
12
- useLayoutEffect,
13
- Fragment,
14
- } from "react";
15
- import {
16
- ChartContainer,
17
- Carousel,
18
- Legend,
19
- ConicalGradient,
20
- Mapping,
21
- TextOverflow,
22
- } from ".";
23
- import { chartContext } from "../context";
24
- import {
25
- getFontStyle,
26
- sortPie,
27
- getDataWithPercent,
28
- getColorList,
29
- } from "../utils";
30
- import { pie, arc, extent, scaleLinear } from "d3v7";
31
- import { animate, linear } from "popmotion";
32
- import LinearGradient from "./LinearGradient";
33
- import { pieLegendFormatter as legendFormatter } from "../formatter";
34
- import ringCss from "../css/piechart.module.css";
35
- import { useAiDataOfPie } from "../hooks";
36
- import { PieTooltip } from "./PieTooltip";
37
- import { getPieAdaptiveMarginPreset } from "../utils/legendPlacement";
38
-
39
- const PI = Math.PI;
40
-
41
- const PIE_ADAPTIVE_MARGINS = {
42
- right: { marginTop: 24, marginBottom: 24, marginLeft: 0, marginRight: 200 },
43
- left: { marginTop: 24, marginBottom: 24, marginLeft: 200, marginRight: 0 },
44
- top: { marginTop: 110, marginBottom: 24, marginLeft: 24, marginRight: 24 },
45
- bottom: { marginTop: 24, marginBottom: 110, marginLeft: 24, marginRight: 24 },
46
- hidden: { marginTop: 24, marginBottom: 24, marginLeft: 24, marginRight: 24 },
47
- };
48
-
49
- const getPieAdaptiveMargin = (show, alignment) =>
50
- getPieAdaptiveMarginPreset(show, alignment, PIE_ADAPTIVE_MARGINS);
51
-
52
- const defaultChart = {
53
- outerRadius: 1,
54
- innerRadius: 0,
55
- cornerRadius: 0,
56
- rose: false,
57
- roseType: "radius",
58
- baseRadius: 0,
59
- padAngle: 0,
60
- };
61
- const defaultAngle = { startAngle: 0, endAngle: 360, antiClockwise: false };
62
-
63
- // const nameDy = (showValue, showPercent, mode, dir) => {
64
- // if (showValue || showPercent) {
65
- // if (mode == "vertical") {
66
- // return dir == 1 ? "1.1em" : "-2.6em";
67
- // } else {
68
- // return 0;
69
- // }
70
- // } else {
71
- // if (mode == "vertical") {
72
- // return dir * 1.1 + "em";
73
- // } else {
74
- // return 0;
75
- // }
76
- // }
77
- // };
78
- // const valueDy = (value1, mode, dir) => {
79
- // if (value1) {
80
- // if (mode == "vertical") {
81
- // return "1.5em";
82
- // } else {
83
- // return 0;
84
- // }
85
- // } else {
86
- // if (mode == "vertical") {
87
- // return dir == 1 ? "1.1em" : "-1.1em";
88
- // } else {
89
- // return 0;
90
- // }
91
- // }
92
- // };
93
-
94
- // const percentDy_ = (showName, showValue, mode, dir) => {
95
- // if (showValue) {
96
- // return 0;
97
- // }
98
- // if (showName) {
99
- // if (mode == "vertical") {
100
- // return "1.5em";
101
- // } else {
102
- // return 0;
103
- // }
104
- // } else {
105
- // if (mode == "vertical") {
106
- // return dir * 1.1 + "em";
107
- // } else {
108
- // return 0;
109
- // }
110
- // }
111
- // };
112
-
113
- // const percentX = (showName, showValue, mode, x) => {
114
- // if (showValue) {
115
- // return "";
116
- // }
117
- // if (showName) {
118
- // if (mode == "vertical") {
119
- // return x;
120
- // } else {
121
- // return "";
122
- // }
123
- // } else {
124
- // return x;
125
- // }
126
- // };
127
-
128
- // const percentDx = (showName, showValue, mode) => {
129
- // if (showValue) {
130
- // return "0.5em";
131
- // }
132
- // if (showName) {
133
- // if (mode == "vertical") {
134
- // return 0;
135
- // } else {
136
- // return "0.5em";
137
- // }
138
- // } else {
139
- // return 0;
140
- // }
141
- // };
142
-
143
- // const percentDy = (showName, showValue, mode) => {
144
- // if (showValue) {
145
- // return 0;
146
- // }
147
- // if (showName) {
148
- // if (mode == "vertical") {
149
- // return "1.5em";
150
- // } else {
151
- // return 0;
152
- // }
153
- // } else {
154
- // return 0;
155
- // }
156
- // };
157
-
158
- // const valueDx = (showName, mode) => {
159
- // if (!showName) {
160
- // return "";
161
- // }
162
- // if (mode == "vertical") {
163
- // return "";
164
- // } else {
165
- // return "0.5em";
166
- // }
167
- // };
168
-
169
- const getCoord = (deg, radius) => {
170
- var x = Math.cos(deg) * radius,
171
- y = Math.sin(deg) * radius;
172
- return [x, y];
173
- };
174
-
175
- const getRoseRadius = ({ innerRadius, baseRadius }) =>
176
- innerRadius + (1 - innerRadius) * baseRadius;
177
-
178
- const getAngle = ({ startAngle, endAngle, antiClockwise, ...rest }) => {
179
- if (antiClockwise)
180
- return {
181
- ...rest,
182
- startAngle: endAngle - 180,
183
- endAngle: startAngle - 180,
184
- };
185
- return { ...rest, startAngle, endAngle };
186
- };
187
-
188
- const getArc = (
189
- radius,
190
- {
191
- padAngle = 0,
192
- innerRadius = 0,
193
- outerRadius = 1,
194
- cornerRadius = 0,
195
- startAngle = 0,
196
- endAngle = 360,
197
- ...rest
198
- },
199
- series_,
200
- index,
201
- ) => {
202
- const series =
203
- series_.find((s) => s.fieldName == rest.data.s) ||
204
- series_[index % series_.length];
205
- return {
206
- ...rest,
207
- type: "pie",
208
- fieldName: series.fieldName,
209
- displayName: series.displayName || rest.data.s,
210
- series: series,
211
- innerRadius: innerRadius * radius,
212
- outerRadius: outerRadius * radius,
213
- arc: arc()
214
- .innerRadius(innerRadius * radius)
215
- .outerRadius(outerRadius * radius)
216
- .cornerRadius(cornerRadius)
217
- .startAngle(startAngle)
218
- .endAngle(endAngle)
219
- .padAngle(padAngle),
220
- };
221
- };
222
-
223
- const getCircleScale = ({ count, color, width, length } = tick, radius) => {
224
- let data = [],
225
- arcs = [],
226
- centroids = [];
227
- for (let i = 0; i < count; i++) {
228
- data.push(1);
229
- }
230
- let scaleData = pie()(data);
231
- scaleData.map((data) => {
232
- let _arc = arc()
233
- .innerRadius(radius + length / 2)
234
- .outerRadius(radius + length / 2)
235
- .startAngle(data.startAngle)
236
- .endAngle(data.endAngle);
237
- arcs.push(_arc());
238
- centroids.push(_arc.centroid());
239
- });
240
- return (
241
- <g>
242
- {centroids.map((center, index) => {
243
- let x = center[0],
244
- y = center[1];
245
- let rate = length / Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
246
- return (
247
- <path
248
- key={index}
249
- d={`M${x},${y}l${x * rate},${y * rate}`}
250
- strokeWidth={width}
251
- stroke={color}
252
- />
253
- );
254
- })}
255
- </g>
256
- );
257
- };
258
-
259
- const Component = memo(
260
- ({
261
- width,
262
- height,
263
- config: {
264
- chart: {
265
- label,
266
- legend: { formatter = legendFormatter, ...legend },
267
- animation: { ringDuration, labelDuration } = {},
268
- margin: { marginLeft, marginTop, marginRight, marginBottom },
269
- },
270
- fan: {
271
- chart = defaultChart,
272
- chart: { outerRadius = defaultChart.outerRadius, padAngle },
273
- angle = defaultAngle,
274
- stroke: { show, strokeWidth, color } = { show: false },
275
- decorate,
276
- decorate2,
277
- categoryText,
278
- outerDecorate,
279
- current,
280
- } = {},
281
- order,
282
- columnsSeries,
283
- series,
284
- animation: {
285
- on,
286
- current: {
287
- widthen = 0,
288
- heighten = 0,
289
- opacity = 0,
290
- width: radiusWidthAdd = 0,
291
- color: animateColor,
292
- textStyle: animateCTS,
293
- gap = 0,
294
- },
295
- rotate = 0,
296
- },
297
- tooltip = {},
298
- },
299
- config,
300
- state: { currentIndex, trigger },
301
- onEvent,
302
- hoverEvent,
303
- data: originData = [],
304
- }) => {
305
- const data = originData.map((d) => ({ ...d, y: d.y < 0 ? 0 : d.y }));
306
- const prevIndex = useRef(null);
307
- const { precision: legendPrecision } = legend.config.percent;
308
- const {
309
- id,
310
- isIOS,
311
- width: chartWidth,
312
- height: chartHeight,
313
- triggerOnRelative,
314
- onEmit,
315
- updateConfig,
316
- } = useContext(chartContext);
317
- const { show: legendShow, name: { layoutMode: legendLayoutMode } = {} } =
318
- legend.config;
319
- const legendAlignment = legend.config?.layout?.alignment;
320
- const prevLegendLayoutKey = useRef(null);
321
- useEffect(() => {
322
- if (legendLayoutMode !== "Adaptive") {
323
- prevLegendLayoutKey.current = null;
324
- return;
325
- }
326
- if (!updateConfig) return;
327
-
328
- const layoutKey = `${legendShow}-${legendAlignment}`;
329
- const prev = prevLegendLayoutKey.current;
330
- prevLegendLayoutKey.current = layoutKey;
331
-
332
- // 挂载/刷新:只记录当前图例布局,margin 始终读配置项
333
- if (prev === null) return;
334
- if (prev === layoutKey) return;
335
-
336
- const target = getPieAdaptiveMargin(legendShow, legendAlignment);
337
- updateConfig({
338
- id,
339
- type: "config",
340
- payload: [
341
- {
342
- path: ["chart", "margin", "marginTop"],
343
- field: "value",
344
- value: target.marginTop,
345
- },
346
- {
347
- path: ["chart", "margin", "marginBottom"],
348
- field: "value",
349
- value: target.marginBottom,
350
- },
351
- {
352
- path: ["chart", "margin", "marginLeft"],
353
- field: "value",
354
- value: target.marginLeft,
355
- },
356
- {
357
- path: ["chart", "margin", "marginRight"],
358
- field: "value",
359
- value: target.marginRight,
360
- },
361
- ],
362
- });
363
- }, [id, updateConfig, legendLayoutMode, legendShow, legendAlignment]);
364
- const [y, setY] = useState(1);
365
- const radius = (Math.min(chartWidth, chartHeight) / 2) * outerRadius;
366
-
367
- const arcsFunc = useMemo(() => {
368
- const { startAngle = 0, endAngle = 360 } = getAngle(angle);
369
- const arcsFunc = pie()
370
- .startAngle((startAngle * PI) / 180)
371
- .endAngle((endAngle * PI) / 180)
372
- .value((d) => d.y);
373
- return arcsFunc;
374
- }, [angle]);
375
- //此处创建arcsFuncTwo的原因是为了兼容数据全为零
376
- const arcsFuncTwo = useMemo(() => {
377
- const { startAngle = 0, endAngle = 360 } = getAngle(angle);
378
- const arcsFunc = pie()
379
- .startAngle((startAngle * PI) / 180)
380
- .endAngle((endAngle * PI) / 180)
381
- .value((d) => (d.y == 0 ? 1 : d.y));
382
- return arcsFunc;
383
- }, [angle]);
384
- let judgeData = 0; //此处声明全局变量是为了父子组件传递值来判断数据是否都为零
385
- const arcs = useMemo(() => {
386
- const _chart = Object.assign(defaultChart, chart);
387
- const {
388
- innerRadius,
389
- outerRadius,
390
- rose,
391
- cornerRadius,
392
- padAngle,
393
- roseType,
394
- } = _chart;
395
- const _padAngle = (padAngle * Math.PI) / 180;
396
-
397
- switch (order) {
398
- case "":
399
- arcsFunc.sort(null);
400
- break;
401
- case "desc":
402
- arcsFunc.sort((a, b) => b.y - a.y);
403
- break;
404
- case "asc":
405
- arcsFunc.sort((a, b) => a.y - b.y);
406
- break;
407
- }
408
-
409
- //此处判断data中的y是否都为零,方便饼图都为零时展示
410
-
411
- let arcs = 0;
412
- data.forEach(function (item) {
413
- judgeData += item.y;
414
- });
415
- if (judgeData == 0) {
416
- arcs = arcsFuncTwo(data);
417
- } else {
418
- arcs = arcsFunc(data);
419
- }
420
-
421
- //const arcs = arcsFunc(data); 此处是原本的传输饼图data流程
422
- const legendDataWithPercent = getDataWithPercent(arcs, legendPrecision);
423
- const _legendDataWithPercent = sortPie(legendDataWithPercent, order);
424
-
425
- if (rose) {
426
- const domain = extent(_legendDataWithPercent, (d) => d.value);
427
- const roseRadius = getRoseRadius(_chart);
428
- const scaler = scaleLinear().domain(domain).range([roseRadius, 1]);
429
-
430
- const angle = (PI * 2) / _legendDataWithPercent.length;
431
- return _legendDataWithPercent.map(
432
- ({ startAngle, endAngle, ...arc }, index) => ({
433
- ...arc,
434
- id: id + "_linear_" + index,
435
- startAngle: roseType == "area" ? angle * index : startAngle,
436
- endAngle: roseType == "area" ? angle * (index + 1) : endAngle,
437
- cornerRadius,
438
- padAngle: _padAngle,
439
- innerRadius,
440
- outerRadius: scaler(arc.value),
441
- }),
442
- );
443
- }
444
- return _legendDataWithPercent.map((arc, index) => ({
445
- ...arc,
446
- id: id + "_linear_" + index,
447
- cornerRadius,
448
- padAngle: _padAngle,
449
- innerRadius,
450
- outerRadius,
451
- }));
452
- }, [data, arcsFunc, arcsFuncTwo, chart, legendPrecision, order]);
453
-
454
- const _arcs = useMemo(() => {
455
- const seriesLength = series.size;
456
- if (!seriesLength) return [];
457
- const _series = [...series.values()];
458
- if (_series.length < arcs.length)
459
- console.warn("请检查数据中是否存在相同的s");
460
- return arcs.map((arc, index) => getArc(radius, arc, _series, index));
461
- }, [series, arcs, radius]);
462
-
463
- const onClick = useCallback(
464
- (e) => {
465
- const _data = arcs[+e.currentTarget.dataset.index].data;
466
- triggerOnRelative("onClick", _data);
467
- onEmit("onClick", _data);
468
- onEvent({
469
- currentIndex: +e.currentTarget.dataset.index,
470
- type: "onClick",
471
- });
472
- },
473
- [onEvent],
474
- );
475
-
476
- const onMouseEnter = useCallback(
477
- (e) => {
478
- const _data = arcs[+e.currentTarget.dataset.index].data;
479
- triggerOnRelative("mousehover", _data);
480
- onEmit("mousehover", _data);
481
- onEvent({
482
- currentIndex: +e.currentTarget.dataset.index,
483
- type: "onMouseEnter",
484
- });
485
- },
486
- [onEvent, triggerOnRelative, onEmit],
487
- );
488
-
489
- const onMouseLeave = useCallback(
490
- (e) => {
491
- setMousePos({
492
- x: 0,
493
- y: 0,
494
- });
495
- onEvent({
496
- currentIndex: +e.currentTarget.dataset.index,
497
- type: "onMouseLeave",
498
- });
499
- },
500
- [onEvent],
501
- );
502
-
503
- useLayoutEffect(() => {
504
- let animation;
505
- if (!!on) {
506
- animation = animate({
507
- from: 0,
508
- to: 1,
509
- duration: 500,
510
- ease: linear,
511
- onPlay: () => {},
512
- onUpdate: (v) => {
513
- setY(v);
514
- },
515
- onComplete: () => {
516
- const _data = arcs[+currentIndex] ? arcs[+currentIndex].data : {};
517
- triggerOnRelative("carousel", _data);
518
- onEmit("carousel", _data);
519
- },
520
- });
521
- } else {
522
- if (currentIndex !== null && trigger === "onClick") {
523
- const _data = arcs[+currentIndex] ? arcs[+currentIndex].data : {};
524
- triggerOnRelative("click", _data);
525
- onEmit("click", _data);
526
- }
527
- }
528
- return () => {
529
- prevIndex.current = currentIndex;
530
- animation && animation.stop();
531
- animation = null;
532
- };
533
- }, [
534
- JSON.stringify(arcs),
535
- on,
536
- currentIndex,
537
- trigger,
538
- onEmit,
539
- triggerOnRelative,
540
- ]);
541
- const aiData = useAiDataOfPie(_arcs, legend);
542
- useEffect(() => {
543
- if (aiData.length) {
544
- if (!window.aiData) {
545
- window.aiData = {};
546
- }
547
- window.aiData[id] = {
548
- getAI: () => {
549
- return aiData;
550
- },
551
- };
552
- }
553
- return () => {
554
- window.aiData && window.aiData[id] && delete window.aiData[id];
555
- };
556
- }, [JSON.stringify(aiData), id]);
557
-
558
- const halfChartWidth = chartWidth / 2;
559
- const halfChartHeight = chartHeight / 2;
560
-
561
- const rotate_ = decorate2
562
- ? (-(arcs[+currentIndex].startAngle + arcs[+currentIndex].endAngle) *
563
- 90) /
564
- Math.PI +
565
- rotate
566
- : 0;
567
- let maxRadius = 0;
568
- _arcs.map((d) => {
569
- maxRadius = Math.max(maxRadius, d.outerRadius);
570
- });
571
- let centerRadius = 0.5 * maxRadius + 0.5 * _arcs[0].innerRadius;
572
- const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
573
- const [hoverData, setHoverData] = useState(null);
574
- const pieWarpEl = useRef(null);
575
- const domRef = useRef();
576
- return outerDecorate ? (
577
- <div ref={domRef}>
578
- <ChartContainer //用于生成甜甜圈图,判断依据是外环装饰这个配置项(outerDecorate)
579
- width={width}
580
- height={height}
581
- marginLeft={marginLeft}
582
- marginTop={marginTop}
583
- ref={pieWarpEl}
584
- >
585
- <g
586
- style={{
587
- "--labelDuration": labelDuration + "ms",
588
- "--ringDuration": ringDuration + "ms",
589
- transition: "transform ease-in-out 0.3s",
590
- transform:
591
- "translate(" +
592
- halfChartWidth +
593
- "px, " +
594
- halfChartHeight +
595
- "px) rotate(" +
596
- rotate_ +
597
- "deg)",
598
- }}
599
- >
600
- {
601
- //用于生成外环装饰的刻度
602
- outerDecorate.tick.show &&
603
- getCircleScale(outerDecorate.tick, maxRadius)
604
- }
605
- <circle //外环装饰
606
- cx="0"
607
- cy="0"
608
- r={maxRadius + 2}
609
- fill="none"
610
- stroke={outerDecorate.color}
611
- strokeWidth={outerDecorate.width}
612
- />
613
- {_arcs.map(
614
- ({ id, value, series, arc, innerRadius, outerRadius }, index) => {
615
- const arcWidth = outerRadius - innerRadius;
616
- const path = arc
617
- .innerRadius(centerRadius)
618
- .outerRadius(centerRadius)(value);
619
- const dashLength = Math.ceil(
620
- (Math.PI * centerRadius * 2) / _arcs.length,
621
- );
622
- const pie = getColorList(series.color);
623
- return (
624
- <Fragment key={index}>
625
- <path
626
- className={ringCss["inner-arc"]}
627
- style={{
628
- strokeDasharray: `${dashLength},${2 * dashLength}`,
629
- strokeDashoffset: dashLength,
630
- animationDelay: `${index * ringDuration}ms`,
631
- }}
632
- data-index={index}
633
- onClick={onClick}
634
- onMouseEnter={onMouseEnter}
635
- onMouseLeave={onMouseLeave}
636
- onMouseMove={(e) => {
637
- const _data = arcs[+e.currentTarget.dataset.index];
638
- const warpBoxPos = {
639
- x: pieWarpEl.current.getBoundingClientRect().x,
640
- y: pieWarpEl.current.getBoundingClientRect().y,
641
- };
642
- setMousePos({
643
- x: e.clientX - warpBoxPos.x,
644
- y: e.clientY - warpBoxPos.y,
645
- });
646
- setHoverData(_data);
647
- }}
648
- d={path.split("L")[0]}
649
- stroke={"url(#" + id + ")"}
650
- strokeWidth={arcWidth}
651
- fill="none"
652
- />
653
- <defs>
654
- <LinearGradient
655
- id={id}
656
- colors={pie}
657
- rotate={series.color.linear.angle + 180}
658
- // gradientUnits='objectBoundingBox'
659
- />
660
- </defs>
661
- </Fragment>
662
- );
663
- },
664
- )}
665
- {label && (
666
- <RingLabel
667
- config={{
668
- ...label,
669
- maxRadius: maxRadius + 2,
670
- ringDuration,
671
- labelDuration,
672
- }}
673
- iosStyle={{
674
- isIOS,
675
- left: halfChartWidth + marginLeft,
676
- top: halfChartHeight + marginTop,
677
- }}
678
- arcs={_arcs}
679
- judge={judgeData}
680
- />
681
- )}
682
- </g>
683
- </ChartContainer>
684
-
685
- <Legend
686
- {...legend}
687
- height={chartHeight}
688
- componentWidth={width}
689
- marginLeft={marginLeft}
690
- marginRight={marginRight}
691
- isPieChart
692
- columnsSeries={columnsSeries}
693
- data={data}
694
- series={_arcs.map((arc) => ({
695
- ...arc,
696
- percent: arc.percent.toFixed(legendPrecision),
697
- }))}
698
- pieClick={onClick}
699
- formatter={formatter}
700
- judge={judgeData}
701
- />
702
- {tooltip &&
703
- mousePos &&
704
- mousePos.x != 0 &&
705
- mousePos.y != 0 &&
706
- tooltip.manual && (
707
- <div
708
- style={{
709
- position: "absolute",
710
- pointerEvents: "none",
711
- }}
712
- >
713
- <PieTooltip
714
- series={series}
715
- domRef={domRef}
716
- data={hoverData}
717
- config={tooltip}
718
- pieCenter={{
719
- x: halfChartWidth,
720
- y: maxRadius + marginTop,
721
- }}
722
- mousePos={mousePos}
723
- />
724
- </div>
725
- )}
726
- </div>
727
- ) : (
728
- <div ref={domRef}>
729
- <ChartContainer
730
- width={width}
731
- height={height}
732
- marginLeft={marginLeft}
733
- marginTop={marginTop}
734
- onMouseEnter={() => {
735
- hoverEvent(true);
736
- }}
737
- onMouseLeave={() => {
738
- hoverEvent(false);
739
- }}
740
- ref={pieWarpEl}
741
- >
742
- <g
743
- style={{
744
- transition: "transform ease-in-out 0.3s",
745
- transform:
746
- "translate(" +
747
- halfChartWidth +
748
- "px, " +
749
- halfChartHeight +
750
- "px) rotate(" +
751
- rotate_ +
752
- "deg)",
753
- }}
754
- >
755
- {_arcs.map(
756
- (
757
- {
758
- id,
759
- value,
760
- series,
761
- arc,
762
- innerRadius,
763
- outerRadius,
764
- index: dataIndex,
765
- },
766
- index,
767
- ) => {
768
- const current = index == currentIndex;
769
- const prev = index == prevIndex.current;
770
- const offset = current ? y : prev ? 1 - y : 0;
771
-
772
- const fillOpacity = animateColor
773
- ? 1
774
- : current
775
- ? opacity / 100
776
- : 1;
777
- const deltaWidthen = offset * widthen;
778
- const deltaHeighten = offset * heighten;
779
- const path = arc
780
- .innerRadius(innerRadius + deltaWidthen)
781
- .outerRadius(outerRadius + deltaHeighten + deltaWidthen)(
782
- value,
783
- );
784
- const pie = getColorList(series.color);
785
- const currentPie = animateColor
786
- ? getColorList(animateColor)
787
- : getColorList(series.color);
788
- let textPath = "",
789
- categoryTextStyle = {};
790
- if (categoryText && categoryText.show) {
791
- //如果有类目文本,则需要计算文字路径
792
- //let offsetWidth=decorate2.radiusWidth/2 + radiusWidthAdd/2; //当前文字需生成在装饰物内,故而半径需要减小
793
- let textArc = arc
794
- .innerRadius(
795
- outerRadius + (current ? gap : categoryText.gap),
796
- )
797
- .outerRadius(
798
- outerRadius + (current ? gap : categoryText.gap),
799
- )(value);
800
- let lastA = textArc.lastIndexOf("A");
801
- textPath = textArc.slice(
802
- 0,
803
- lastA > 0 ? lastA : textArc.length,
804
- ); //文字路径
805
- categoryTextStyle = current
806
- ? animateCTS
807
- : categoryText.textStyle; //这里把textstyle拿出来
808
- }
809
-
810
- return (
811
- <Fragment key={index}>
812
- <path
813
- data-index={index}
814
- onClick={onClick}
815
- onMouseEnter={onMouseEnter}
816
- onMouseLeave={onMouseLeave}
817
- onMouseMove={(e) => {
818
- const _data = arcs[+e.currentTarget.dataset.index];
819
- const warpBoxPos = {
820
- x: pieWarpEl.current.getBoundingClientRect().x,
821
- y: pieWarpEl.current.getBoundingClientRect().y,
822
- };
823
- setMousePos({
824
- x: e.clientX - warpBoxPos.x,
825
- y: e.clientY - warpBoxPos.y,
826
- });
827
- setHoverData(_data);
828
- }}
829
- d={path}
830
- stroke={show ? color : "none"}
831
- strokeWidth={show ? strokeWidth : "0"}
832
- fill={"url(#" + id + ")"}
833
- fillOpacity={fillOpacity}
834
- />
835
- {
836
- //装饰物2,产生于每个弧的外部
837
- decorate2 && decorate2.show && (
838
- <path
839
- data-index={index}
840
- onClick={onClick}
841
- onMouseEnter={onMouseEnter}
842
- onMouseLeave={onMouseLeave}
843
- d={arc
844
- .innerRadius(outerRadius)
845
- .outerRadius(
846
- outerRadius +
847
- decorate2.radiusWidth +
848
- (current ? radiusWidthAdd : 0),
849
- )(value)}
850
- stroke={show ? color : "none"}
851
- strokeWidth={show ? strokeWidth : "0"}
852
- fill={"url(#" + id + ")"}
853
- fillOpacity={decorate2.opacity / 100}
854
- />
855
- )
856
- }
857
- {
858
- //类目文本
859
- value && categoryText && categoryText.show && (
860
- <g>
861
- <path
862
- onClick={onClick}
863
- onMouseEnter={onMouseEnter}
864
- onMouseLeave={onMouseLeave}
865
- id={id + "_text_" + index}
866
- d={textPath}
867
- fill="none"
868
- stroke="none"
869
- />
870
- <text
871
- textAnchor="middle"
872
- style={{
873
- ...categoryTextStyle,
874
- fontWeight: categoryTextStyle.bold
875
- ? "bold"
876
- : "normal",
877
- fontStyle: categoryTextStyle.italic
878
- ? "italic"
879
- : "normal",
880
- pointerEvents: "none",
881
- }}
882
- fill={categoryTextStyle.color}
883
- >
884
- <textPath
885
- startOffset="50%"
886
- href={"#" + id + "_text_" + index}
887
- >
888
- {_arcs[index].displayName ||
889
- _arcs[index].fieldName}
890
- </textPath>
891
- </text>
892
- </g>
893
- )
894
- }
895
- <defs>
896
- {/* 此处是环的发生地 */}
897
- <LinearGradient
898
- id={id}
899
- colors={current ? currentPie : pie}
900
- rotate={
901
- current
902
- ? animateColor
903
- ? animateColor.linear.angle + 180
904
- : series.color.linear.angle + 180
905
- : series.color.linear.angle + 180
906
- }
907
- // gradientUnits='objectBoundingBox'
908
- />
909
- </defs>
910
- </Fragment>
911
- );
912
- },
913
- )}
914
- {label && (
915
- <Label
916
- config={label}
917
- iosStyle={{
918
- isIOS,
919
- left: halfChartWidth + marginLeft,
920
- top: halfChartHeight + marginTop,
921
- }}
922
- arcs={_arcs}
923
- judge={judgeData}
924
- />
925
- )}
926
- {current && (
927
- <g
928
- fillOpacity={y}
929
- style={{ transform: "rotate(" + -rotate_ + "deg)" }}
930
- >
931
- <Current
932
- config={current}
933
- width={chartWidth}
934
- height={chartHeight}
935
- iosStyle={{
936
- marginLeft,
937
- marginTop,
938
- isIOS,
939
- }}
940
- data={_arcs}
941
- judge={judgeData}
942
- currentIndex={+currentIndex}
943
- />
944
- </g>
945
- )}
946
- </g>
947
- </ChartContainer>
948
- {decorate && (
949
- <ConicalGradient
950
- width={width}
951
- height={height}
952
- centerX={halfChartWidth + marginLeft}
953
- centerY={halfChartHeight + marginTop}
954
- config={decorate}
955
- arcs={_arcs}
956
- radius={radius}
957
- />
958
- )}
959
-
960
- <Legend
961
- {...legend}
962
- height={chartHeight}
963
- componentWidth={width}
964
- marginLeft={marginLeft}
965
- marginRight={marginRight}
966
- isPieChart
967
- data={data}
968
- columnsSeries={columnsSeries}
969
- series={_arcs.map((arc) => ({
970
- ...arc,
971
- percent: arc.percent.toFixed(legendPrecision),
972
- }))}
973
- pieClick={onClick}
974
- formatter={formatter}
975
- judge={judgeData}
976
- />
977
- {tooltip &&
978
- mousePos &&
979
- mousePos.x != 0 &&
980
- mousePos.y != 0 &&
981
- tooltip.manual && (
982
- <div
983
- style={{
984
- position: "absolute",
985
- pointerEvents: "none",
986
- }}
987
- >
988
- <PieTooltip
989
- series={series}
990
- domRef={domRef}
991
- data={hoverData}
992
- config={tooltip}
993
- pieCenter={{
994
- x: halfChartWidth,
995
- y: maxRadius + marginTop,
996
- }}
997
- mousePos={mousePos}
998
- />
999
- </div>
1000
- )}
1001
- </div>
1002
- );
1003
- },
1004
- );
1005
-
1006
- const Current = ({
1007
- config: {
1008
- show,
1009
- gap,
1010
- name: {
1011
- show: showName,
1012
- sameColor: nameColor,
1013
- font: nameFont,
1014
- translate = { x: 0, y: 0 },
1015
- maxWidth,
1016
- textOverflow,
1017
- speed,
1018
- },
1019
- percent: {
1020
- show: showPercent,
1021
- sameColor: percentColor,
1022
- font: percentFont,
1023
- precision,
1024
- translate: { x: translatePercentX, y: translatePercentY },
1025
- },
1026
- value: {
1027
- show: showValue,
1028
- sameColor: valueColor,
1029
- font: valueFont,
1030
- translate: { x: translateValueX, y: translateValueY },
1031
- suffix: {
1032
- show: showSuffix,
1033
- fontSize,
1034
- text,
1035
- translate: { x: translateSuffixX, y: translateSuffixY },
1036
- },
1037
- },
1038
- },
1039
- iosStyle: { isIOS, marginLeft, marginTop },
1040
- width,
1041
- height,
1042
- data,
1043
- judge,
1044
- currentIndex,
1045
- }) => {
1046
- const _data = useMemo(() => {
1047
- const legendDataWithPercent = getDataWithPercent(data, precision);
1048
- return sortPie(legendDataWithPercent, "");
1049
- }, [data, precision]);
1050
-
1051
- //数据容错,当data都为零那么需要进行以下容错
1052
- if (judge == 0) {
1053
- _data.forEach((d) => {
1054
- ((d.percent = 0), (d.value = 0));
1055
- });
1056
- }
1057
-
1058
- const currentData = _data[currentIndex];
1059
-
1060
- if (!currentData) return null;
1061
-
1062
- const { displayName, fieldName, value, percent } = currentData;
1063
- let nameTemp = displayName ? displayName : fieldName; //类目名
1064
-
1065
- let foreignStyle = {
1066
- //foreignObject标签样式,
1067
- width,
1068
- height,
1069
- position: "relative",
1070
- overflow: "visible",
1071
- pointerEvents: "none",
1072
- },
1073
- boxStyle = {
1074
- //弹性盒子样式,用于当前值的上下居中对齐等
1075
- width,
1076
- height,
1077
- position: "absolute",
1078
- display: "flex",
1079
- flexDirection: "column",
1080
- justifyContent: "center",
1081
- alignItems: "center",
1082
- transform: isIOS
1083
- ? `translate(${marginLeft}px,${marginTop}px)`
1084
- : `translate(-${width / 2}px,-${height / 2}px)`,
1085
- };
1086
- let seriesColor = currentData.series.color;
1087
- seriesColor =
1088
- seriesColor.type == "pure"
1089
- ? seriesColor.pure
1090
- : seriesColor.linear.stops[0].color;
1091
- return (
1092
- show && (
1093
- <foreignObject style={foreignStyle}>
1094
- <div style={boxStyle}>
1095
- {showName && (
1096
- <TextOverflow
1097
- type={textOverflow}
1098
- value={nameTemp}
1099
- speed={speed}
1100
- style={{
1101
- width: "100%",
1102
- maxWidth,
1103
- textAlign: "center",
1104
- display: textOverflow == "marquee" ? "flex" : "bolck",
1105
- justifyContent: "center",
1106
- ...getFontStyle(nameFont),
1107
- margin: gap / 2 + "px 0",
1108
- color: nameColor ? seriesColor : nameFont.color,
1109
- transform: `translate(${translate.x}px,${translate.y}px)`,
1110
- }}
1111
- ></TextOverflow>
1112
- )}
1113
- {
1114
- //真实值
1115
- showValue && (
1116
- <span
1117
- style={{
1118
- ...getFontStyle(valueFont),
1119
- transform:
1120
- "translate(" +
1121
- translateValueX +
1122
- "px," +
1123
- translateValueY +
1124
- "px)",
1125
- margin: gap / 2 + "px 0",
1126
- color: valueColor ? seriesColor : valueFont.color,
1127
- }}
1128
- >
1129
- {value}
1130
- {showSuffix && text && (
1131
- <span
1132
- style={{
1133
- display: "inline-block",
1134
- transform:
1135
- "translate(" +
1136
- translateSuffixX +
1137
- "px," +
1138
- translateSuffixY +
1139
- "px)",
1140
- fontSize: fontSize,
1141
- }}
1142
- >
1143
- {text}
1144
- </span>
1145
- )}
1146
- </span>
1147
- )
1148
- }
1149
- {
1150
- //百分比值
1151
- showPercent && (
1152
- <span
1153
- style={{
1154
- transform:
1155
- "translate(" +
1156
- translatePercentX +
1157
- "px," +
1158
- translatePercentY +
1159
- "px)",
1160
- ...getFontStyle(percentFont),
1161
- margin: gap / 2 + "px 0",
1162
- color: percentColor ? seriesColor : percentFont.color,
1163
- }}
1164
- >
1165
- {percent + "%"}
1166
- </span>
1167
- )
1168
- }
1169
- </div>
1170
- </foreignObject>
1171
- )
1172
- );
1173
- };
1174
-
1175
- const Label = ({
1176
- config: {
1177
- maxRadius = 0,
1178
- lineLength,
1179
- lineColor,
1180
- distance,
1181
- mode,
1182
- align,
1183
- show,
1184
- translate: { x: translateX, y: translateY },
1185
- name: {
1186
- show: showName,
1187
- font: nameFont,
1188
- maxWidth,
1189
- textOverflow,
1190
- speed,
1191
- translate: NameTranslate,
1192
- },
1193
- value: {
1194
- show: showValue,
1195
- font: valueFont,
1196
- sameColor: valueSameColor,
1197
- suffix: {
1198
- show: showSuffix,
1199
- text,
1200
- fontSize: suffixFontSize,
1201
- translate: { x: suffixTranslateX, y: suffixTranslateY },
1202
- },
1203
- translate: ValueTranslate,
1204
- },
1205
- percent: {
1206
- show: showPercent,
1207
- sameColor: percentSameColor,
1208
- font: percentFont,
1209
- precision,
1210
- translate: PercentTranslate,
1211
- },
1212
- },
1213
- iosStyle: { isIOS, left, top },
1214
- arcs,
1215
- judge,
1216
- animation,
1217
- }) => {
1218
- const _arcs = useMemo(
1219
- () => getDataWithPercent(arcs, precision),
1220
- [arcs, precision],
1221
- );
1222
- //数据做出容错
1223
- if (judge == 0) {
1224
- _arcs.forEach((d) => {
1225
- d.percent = 0;
1226
- });
1227
- }
1228
- return (
1229
- <g>
1230
- {_arcs.map(
1231
- (
1232
- {
1233
- series: {
1234
- color: {
1235
- type,
1236
- pure,
1237
- linear: { stops },
1238
- },
1239
- },
1240
- data,
1241
- displayName,
1242
- value,
1243
- percent,
1244
- arc,
1245
- outerRadius,
1246
- index: actualIndex,
1247
- },
1248
- index,
1249
- ) => {
1250
- const [x, y] = arc.centroid();
1251
- const midAngle = Math.atan2(y, x);
1252
-
1253
- const [x1, y1] = getCoord(
1254
- midAngle,
1255
- maxRadius ? maxRadius : outerRadius,
1256
- );
1257
-
1258
- const radius = (maxRadius ? maxRadius : outerRadius) + distance;
1259
- const [x2, y2] = getCoord(midAngle, radius);
1260
-
1261
- const direction = x2 < 0 ? -1 : 1;
1262
- const x3 = x2 + lineLength * direction;
1263
-
1264
- const _x = x3 + (translateX + 6) * direction;
1265
-
1266
- const _showName = showName && displayName;
1267
- const _showValue = showValue && (value || showSuffix);
1268
- const nameStyle = getFontStyle(nameFont);
1269
- return (
1270
- show &&
1271
- (_showName || showPercent || showValue) && (
1272
- <g key={index}>
1273
- <path
1274
- className={animation ? ringCss["label-line"] : ""}
1275
- style={{
1276
- animationDelay: `${
1277
- animation
1278
- ? (actualIndex + 1) * ringDuration - labelDuration
1279
- : 0
1280
- }ms`,
1281
- }}
1282
- d={
1283
- "M" +
1284
- x1 +
1285
- ", " +
1286
- y1 +
1287
- "L" +
1288
- x2 +
1289
- ", " +
1290
- y2 +
1291
- "L" +
1292
- x3 +
1293
- ", " +
1294
- y2
1295
- }
1296
- stroke={
1297
- lineColor
1298
- ? lineColor
1299
- : type == "pure"
1300
- ? pure
1301
- : stops[0].color
1302
- }
1303
- fill="none"
1304
- />
1305
- <foreignObject
1306
- width="1"
1307
- height="1"
1308
- x={_x}
1309
- y={y2 + translateY}
1310
- style={{ overflow: "visible", position: "relative" }}
1311
- >
1312
- <div
1313
- className={animation ? ringCss["label-text"] : ""}
1314
- style={{
1315
- position: isIOS ? "absolute" : "relative",
1316
- transform: isIOS
1317
- ? `translate(calc(${x3 < 0 ? "-100%" : "0px"} + ${
1318
- left + _x
1319
- }px),calc(-50% + ${top + y2 + translateY}px))`
1320
- : "translate(0,-50%)",
1321
- whiteSpace: "nowrap",
1322
- float: x3 >= 0 ? "left" : "right",
1323
- width: "max-content",
1324
- display: "flex",
1325
- flexDirection: mode == "horizontal" ? "row" : "column",
1326
- alignItems:
1327
- align == "left"
1328
- ? "flex-start"
1329
- : align == "center"
1330
- ? "center"
1331
- : "flex-end",
1332
- justifyContent: "center",
1333
- }}
1334
- >
1335
- {_showName && (
1336
- <TextOverflow
1337
- type={textOverflow}
1338
- value={
1339
- displayName + (showValue || showPercent ? ":" : "")
1340
- }
1341
- speed={speed}
1342
- style={{
1343
- maxWidth,
1344
- ...nameStyle,
1345
- float: mode == "horizontal" ? "left" : "none",
1346
- transform: `translate(${NameTranslate.x}px, ${NameTranslate.y}px)`,
1347
- }}
1348
- ></TextOverflow>
1349
- )}
1350
- {showValue && (
1351
- <span
1352
- style={{
1353
- ...getFontStyle(valueFont),
1354
- color: valueSameColor ? pure : valueFont.color,
1355
- transform: `translate(${ValueTranslate.x}px, ${ValueTranslate.y}px)`,
1356
- }}
1357
- >
1358
- {data.y}
1359
- {showSuffix && (
1360
- <span
1361
- style={{
1362
- position: "relative",
1363
- fontSize: suffixFontSize,
1364
- marginLeft: suffixTranslateX,
1365
- top: suffixTranslateY,
1366
- }}
1367
- >
1368
- {text}
1369
- </span>
1370
- )}
1371
- </span>
1372
- )}
1373
- {showPercent && (
1374
- <span
1375
- style={{
1376
- ...getFontStyle(percentFont),
1377
- color: percentSameColor ? pure : percentFont.color,
1378
- transform: `translate(${PercentTranslate.x}px, ${PercentTranslate.y}px)`,
1379
- }}
1380
- >
1381
- {(_showValue ? "(" : "") +
1382
- percent +
1383
- "%" +
1384
- (_showValue ? ")" : "")}
1385
- </span>
1386
- )}
1387
- </div>
1388
- </foreignObject>
1389
- </g>
1390
- )
1391
- );
1392
- },
1393
- )}
1394
- </g>
1395
- );
1396
- };
1397
-
1398
- function getAlign(align, reverse) {
1399
- if (align == "center") return "center";
1400
- if (align == "left") return reverse ? "flex-end" : "flex-start";
1401
- return reverse ? "flex-start" : "flex-end";
1402
- }
1403
- function getTranslate(translate, reverse) {
1404
- const { x, y } = translate;
1405
- return `translate(${reverse ? -x : x}px, ${y}px)`;
1406
- }
1407
- const RingLabel = ({
1408
- config: {
1409
- ringDuration,
1410
- labelDuration,
1411
- maxRadius = 0,
1412
- lineLength,
1413
- lineColor,
1414
- distance,
1415
- mode,
1416
- align = "center",
1417
- show,
1418
- translate: { x: translateX, y: translateY },
1419
- name: {
1420
- show: showName,
1421
- font: nameFont,
1422
- maxWidth,
1423
- textOverflow,
1424
- speed,
1425
- translate: nameTranslate,
1426
- },
1427
- value: {
1428
- show: showValue,
1429
- font: valueFont,
1430
- sameColor: valueSameColor,
1431
- suffix: {
1432
- show: showSuffix,
1433
- text,
1434
- fontSize: suffixFontSize,
1435
- translate: { x: suffixTranslateX, y: suffixTranslateY },
1436
- },
1437
- translate: valueTranslate,
1438
- },
1439
- percent: {
1440
- show: showPercent,
1441
- sameColor: percentSameColor,
1442
- font: percentFont,
1443
- precision,
1444
- translate: percentTranslate,
1445
- },
1446
- },
1447
- iosStyle: { isIOS, left, top },
1448
- judge,
1449
- arcs,
1450
- }) => {
1451
- const _arcs = useMemo(
1452
- () => getDataWithPercent(arcs, precision),
1453
- [arcs, precision],
1454
- );
1455
-
1456
- //数据做出容错
1457
- if (judge == 0) {
1458
- _arcs.forEach((d) => {
1459
- d.percent = 0;
1460
- });
1461
- }
1462
-
1463
- return (
1464
- <g>
1465
- {_arcs.map(
1466
- (
1467
- {
1468
- series: {
1469
- color: {
1470
- type,
1471
- pure,
1472
- linear: { stops },
1473
- },
1474
- },
1475
- data: realData,
1476
- displayName,
1477
- value,
1478
- percent,
1479
- arc,
1480
- outerRadius,
1481
- index: actualIndex,
1482
- },
1483
- index,
1484
- ) => {
1485
- const [x, y] = arc.centroid();
1486
- const midAngle = Math.atan2(y, x);
1487
-
1488
- const [x1, y1] = getCoord(
1489
- midAngle,
1490
- maxRadius ? maxRadius : outerRadius,
1491
- );
1492
-
1493
- const radius = (maxRadius ? maxRadius : outerRadius) + distance;
1494
- const [x2, y2] = getCoord(midAngle, radius);
1495
-
1496
- const directionX = x2 < 0 ? -1 : 1;
1497
- const directionY = y2 < 0 ? -1 : 1;
1498
- const x3 = x2 + lineLength * directionX;
1499
- const _x = x3 + (translateX + 6) * directionX;
1500
-
1501
- const _showName = showName && displayName;
1502
- const _showValue = showValue && (value || showSuffix);
1503
-
1504
- return (
1505
- show &&
1506
- (_showName || showPercent || _showValue) && (
1507
- <g key={index}>
1508
- <path
1509
- className={ringCss["label-line"]}
1510
- style={{
1511
- animationDelay: `${
1512
- (actualIndex + 1) * ringDuration - labelDuration
1513
- }ms`,
1514
- }}
1515
- d={
1516
- "M" +
1517
- x1 +
1518
- ", " +
1519
- y1 +
1520
- "L" +
1521
- x2 +
1522
- ", " +
1523
- y2 +
1524
- "L" +
1525
- x3 +
1526
- ", " +
1527
- y2
1528
- }
1529
- stroke={
1530
- lineColor
1531
- ? lineColor
1532
- : type == "pure"
1533
- ? pure
1534
- : stops[0].color
1535
- }
1536
- fill="none"
1537
- />
1538
- <foreignObject
1539
- width="1"
1540
- height="1"
1541
- x={_x}
1542
- y={y2 + translateY}
1543
- style={{ overflow: "visible", position: "relative" }}
1544
- >
1545
- <div
1546
- className={ringCss["label-text"]}
1547
- style={{
1548
- position: isIOS ? "absolute" : "relative",
1549
- transform: isIOS
1550
- ? `translate(calc(${x3 < 0 ? "-100%" : "0px"} + ${
1551
- left + _x
1552
- }px),calc(-50% + ${top + y2 + translateY}px))`
1553
- : "translate(0,-50%)",
1554
- whiteSpace: "nowrap",
1555
- float: x3 >= 0 ? "left" : "right",
1556
- width: "max-content",
1557
- animationDelay: `${
1558
- (actualIndex + 1) * ringDuration - labelDuration
1559
- }ms`,
1560
- display: "flex",
1561
- flexDirection: mode == "horizontal" ? "row" : "column",
1562
- alignItems: getAlign(align, x3 >= 0),
1563
- justifyContent: "center",
1564
- }}
1565
- >
1566
- {_showName && (
1567
- <TextOverflow
1568
- type={textOverflow}
1569
- value={
1570
- displayName + (showValue || showPercent ? ":" : "")
1571
- }
1572
- speed={speed}
1573
- style={{
1574
- maxWidth,
1575
- ...getFontStyle(nameFont),
1576
- float: mode == "horizontal" ? "left" : "none",
1577
- transform: getTranslate(nameTranslate, x3 >= 0),
1578
- }}
1579
- ></TextOverflow>
1580
- )}
1581
- {showValue && (
1582
- <span
1583
- style={{
1584
- ...getFontStyle(valueFont),
1585
- transform: getTranslate(valueTranslate, x3 >= 0),
1586
- color: valueSameColor ? pure : valueFont.color,
1587
- }}
1588
- >
1589
- {realData.y}
1590
- {showSuffix && (
1591
- <span
1592
- style={{
1593
- position: "relative",
1594
- fontSize: suffixFontSize,
1595
- marginLeft: suffixTranslateX,
1596
- top: suffixTranslateY,
1597
- }}
1598
- >
1599
- {text}
1600
- </span>
1601
- )}
1602
- </span>
1603
- )}
1604
- {showPercent && (
1605
- <span
1606
- style={{
1607
- ...getFontStyle(percentFont),
1608
- transform: getTranslate(percentTranslate, x3 >= 0),
1609
- color: percentSameColor ? pure : percentFont.color,
1610
- }}
1611
- >
1612
- {(_showValue ? "(" : "") +
1613
- percent +
1614
- "%" +
1615
- (_showValue ? ")" : "")}
1616
- </span>
1617
- )}
1618
- </div>
1619
- </foreignObject>
1620
- </g>
1621
- )
1622
- );
1623
- },
1624
- )}
1625
- </g>
1626
- );
1627
- };
1628
-
1629
- export default Mapping(Carousel(Component));
1
+ /**
2
+ * 饼环图
3
+ */
4
+ import React, {
5
+ memo,
6
+ useMemo,
7
+ useCallback,
8
+ useRef,
9
+ useState,
10
+ useEffect,
11
+ useContext,
12
+ useLayoutEffect,
13
+ Fragment,
14
+ } from "react";
15
+ import {
16
+ ChartContainer,
17
+ Carousel,
18
+ Legend,
19
+ ConicalGradient,
20
+ Mapping,
21
+ TextOverflow,
22
+ } from ".";
23
+ import { chartContext } from "../context";
24
+ import {
25
+ getFontStyle,
26
+ sortPie,
27
+ getDataWithPercent,
28
+ getColorList,
29
+ } from "../utils";
30
+ import { pie, arc, extent, scaleLinear } from "d3v7";
31
+ import { animate, linear } from "popmotion";
32
+ import LinearGradient from "./LinearGradient";
33
+ import { pieLegendFormatter as legendFormatter } from "../formatter";
34
+ import ringCss from "../css/piechart.module.css";
35
+ import { useAiDataOfPie } from "../hooks";
36
+ import { PieTooltip } from "./PieTooltip";
37
+ import { getPieAdaptiveMarginPreset } from "../utils/legendPlacement";
38
+
39
+ const PI = Math.PI;
40
+
41
+ const PIE_ADAPTIVE_MARGINS = {
42
+ right: { marginTop: 24, marginBottom: 24, marginLeft: 0, marginRight: 200 },
43
+ left: { marginTop: 24, marginBottom: 24, marginLeft: 200, marginRight: 0 },
44
+ top: { marginTop: 110, marginBottom: 24, marginLeft: 24, marginRight: 24 },
45
+ bottom: { marginTop: 24, marginBottom: 110, marginLeft: 24, marginRight: 24 },
46
+ hidden: { marginTop: 24, marginBottom: 24, marginLeft: 24, marginRight: 24 },
47
+ };
48
+
49
+ const getPieAdaptiveMargin = (show, alignment) =>
50
+ getPieAdaptiveMarginPreset(show, alignment, PIE_ADAPTIVE_MARGINS);
51
+
52
+ const defaultChart = {
53
+ outerRadius: 1,
54
+ innerRadius: 0,
55
+ cornerRadius: 0,
56
+ rose: false,
57
+ roseType: "radius",
58
+ baseRadius: 0,
59
+ padAngle: 0,
60
+ };
61
+ const defaultAngle = { startAngle: 0, endAngle: 360, antiClockwise: false };
62
+
63
+ // const nameDy = (showValue, showPercent, mode, dir) => {
64
+ // if (showValue || showPercent) {
65
+ // if (mode == "vertical") {
66
+ // return dir == 1 ? "1.1em" : "-2.6em";
67
+ // } else {
68
+ // return 0;
69
+ // }
70
+ // } else {
71
+ // if (mode == "vertical") {
72
+ // return dir * 1.1 + "em";
73
+ // } else {
74
+ // return 0;
75
+ // }
76
+ // }
77
+ // };
78
+ // const valueDy = (value1, mode, dir) => {
79
+ // if (value1) {
80
+ // if (mode == "vertical") {
81
+ // return "1.5em";
82
+ // } else {
83
+ // return 0;
84
+ // }
85
+ // } else {
86
+ // if (mode == "vertical") {
87
+ // return dir == 1 ? "1.1em" : "-1.1em";
88
+ // } else {
89
+ // return 0;
90
+ // }
91
+ // }
92
+ // };
93
+
94
+ // const percentDy_ = (showName, showValue, mode, dir) => {
95
+ // if (showValue) {
96
+ // return 0;
97
+ // }
98
+ // if (showName) {
99
+ // if (mode == "vertical") {
100
+ // return "1.5em";
101
+ // } else {
102
+ // return 0;
103
+ // }
104
+ // } else {
105
+ // if (mode == "vertical") {
106
+ // return dir * 1.1 + "em";
107
+ // } else {
108
+ // return 0;
109
+ // }
110
+ // }
111
+ // };
112
+
113
+ // const percentX = (showName, showValue, mode, x) => {
114
+ // if (showValue) {
115
+ // return "";
116
+ // }
117
+ // if (showName) {
118
+ // if (mode == "vertical") {
119
+ // return x;
120
+ // } else {
121
+ // return "";
122
+ // }
123
+ // } else {
124
+ // return x;
125
+ // }
126
+ // };
127
+
128
+ // const percentDx = (showName, showValue, mode) => {
129
+ // if (showValue) {
130
+ // return "0.5em";
131
+ // }
132
+ // if (showName) {
133
+ // if (mode == "vertical") {
134
+ // return 0;
135
+ // } else {
136
+ // return "0.5em";
137
+ // }
138
+ // } else {
139
+ // return 0;
140
+ // }
141
+ // };
142
+
143
+ // const percentDy = (showName, showValue, mode) => {
144
+ // if (showValue) {
145
+ // return 0;
146
+ // }
147
+ // if (showName) {
148
+ // if (mode == "vertical") {
149
+ // return "1.5em";
150
+ // } else {
151
+ // return 0;
152
+ // }
153
+ // } else {
154
+ // return 0;
155
+ // }
156
+ // };
157
+
158
+ // const valueDx = (showName, mode) => {
159
+ // if (!showName) {
160
+ // return "";
161
+ // }
162
+ // if (mode == "vertical") {
163
+ // return "";
164
+ // } else {
165
+ // return "0.5em";
166
+ // }
167
+ // };
168
+
169
+ const getCoord = (deg, radius) => {
170
+ var x = Math.cos(deg) * radius,
171
+ y = Math.sin(deg) * radius;
172
+ return [x, y];
173
+ };
174
+
175
+ const getRoseRadius = ({ innerRadius, baseRadius }) =>
176
+ innerRadius + (1 - innerRadius) * baseRadius;
177
+
178
+ const getAngle = ({ startAngle, endAngle, antiClockwise, ...rest }) => {
179
+ if (antiClockwise)
180
+ return {
181
+ ...rest,
182
+ startAngle: endAngle - 180,
183
+ endAngle: startAngle - 180,
184
+ };
185
+ return { ...rest, startAngle, endAngle };
186
+ };
187
+
188
+ const getArc = (
189
+ radius,
190
+ {
191
+ padAngle = 0,
192
+ innerRadius = 0,
193
+ outerRadius = 1,
194
+ cornerRadius = 0,
195
+ startAngle = 0,
196
+ endAngle = 360,
197
+ ...rest
198
+ },
199
+ series_,
200
+ index,
201
+ ) => {
202
+ const series =
203
+ series_.find((s) => s.fieldName == rest.data.s) ||
204
+ series_[index % series_.length];
205
+ return {
206
+ ...rest,
207
+ type: "pie",
208
+ fieldName: series.fieldName,
209
+ displayName: series.displayName || rest.data.s,
210
+ series: series,
211
+ innerRadius: innerRadius * radius,
212
+ outerRadius: outerRadius * radius,
213
+ arc: arc()
214
+ .innerRadius(innerRadius * radius)
215
+ .outerRadius(outerRadius * radius)
216
+ .cornerRadius(cornerRadius)
217
+ .startAngle(startAngle)
218
+ .endAngle(endAngle)
219
+ .padAngle(padAngle),
220
+ };
221
+ };
222
+
223
+ const getCircleScale = ({ count, color, width, length } = tick, radius) => {
224
+ let data = [],
225
+ arcs = [],
226
+ centroids = [];
227
+ for (let i = 0; i < count; i++) {
228
+ data.push(1);
229
+ }
230
+ let scaleData = pie()(data);
231
+ scaleData.map((data) => {
232
+ let _arc = arc()
233
+ .innerRadius(radius + length / 2)
234
+ .outerRadius(radius + length / 2)
235
+ .startAngle(data.startAngle)
236
+ .endAngle(data.endAngle);
237
+ arcs.push(_arc());
238
+ centroids.push(_arc.centroid());
239
+ });
240
+ return (
241
+ <g>
242
+ {centroids.map((center, index) => {
243
+ let x = center[0],
244
+ y = center[1];
245
+ let rate = length / Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
246
+ return (
247
+ <path
248
+ key={index}
249
+ d={`M${x},${y}l${x * rate},${y * rate}`}
250
+ strokeWidth={width}
251
+ stroke={color}
252
+ />
253
+ );
254
+ })}
255
+ </g>
256
+ );
257
+ };
258
+
259
+ const Component = memo(
260
+ ({
261
+ width,
262
+ height,
263
+ config: {
264
+ chart: {
265
+ label,
266
+ legend: { formatter = legendFormatter, ...legend },
267
+ animation: { ringDuration, labelDuration } = {},
268
+ margin: { marginLeft, marginTop, marginRight, marginBottom },
269
+ },
270
+ fan: {
271
+ chart = defaultChart,
272
+ chart: { outerRadius = defaultChart.outerRadius, padAngle },
273
+ angle = defaultAngle,
274
+ stroke: { show, strokeWidth, color } = { show: false },
275
+ decorate,
276
+ decorate2,
277
+ categoryText,
278
+ outerDecorate,
279
+ current,
280
+ } = {},
281
+ order,
282
+ columnsSeries,
283
+ series,
284
+ animation: {
285
+ on,
286
+ current: {
287
+ widthen = 0,
288
+ heighten = 0,
289
+ opacity = 0,
290
+ width: radiusWidthAdd = 0,
291
+ color: animateColor,
292
+ textStyle: animateCTS,
293
+ gap = 0,
294
+ },
295
+ rotate = 0,
296
+ },
297
+ tooltip = {},
298
+ },
299
+ config,
300
+ state: { currentIndex, trigger },
301
+ onEvent,
302
+ hoverEvent,
303
+ data: originData = [],
304
+ }) => {
305
+ const data = originData.map((d) => ({ ...d, y: d.y < 0 ? 0 : d.y }));
306
+ const prevIndex = useRef(null);
307
+ const { precision: legendPrecision } = legend.config.percent;
308
+ const {
309
+ id,
310
+ isIOS,
311
+ width: chartWidth,
312
+ height: chartHeight,
313
+ triggerOnRelative,
314
+ onEmit,
315
+ updateConfig,
316
+ } = useContext(chartContext);
317
+ const { show: legendShow, name: { layoutMode: legendLayoutMode } = {} } =
318
+ legend.config;
319
+ const legendAlignment = legend.config?.layout?.alignment;
320
+ const prevLegendLayoutKey = useRef(null);
321
+ useEffect(() => {
322
+ if (legendLayoutMode !== "Adaptive") {
323
+ prevLegendLayoutKey.current = null;
324
+ return;
325
+ }
326
+ if (!updateConfig) return;
327
+
328
+ const layoutKey = `${legendShow}-${legendAlignment}`;
329
+ const prev = prevLegendLayoutKey.current;
330
+ prevLegendLayoutKey.current = layoutKey;
331
+
332
+ // 挂载/刷新:只记录当前图例布局,margin 始终读配置项
333
+ if (prev === null) return;
334
+ if (prev === layoutKey) return;
335
+
336
+ const target = getPieAdaptiveMargin(legendShow, legendAlignment);
337
+ updateConfig({
338
+ id,
339
+ type: "config",
340
+ payload: [
341
+ {
342
+ path: ["chart", "margin", "marginTop"],
343
+ field: "value",
344
+ value: target.marginTop,
345
+ },
346
+ {
347
+ path: ["chart", "margin", "marginBottom"],
348
+ field: "value",
349
+ value: target.marginBottom,
350
+ },
351
+ {
352
+ path: ["chart", "margin", "marginLeft"],
353
+ field: "value",
354
+ value: target.marginLeft,
355
+ },
356
+ {
357
+ path: ["chart", "margin", "marginRight"],
358
+ field: "value",
359
+ value: target.marginRight,
360
+ },
361
+ ],
362
+ });
363
+ }, [id, updateConfig, legendLayoutMode, legendShow, legendAlignment]);
364
+ const [y, setY] = useState(1);
365
+ const radius = (Math.min(chartWidth, chartHeight) / 2) * outerRadius;
366
+
367
+ const arcsFunc = useMemo(() => {
368
+ const { startAngle = 0, endAngle = 360 } = getAngle(angle);
369
+ const arcsFunc = pie()
370
+ .startAngle((startAngle * PI) / 180)
371
+ .endAngle((endAngle * PI) / 180)
372
+ .value((d) => d.y);
373
+ return arcsFunc;
374
+ }, [angle]);
375
+ //此处创建arcsFuncTwo的原因是为了兼容数据全为零
376
+ const arcsFuncTwo = useMemo(() => {
377
+ const { startAngle = 0, endAngle = 360 } = getAngle(angle);
378
+ const arcsFunc = pie()
379
+ .startAngle((startAngle * PI) / 180)
380
+ .endAngle((endAngle * PI) / 180)
381
+ .value((d) => (d.y == 0 ? 1 : d.y));
382
+ return arcsFunc;
383
+ }, [angle]);
384
+ let judgeData = 0; //此处声明全局变量是为了父子组件传递值来判断数据是否都为零
385
+ const arcs = useMemo(() => {
386
+ const _chart = Object.assign(defaultChart, chart);
387
+ const {
388
+ innerRadius,
389
+ outerRadius,
390
+ rose,
391
+ cornerRadius,
392
+ padAngle,
393
+ roseType,
394
+ } = _chart;
395
+ const _padAngle = (padAngle * Math.PI) / 180;
396
+
397
+ switch (order) {
398
+ case "":
399
+ arcsFunc.sort(null);
400
+ break;
401
+ case "desc":
402
+ arcsFunc.sort((a, b) => b.y - a.y);
403
+ break;
404
+ case "asc":
405
+ arcsFunc.sort((a, b) => a.y - b.y);
406
+ break;
407
+ }
408
+
409
+ //此处判断data中的y是否都为零,方便饼图都为零时展示
410
+
411
+ let arcs = 0;
412
+ data.forEach(function (item) {
413
+ judgeData += item.y;
414
+ });
415
+ if (judgeData == 0) {
416
+ arcs = arcsFuncTwo(data);
417
+ } else {
418
+ arcs = arcsFunc(data);
419
+ }
420
+
421
+ //const arcs = arcsFunc(data); 此处是原本的传输饼图data流程
422
+ const legendDataWithPercent = getDataWithPercent(arcs, legendPrecision);
423
+ const _legendDataWithPercent = sortPie(legendDataWithPercent, order);
424
+
425
+ if (rose) {
426
+ const domain = extent(_legendDataWithPercent, (d) => d.value);
427
+ const roseRadius = getRoseRadius(_chart);
428
+ const scaler = scaleLinear().domain(domain).range([roseRadius, 1]);
429
+
430
+ const angle = (PI * 2) / _legendDataWithPercent.length;
431
+ return _legendDataWithPercent.map(
432
+ ({ startAngle, endAngle, ...arc }, index) => ({
433
+ ...arc,
434
+ id: id + "_linear_" + index,
435
+ startAngle: roseType == "area" ? angle * index : startAngle,
436
+ endAngle: roseType == "area" ? angle * (index + 1) : endAngle,
437
+ cornerRadius,
438
+ padAngle: _padAngle,
439
+ innerRadius,
440
+ outerRadius: scaler(arc.value),
441
+ }),
442
+ );
443
+ }
444
+ return _legendDataWithPercent.map((arc, index) => ({
445
+ ...arc,
446
+ id: id + "_linear_" + index,
447
+ cornerRadius,
448
+ padAngle: _padAngle,
449
+ innerRadius,
450
+ outerRadius,
451
+ }));
452
+ }, [data, arcsFunc, arcsFuncTwo, chart, legendPrecision, order]);
453
+
454
+ const _arcs = useMemo(() => {
455
+ const seriesLength = series.size;
456
+ if (!seriesLength) return [];
457
+ const _series = [...series.values()];
458
+ if (_series.length < arcs.length)
459
+ console.warn("请检查数据中是否存在相同的s");
460
+ return arcs.map((arc, index) => getArc(radius, arc, _series, index));
461
+ }, [series, arcs, radius]);
462
+
463
+ const onClick = useCallback(
464
+ (e) => {
465
+ const _data = arcs[+e.currentTarget.dataset.index].data;
466
+ triggerOnRelative("onClick", _data);
467
+ onEmit("onClick", _data);
468
+ onEvent({
469
+ currentIndex: +e.currentTarget.dataset.index,
470
+ type: "onClick",
471
+ });
472
+ },
473
+ [onEvent],
474
+ );
475
+
476
+ const onMouseEnter = useCallback(
477
+ (e) => {
478
+ const _data = arcs[+e.currentTarget.dataset.index].data;
479
+ triggerOnRelative("mousehover", _data);
480
+ onEmit("mousehover", _data);
481
+ onEvent({
482
+ currentIndex: +e.currentTarget.dataset.index,
483
+ type: "onMouseEnter",
484
+ });
485
+ },
486
+ [onEvent, triggerOnRelative, onEmit],
487
+ );
488
+
489
+ const onMouseLeave = useCallback(
490
+ (e) => {
491
+ setMousePos({
492
+ x: 0,
493
+ y: 0,
494
+ });
495
+ onEvent({
496
+ currentIndex: +e.currentTarget.dataset.index,
497
+ type: "onMouseLeave",
498
+ });
499
+ },
500
+ [onEvent],
501
+ );
502
+
503
+ useLayoutEffect(() => {
504
+ let animation;
505
+ if (!!on) {
506
+ animation = animate({
507
+ from: 0,
508
+ to: 1,
509
+ duration: 500,
510
+ ease: linear,
511
+ onPlay: () => {},
512
+ onUpdate: (v) => {
513
+ setY(v);
514
+ },
515
+ onComplete: () => {
516
+ const _data = arcs[+currentIndex] ? arcs[+currentIndex].data : {};
517
+ triggerOnRelative("carousel", _data);
518
+ onEmit("carousel", _data);
519
+ },
520
+ });
521
+ } else {
522
+ if (currentIndex !== null && trigger === "onClick") {
523
+ const _data = arcs[+currentIndex] ? arcs[+currentIndex].data : {};
524
+ triggerOnRelative("click", _data);
525
+ onEmit("click", _data);
526
+ }
527
+ }
528
+ return () => {
529
+ prevIndex.current = currentIndex;
530
+ animation && animation.stop();
531
+ animation = null;
532
+ };
533
+ }, [
534
+ JSON.stringify(arcs),
535
+ on,
536
+ currentIndex,
537
+ trigger,
538
+ onEmit,
539
+ triggerOnRelative,
540
+ ]);
541
+ const aiData = useAiDataOfPie(_arcs, legend);
542
+ useEffect(() => {
543
+ if (aiData.length) {
544
+ if (!window.aiData) {
545
+ window.aiData = {};
546
+ }
547
+ window.aiData[id] = {
548
+ getAI: () => {
549
+ return aiData;
550
+ },
551
+ };
552
+ }
553
+ return () => {
554
+ window.aiData && window.aiData[id] && delete window.aiData[id];
555
+ };
556
+ }, [JSON.stringify(aiData), id]);
557
+
558
+ const halfChartWidth = chartWidth / 2;
559
+ const halfChartHeight = chartHeight / 2;
560
+
561
+ const rotate_ = decorate2
562
+ ? (-(arcs[+currentIndex].startAngle + arcs[+currentIndex].endAngle) *
563
+ 90) /
564
+ Math.PI +
565
+ rotate
566
+ : 0;
567
+ let maxRadius = 0;
568
+ _arcs.map((d) => {
569
+ maxRadius = Math.max(maxRadius, d.outerRadius);
570
+ });
571
+ let centerRadius = 0.5 * maxRadius + 0.5 * _arcs[0].innerRadius;
572
+ const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
573
+ const [hoverData, setHoverData] = useState(null);
574
+ const pieWarpEl = useRef(null);
575
+ const domRef = useRef();
576
+ return outerDecorate ? (
577
+ <div ref={domRef}>
578
+ <ChartContainer //用于生成甜甜圈图,判断依据是外环装饰这个配置项(outerDecorate)
579
+ width={width}
580
+ height={height}
581
+ marginLeft={marginLeft}
582
+ marginTop={marginTop}
583
+ ref={pieWarpEl}
584
+ >
585
+ <g
586
+ style={{
587
+ "--labelDuration": labelDuration + "ms",
588
+ "--ringDuration": ringDuration + "ms",
589
+ transition: "transform ease-in-out 0.3s",
590
+ transform:
591
+ "translate(" +
592
+ halfChartWidth +
593
+ "px, " +
594
+ halfChartHeight +
595
+ "px) rotate(" +
596
+ rotate_ +
597
+ "deg)",
598
+ }}
599
+ >
600
+ {
601
+ //用于生成外环装饰的刻度
602
+ outerDecorate.tick.show &&
603
+ getCircleScale(outerDecorate.tick, maxRadius)
604
+ }
605
+ <circle //外环装饰
606
+ cx="0"
607
+ cy="0"
608
+ r={maxRadius + 2}
609
+ fill="none"
610
+ stroke={outerDecorate.color}
611
+ strokeWidth={outerDecorate.width}
612
+ />
613
+ {_arcs.map(
614
+ ({ id, value, series, arc, innerRadius, outerRadius }, index) => {
615
+ const arcWidth = outerRadius - innerRadius;
616
+ const path = arc
617
+ .innerRadius(centerRadius)
618
+ .outerRadius(centerRadius)(value);
619
+ const dashLength = Math.ceil(
620
+ (Math.PI * centerRadius * 2) / _arcs.length,
621
+ );
622
+ const pie = getColorList(series.color);
623
+ return (
624
+ <Fragment key={index}>
625
+ <path
626
+ className={ringCss["inner-arc"]}
627
+ style={{
628
+ strokeDasharray: `${dashLength},${2 * dashLength}`,
629
+ strokeDashoffset: dashLength,
630
+ animationDelay: `${index * ringDuration}ms`,
631
+ }}
632
+ data-index={index}
633
+ onClick={onClick}
634
+ onMouseEnter={onMouseEnter}
635
+ onMouseLeave={onMouseLeave}
636
+ onMouseMove={(e) => {
637
+ const _data = arcs[+e.currentTarget.dataset.index];
638
+ const warpBoxPos = {
639
+ x: pieWarpEl.current.getBoundingClientRect().x,
640
+ y: pieWarpEl.current.getBoundingClientRect().y,
641
+ };
642
+ setMousePos({
643
+ x: e.clientX - warpBoxPos.x,
644
+ y: e.clientY - warpBoxPos.y,
645
+ });
646
+ setHoverData(_data);
647
+ }}
648
+ d={path.split("L")[0]}
649
+ stroke={"url(#" + id + ")"}
650
+ strokeWidth={arcWidth}
651
+ fill="none"
652
+ />
653
+ <defs>
654
+ <LinearGradient
655
+ id={id}
656
+ colors={pie}
657
+ rotate={series.color.linear.angle + 180}
658
+ // gradientUnits='objectBoundingBox'
659
+ />
660
+ </defs>
661
+ </Fragment>
662
+ );
663
+ },
664
+ )}
665
+ {label && (
666
+ <RingLabel
667
+ config={{
668
+ ...label,
669
+ maxRadius: maxRadius + 2,
670
+ ringDuration,
671
+ labelDuration,
672
+ }}
673
+ iosStyle={{
674
+ isIOS,
675
+ left: halfChartWidth + marginLeft,
676
+ top: halfChartHeight + marginTop,
677
+ }}
678
+ arcs={_arcs}
679
+ judge={judgeData}
680
+ />
681
+ )}
682
+ </g>
683
+ </ChartContainer>
684
+
685
+ <Legend
686
+ {...legend}
687
+ height={chartHeight}
688
+ componentWidth={width}
689
+ marginLeft={marginLeft}
690
+ marginRight={marginRight}
691
+ isPieChart
692
+ columnsSeries={columnsSeries}
693
+ data={data}
694
+ series={_arcs.map((arc) => ({
695
+ ...arc,
696
+ percent: arc.percent.toFixed(legendPrecision),
697
+ }))}
698
+ pieClick={onClick}
699
+ formatter={formatter}
700
+ judge={judgeData}
701
+ />
702
+ {tooltip &&
703
+ mousePos &&
704
+ mousePos.x != 0 &&
705
+ mousePos.y != 0 &&
706
+ tooltip.manual && (
707
+ <div
708
+ style={{
709
+ position: "absolute",
710
+ pointerEvents: "none",
711
+ }}
712
+ >
713
+ <PieTooltip
714
+ series={series}
715
+ domRef={domRef}
716
+ data={hoverData}
717
+ config={tooltip}
718
+ pieCenter={{
719
+ x: halfChartWidth,
720
+ y: maxRadius + marginTop,
721
+ }}
722
+ mousePos={mousePos}
723
+ />
724
+ </div>
725
+ )}
726
+ </div>
727
+ ) : (
728
+ <div ref={domRef}>
729
+ <ChartContainer
730
+ width={width}
731
+ height={height}
732
+ marginLeft={marginLeft}
733
+ marginTop={marginTop}
734
+ onMouseEnter={() => {
735
+ hoverEvent(true);
736
+ }}
737
+ onMouseLeave={() => {
738
+ hoverEvent(false);
739
+ }}
740
+ ref={pieWarpEl}
741
+ >
742
+ <g
743
+ style={{
744
+ transition: "transform ease-in-out 0.3s",
745
+ transform:
746
+ "translate(" +
747
+ halfChartWidth +
748
+ "px, " +
749
+ halfChartHeight +
750
+ "px) rotate(" +
751
+ rotate_ +
752
+ "deg)",
753
+ }}
754
+ >
755
+ {_arcs.map(
756
+ (
757
+ {
758
+ id,
759
+ value,
760
+ series,
761
+ arc,
762
+ innerRadius,
763
+ outerRadius,
764
+ index: dataIndex,
765
+ },
766
+ index,
767
+ ) => {
768
+ const current = index == currentIndex;
769
+ const prev = index == prevIndex.current;
770
+ const offset = current ? y : prev ? 1 - y : 0;
771
+
772
+ const fillOpacity = animateColor
773
+ ? 1
774
+ : current
775
+ ? opacity / 100
776
+ : 1;
777
+ const deltaWidthen = offset * widthen;
778
+ const deltaHeighten = offset * heighten;
779
+ const path = arc
780
+ .innerRadius(innerRadius + deltaWidthen)
781
+ .outerRadius(outerRadius + deltaHeighten + deltaWidthen)(
782
+ value,
783
+ );
784
+ const pie = getColorList(series.color);
785
+ const currentPie = animateColor
786
+ ? getColorList(animateColor)
787
+ : getColorList(series.color);
788
+ let textPath = "",
789
+ categoryTextStyle = {};
790
+ if (categoryText && categoryText.show) {
791
+ //如果有类目文本,则需要计算文字路径
792
+ //let offsetWidth=decorate2.radiusWidth/2 + radiusWidthAdd/2; //当前文字需生成在装饰物内,故而半径需要减小
793
+ let textArc = arc
794
+ .innerRadius(
795
+ outerRadius + (current ? gap : categoryText.gap),
796
+ )
797
+ .outerRadius(
798
+ outerRadius + (current ? gap : categoryText.gap),
799
+ )(value);
800
+ let lastA = textArc.lastIndexOf("A");
801
+ textPath = textArc.slice(
802
+ 0,
803
+ lastA > 0 ? lastA : textArc.length,
804
+ ); //文字路径
805
+ categoryTextStyle = current
806
+ ? animateCTS
807
+ : categoryText.textStyle; //这里把textstyle拿出来
808
+ }
809
+
810
+ return (
811
+ <Fragment key={index}>
812
+ <path
813
+ data-index={index}
814
+ onClick={onClick}
815
+ onMouseEnter={onMouseEnter}
816
+ onMouseLeave={onMouseLeave}
817
+ onMouseMove={(e) => {
818
+ const _data = arcs[+e.currentTarget.dataset.index];
819
+ const warpBoxPos = {
820
+ x: pieWarpEl.current.getBoundingClientRect().x,
821
+ y: pieWarpEl.current.getBoundingClientRect().y,
822
+ };
823
+ setMousePos({
824
+ x: e.clientX - warpBoxPos.x,
825
+ y: e.clientY - warpBoxPos.y,
826
+ });
827
+ setHoverData(_data);
828
+ }}
829
+ d={path}
830
+ stroke={show ? color : "none"}
831
+ strokeWidth={show ? strokeWidth : "0"}
832
+ fill={"url(#" + id + ")"}
833
+ fillOpacity={fillOpacity}
834
+ />
835
+ {
836
+ //装饰物2,产生于每个弧的外部
837
+ decorate2 && decorate2.show && (
838
+ <path
839
+ data-index={index}
840
+ onClick={onClick}
841
+ onMouseEnter={onMouseEnter}
842
+ onMouseLeave={onMouseLeave}
843
+ d={arc
844
+ .innerRadius(outerRadius)
845
+ .outerRadius(
846
+ outerRadius +
847
+ decorate2.radiusWidth +
848
+ (current ? radiusWidthAdd : 0),
849
+ )(value)}
850
+ stroke={show ? color : "none"}
851
+ strokeWidth={show ? strokeWidth : "0"}
852
+ fill={"url(#" + id + ")"}
853
+ fillOpacity={decorate2.opacity / 100}
854
+ />
855
+ )
856
+ }
857
+ {
858
+ //类目文本
859
+ value && categoryText && categoryText.show && (
860
+ <g>
861
+ <path
862
+ onClick={onClick}
863
+ onMouseEnter={onMouseEnter}
864
+ onMouseLeave={onMouseLeave}
865
+ id={id + "_text_" + index}
866
+ d={textPath}
867
+ fill="none"
868
+ stroke="none"
869
+ />
870
+ <text
871
+ textAnchor="middle"
872
+ style={{
873
+ ...categoryTextStyle,
874
+ fontWeight: categoryTextStyle.bold
875
+ ? "bold"
876
+ : "normal",
877
+ fontStyle: categoryTextStyle.italic
878
+ ? "italic"
879
+ : "normal",
880
+ pointerEvents: "none",
881
+ }}
882
+ fill={categoryTextStyle.color}
883
+ >
884
+ <textPath
885
+ startOffset="50%"
886
+ href={"#" + id + "_text_" + index}
887
+ >
888
+ {_arcs[index].displayName ||
889
+ _arcs[index].fieldName}
890
+ </textPath>
891
+ </text>
892
+ </g>
893
+ )
894
+ }
895
+ <defs>
896
+ {/* 此处是环的发生地 */}
897
+ <LinearGradient
898
+ id={id}
899
+ colors={current ? currentPie : pie}
900
+ rotate={
901
+ current
902
+ ? animateColor
903
+ ? animateColor.linear.angle + 180
904
+ : series.color.linear.angle + 180
905
+ : series.color.linear.angle + 180
906
+ }
907
+ // gradientUnits='objectBoundingBox'
908
+ />
909
+ </defs>
910
+ </Fragment>
911
+ );
912
+ },
913
+ )}
914
+ {label && (
915
+ <Label
916
+ config={label}
917
+ iosStyle={{
918
+ isIOS,
919
+ left: halfChartWidth + marginLeft,
920
+ top: halfChartHeight + marginTop,
921
+ }}
922
+ arcs={_arcs}
923
+ judge={judgeData}
924
+ />
925
+ )}
926
+ {current && (
927
+ <g
928
+ fillOpacity={y}
929
+ style={{ transform: "rotate(" + -rotate_ + "deg)" }}
930
+ >
931
+ <Current
932
+ config={current}
933
+ width={chartWidth}
934
+ height={chartHeight}
935
+ iosStyle={{
936
+ marginLeft,
937
+ marginTop,
938
+ isIOS,
939
+ }}
940
+ data={_arcs}
941
+ judge={judgeData}
942
+ currentIndex={+currentIndex}
943
+ />
944
+ </g>
945
+ )}
946
+ </g>
947
+ </ChartContainer>
948
+ {decorate && (
949
+ <ConicalGradient
950
+ width={width}
951
+ height={height}
952
+ centerX={halfChartWidth + marginLeft}
953
+ centerY={halfChartHeight + marginTop}
954
+ config={decorate}
955
+ arcs={_arcs}
956
+ radius={radius}
957
+ />
958
+ )}
959
+
960
+ <Legend
961
+ {...legend}
962
+ height={chartHeight}
963
+ componentWidth={width}
964
+ marginLeft={marginLeft}
965
+ marginRight={marginRight}
966
+ isPieChart
967
+ data={data}
968
+ columnsSeries={columnsSeries}
969
+ series={_arcs.map((arc) => ({
970
+ ...arc,
971
+ percent: arc.percent.toFixed(legendPrecision),
972
+ }))}
973
+ pieClick={onClick}
974
+ formatter={formatter}
975
+ judge={judgeData}
976
+ />
977
+ {tooltip &&
978
+ mousePos &&
979
+ mousePos.x != 0 &&
980
+ mousePos.y != 0 &&
981
+ tooltip.manual && (
982
+ <div
983
+ style={{
984
+ position: "absolute",
985
+ pointerEvents: "none",
986
+ }}
987
+ >
988
+ <PieTooltip
989
+ series={series}
990
+ domRef={domRef}
991
+ data={hoverData}
992
+ config={tooltip}
993
+ pieCenter={{
994
+ x: halfChartWidth,
995
+ y: maxRadius + marginTop,
996
+ }}
997
+ mousePos={mousePos}
998
+ />
999
+ </div>
1000
+ )}
1001
+ </div>
1002
+ );
1003
+ },
1004
+ );
1005
+
1006
+ const Current = ({
1007
+ config: {
1008
+ show,
1009
+ gap,
1010
+ name: {
1011
+ show: showName,
1012
+ sameColor: nameColor,
1013
+ font: nameFont,
1014
+ translate = { x: 0, y: 0 },
1015
+ maxWidth,
1016
+ textOverflow,
1017
+ speed,
1018
+ },
1019
+ percent: {
1020
+ show: showPercent,
1021
+ sameColor: percentColor,
1022
+ font: percentFont,
1023
+ precision,
1024
+ translate: { x: translatePercentX, y: translatePercentY },
1025
+ },
1026
+ value: {
1027
+ show: showValue,
1028
+ sameColor: valueColor,
1029
+ font: valueFont,
1030
+ translate: { x: translateValueX, y: translateValueY },
1031
+ suffix: {
1032
+ show: showSuffix,
1033
+ fontSize,
1034
+ text,
1035
+ translate: { x: translateSuffixX, y: translateSuffixY },
1036
+ },
1037
+ },
1038
+ },
1039
+ iosStyle: { isIOS, marginLeft, marginTop },
1040
+ width,
1041
+ height,
1042
+ data,
1043
+ judge,
1044
+ currentIndex,
1045
+ }) => {
1046
+ const _data = useMemo(() => {
1047
+ const legendDataWithPercent = getDataWithPercent(data, precision);
1048
+ return sortPie(legendDataWithPercent, "");
1049
+ }, [data, precision]);
1050
+
1051
+ //数据容错,当data都为零那么需要进行以下容错
1052
+ if (judge == 0) {
1053
+ _data.forEach((d) => {
1054
+ ((d.percent = 0), (d.value = 0));
1055
+ });
1056
+ }
1057
+
1058
+ const currentData = _data[currentIndex];
1059
+
1060
+ if (!currentData) return null;
1061
+
1062
+ const { displayName, fieldName, value, percent } = currentData;
1063
+ let nameTemp = displayName ? displayName : fieldName; //类目名
1064
+
1065
+ let foreignStyle = {
1066
+ //foreignObject标签样式,
1067
+ width,
1068
+ height,
1069
+ position: "relative",
1070
+ overflow: "visible",
1071
+ pointerEvents: "none",
1072
+ },
1073
+ boxStyle = {
1074
+ //弹性盒子样式,用于当前值的上下居中对齐等
1075
+ width,
1076
+ height,
1077
+ position: "absolute",
1078
+ display: "flex",
1079
+ flexDirection: "column",
1080
+ justifyContent: "center",
1081
+ alignItems: "center",
1082
+ transform: isIOS
1083
+ ? `translate(${marginLeft}px,${marginTop}px)`
1084
+ : `translate(-${width / 2}px,-${height / 2}px)`,
1085
+ };
1086
+ let seriesColor = currentData.series.color;
1087
+ seriesColor =
1088
+ seriesColor.type == "pure"
1089
+ ? seriesColor.pure
1090
+ : seriesColor.linear.stops[0].color;
1091
+ return (
1092
+ show && (
1093
+ <foreignObject style={foreignStyle}>
1094
+ <div style={boxStyle}>
1095
+ {showName && (
1096
+ <TextOverflow
1097
+ type={textOverflow}
1098
+ value={nameTemp}
1099
+ speed={speed}
1100
+ style={{
1101
+ width: "100%",
1102
+ maxWidth,
1103
+ textAlign: "center",
1104
+ display: textOverflow == "marquee" ? "flex" : "bolck",
1105
+ justifyContent: "center",
1106
+ ...getFontStyle(nameFont),
1107
+ margin: gap / 2 + "px 0",
1108
+ color: nameColor ? seriesColor : nameFont.color,
1109
+ transform: `translate(${translate.x}px,${translate.y}px)`,
1110
+ }}
1111
+ ></TextOverflow>
1112
+ )}
1113
+ {
1114
+ //真实值
1115
+ showValue && (
1116
+ <span
1117
+ style={{
1118
+ ...getFontStyle(valueFont),
1119
+ transform:
1120
+ "translate(" +
1121
+ translateValueX +
1122
+ "px," +
1123
+ translateValueY +
1124
+ "px)",
1125
+ margin: gap / 2 + "px 0",
1126
+ color: valueColor ? seriesColor : valueFont.color,
1127
+ }}
1128
+ >
1129
+ {value}
1130
+ {showSuffix && text && (
1131
+ <span
1132
+ style={{
1133
+ display: "inline-block",
1134
+ transform:
1135
+ "translate(" +
1136
+ translateSuffixX +
1137
+ "px," +
1138
+ translateSuffixY +
1139
+ "px)",
1140
+ fontSize: fontSize,
1141
+ }}
1142
+ >
1143
+ {text}
1144
+ </span>
1145
+ )}
1146
+ </span>
1147
+ )
1148
+ }
1149
+ {
1150
+ //百分比值
1151
+ showPercent && (
1152
+ <span
1153
+ style={{
1154
+ transform:
1155
+ "translate(" +
1156
+ translatePercentX +
1157
+ "px," +
1158
+ translatePercentY +
1159
+ "px)",
1160
+ ...getFontStyle(percentFont),
1161
+ margin: gap / 2 + "px 0",
1162
+ color: percentColor ? seriesColor : percentFont.color,
1163
+ }}
1164
+ >
1165
+ {percent + "%"}
1166
+ </span>
1167
+ )
1168
+ }
1169
+ </div>
1170
+ </foreignObject>
1171
+ )
1172
+ );
1173
+ };
1174
+
1175
+ const Label = ({
1176
+ config: {
1177
+ maxRadius = 0,
1178
+ lineLength,
1179
+ lineColor,
1180
+ distance,
1181
+ mode,
1182
+ align,
1183
+ show,
1184
+ translate: { x: translateX, y: translateY },
1185
+ name: {
1186
+ show: showName,
1187
+ font: nameFont,
1188
+ maxWidth,
1189
+ textOverflow,
1190
+ speed,
1191
+ translate: NameTranslate,
1192
+ },
1193
+ value: {
1194
+ show: showValue,
1195
+ font: valueFont,
1196
+ sameColor: valueSameColor,
1197
+ suffix: {
1198
+ show: showSuffix,
1199
+ text,
1200
+ fontSize: suffixFontSize,
1201
+ translate: { x: suffixTranslateX, y: suffixTranslateY },
1202
+ },
1203
+ translate: ValueTranslate,
1204
+ },
1205
+ percent: {
1206
+ show: showPercent,
1207
+ sameColor: percentSameColor,
1208
+ font: percentFont,
1209
+ precision,
1210
+ translate: PercentTranslate,
1211
+ },
1212
+ },
1213
+ iosStyle: { isIOS, left, top },
1214
+ arcs,
1215
+ judge,
1216
+ animation,
1217
+ }) => {
1218
+ const _arcs = useMemo(
1219
+ () => getDataWithPercent(arcs, precision),
1220
+ [arcs, precision],
1221
+ );
1222
+ //数据做出容错
1223
+ if (judge == 0) {
1224
+ _arcs.forEach((d) => {
1225
+ d.percent = 0;
1226
+ });
1227
+ }
1228
+ return (
1229
+ <g>
1230
+ {_arcs.map(
1231
+ (
1232
+ {
1233
+ series: {
1234
+ color: {
1235
+ type,
1236
+ pure,
1237
+ linear: { stops },
1238
+ },
1239
+ },
1240
+ data,
1241
+ displayName,
1242
+ value,
1243
+ percent,
1244
+ arc,
1245
+ outerRadius,
1246
+ index: actualIndex,
1247
+ },
1248
+ index,
1249
+ ) => {
1250
+ const [x, y] = arc.centroid();
1251
+ const midAngle = Math.atan2(y, x);
1252
+
1253
+ const [x1, y1] = getCoord(
1254
+ midAngle,
1255
+ maxRadius ? maxRadius : outerRadius,
1256
+ );
1257
+
1258
+ const radius = (maxRadius ? maxRadius : outerRadius) + distance;
1259
+ const [x2, y2] = getCoord(midAngle, radius);
1260
+
1261
+ const direction = x2 < 0 ? -1 : 1;
1262
+ const x3 = x2 + lineLength * direction;
1263
+
1264
+ const _x = x3 + (translateX + 6) * direction;
1265
+
1266
+ const _showName = showName && displayName;
1267
+ const _showValue = showValue && (value || showSuffix);
1268
+ const nameStyle = getFontStyle(nameFont);
1269
+ return (
1270
+ show &&
1271
+ (_showName || showPercent || showValue) && (
1272
+ <g key={index}>
1273
+ <path
1274
+ className={animation ? ringCss["label-line"] : ""}
1275
+ style={{
1276
+ animationDelay: `${
1277
+ animation
1278
+ ? (actualIndex + 1) * ringDuration - labelDuration
1279
+ : 0
1280
+ }ms`,
1281
+ }}
1282
+ d={
1283
+ "M" +
1284
+ x1 +
1285
+ ", " +
1286
+ y1 +
1287
+ "L" +
1288
+ x2 +
1289
+ ", " +
1290
+ y2 +
1291
+ "L" +
1292
+ x3 +
1293
+ ", " +
1294
+ y2
1295
+ }
1296
+ stroke={
1297
+ lineColor
1298
+ ? lineColor
1299
+ : type == "pure"
1300
+ ? pure
1301
+ : stops[0].color
1302
+ }
1303
+ fill="none"
1304
+ />
1305
+ <foreignObject
1306
+ width="1"
1307
+ height="1"
1308
+ x={_x}
1309
+ y={y2 + translateY}
1310
+ style={{ overflow: "visible", position: "relative" }}
1311
+ >
1312
+ <div
1313
+ className={animation ? ringCss["label-text"] : ""}
1314
+ style={{
1315
+ position: isIOS ? "absolute" : "relative",
1316
+ transform: isIOS
1317
+ ? `translate(calc(${x3 < 0 ? "-100%" : "0px"} + ${
1318
+ left + _x
1319
+ }px),calc(-50% + ${top + y2 + translateY}px))`
1320
+ : "translate(0,-50%)",
1321
+ whiteSpace: "nowrap",
1322
+ float: x3 >= 0 ? "left" : "right",
1323
+ width: "max-content",
1324
+ display: "flex",
1325
+ flexDirection: mode == "horizontal" ? "row" : "column",
1326
+ alignItems:
1327
+ align == "left"
1328
+ ? "flex-start"
1329
+ : align == "center"
1330
+ ? "center"
1331
+ : "flex-end",
1332
+ justifyContent: "center",
1333
+ }}
1334
+ >
1335
+ {_showName && (
1336
+ <TextOverflow
1337
+ type={textOverflow}
1338
+ value={
1339
+ displayName + (showValue || showPercent ? ":" : "")
1340
+ }
1341
+ speed={speed}
1342
+ style={{
1343
+ maxWidth,
1344
+ ...nameStyle,
1345
+ float: mode == "horizontal" ? "left" : "none",
1346
+ transform: `translate(${NameTranslate.x}px, ${NameTranslate.y}px)`,
1347
+ }}
1348
+ ></TextOverflow>
1349
+ )}
1350
+ {showValue && (
1351
+ <span
1352
+ style={{
1353
+ ...getFontStyle(valueFont),
1354
+ color: valueSameColor ? pure : valueFont.color,
1355
+ transform: `translate(${ValueTranslate.x}px, ${ValueTranslate.y}px)`,
1356
+ }}
1357
+ >
1358
+ {data.y}
1359
+ {showSuffix && (
1360
+ <span
1361
+ style={{
1362
+ position: "relative",
1363
+ fontSize: suffixFontSize,
1364
+ marginLeft: suffixTranslateX,
1365
+ top: suffixTranslateY,
1366
+ }}
1367
+ >
1368
+ {text}
1369
+ </span>
1370
+ )}
1371
+ </span>
1372
+ )}
1373
+ {showPercent && (
1374
+ <span
1375
+ style={{
1376
+ ...getFontStyle(percentFont),
1377
+ color: percentSameColor ? pure : percentFont.color,
1378
+ transform: `translate(${PercentTranslate.x}px, ${PercentTranslate.y}px)`,
1379
+ }}
1380
+ >
1381
+ {(_showValue ? "(" : "") +
1382
+ percent +
1383
+ "%" +
1384
+ (_showValue ? ")" : "")}
1385
+ </span>
1386
+ )}
1387
+ </div>
1388
+ </foreignObject>
1389
+ </g>
1390
+ )
1391
+ );
1392
+ },
1393
+ )}
1394
+ </g>
1395
+ );
1396
+ };
1397
+
1398
+ function getAlign(align, reverse) {
1399
+ if (align == "center") return "center";
1400
+ if (align == "left") return reverse ? "flex-end" : "flex-start";
1401
+ return reverse ? "flex-start" : "flex-end";
1402
+ }
1403
+ function getTranslate(translate, reverse) {
1404
+ const { x, y } = translate;
1405
+ return `translate(${reverse ? -x : x}px, ${y}px)`;
1406
+ }
1407
+ const RingLabel = ({
1408
+ config: {
1409
+ ringDuration,
1410
+ labelDuration,
1411
+ maxRadius = 0,
1412
+ lineLength,
1413
+ lineColor,
1414
+ distance,
1415
+ mode,
1416
+ align = "center",
1417
+ show,
1418
+ translate: { x: translateX, y: translateY },
1419
+ name: {
1420
+ show: showName,
1421
+ font: nameFont,
1422
+ maxWidth,
1423
+ textOverflow,
1424
+ speed,
1425
+ translate: nameTranslate,
1426
+ },
1427
+ value: {
1428
+ show: showValue,
1429
+ font: valueFont,
1430
+ sameColor: valueSameColor,
1431
+ suffix: {
1432
+ show: showSuffix,
1433
+ text,
1434
+ fontSize: suffixFontSize,
1435
+ translate: { x: suffixTranslateX, y: suffixTranslateY },
1436
+ },
1437
+ translate: valueTranslate,
1438
+ },
1439
+ percent: {
1440
+ show: showPercent,
1441
+ sameColor: percentSameColor,
1442
+ font: percentFont,
1443
+ precision,
1444
+ translate: percentTranslate,
1445
+ },
1446
+ },
1447
+ iosStyle: { isIOS, left, top },
1448
+ judge,
1449
+ arcs,
1450
+ }) => {
1451
+ const _arcs = useMemo(
1452
+ () => getDataWithPercent(arcs, precision),
1453
+ [arcs, precision],
1454
+ );
1455
+
1456
+ //数据做出容错
1457
+ if (judge == 0) {
1458
+ _arcs.forEach((d) => {
1459
+ d.percent = 0;
1460
+ });
1461
+ }
1462
+
1463
+ return (
1464
+ <g>
1465
+ {_arcs.map(
1466
+ (
1467
+ {
1468
+ series: {
1469
+ color: {
1470
+ type,
1471
+ pure,
1472
+ linear: { stops },
1473
+ },
1474
+ },
1475
+ data: realData,
1476
+ displayName,
1477
+ value,
1478
+ percent,
1479
+ arc,
1480
+ outerRadius,
1481
+ index: actualIndex,
1482
+ },
1483
+ index,
1484
+ ) => {
1485
+ const [x, y] = arc.centroid();
1486
+ const midAngle = Math.atan2(y, x);
1487
+
1488
+ const [x1, y1] = getCoord(
1489
+ midAngle,
1490
+ maxRadius ? maxRadius : outerRadius,
1491
+ );
1492
+
1493
+ const radius = (maxRadius ? maxRadius : outerRadius) + distance;
1494
+ const [x2, y2] = getCoord(midAngle, radius);
1495
+
1496
+ const directionX = x2 < 0 ? -1 : 1;
1497
+ const directionY = y2 < 0 ? -1 : 1;
1498
+ const x3 = x2 + lineLength * directionX;
1499
+ const _x = x3 + (translateX + 6) * directionX;
1500
+
1501
+ const _showName = showName && displayName;
1502
+ const _showValue = showValue && (value || showSuffix);
1503
+
1504
+ return (
1505
+ show &&
1506
+ (_showName || showPercent || _showValue) && (
1507
+ <g key={index}>
1508
+ <path
1509
+ className={ringCss["label-line"]}
1510
+ style={{
1511
+ animationDelay: `${
1512
+ (actualIndex + 1) * ringDuration - labelDuration
1513
+ }ms`,
1514
+ }}
1515
+ d={
1516
+ "M" +
1517
+ x1 +
1518
+ ", " +
1519
+ y1 +
1520
+ "L" +
1521
+ x2 +
1522
+ ", " +
1523
+ y2 +
1524
+ "L" +
1525
+ x3 +
1526
+ ", " +
1527
+ y2
1528
+ }
1529
+ stroke={
1530
+ lineColor
1531
+ ? lineColor
1532
+ : type == "pure"
1533
+ ? pure
1534
+ : stops[0].color
1535
+ }
1536
+ fill="none"
1537
+ />
1538
+ <foreignObject
1539
+ width="1"
1540
+ height="1"
1541
+ x={_x}
1542
+ y={y2 + translateY}
1543
+ style={{ overflow: "visible", position: "relative" }}
1544
+ >
1545
+ <div
1546
+ className={ringCss["label-text"]}
1547
+ style={{
1548
+ position: isIOS ? "absolute" : "relative",
1549
+ transform: isIOS
1550
+ ? `translate(calc(${x3 < 0 ? "-100%" : "0px"} + ${
1551
+ left + _x
1552
+ }px),calc(-50% + ${top + y2 + translateY}px))`
1553
+ : "translate(0,-50%)",
1554
+ whiteSpace: "nowrap",
1555
+ float: x3 >= 0 ? "left" : "right",
1556
+ width: "max-content",
1557
+ animationDelay: `${
1558
+ (actualIndex + 1) * ringDuration - labelDuration
1559
+ }ms`,
1560
+ display: "flex",
1561
+ flexDirection: mode == "horizontal" ? "row" : "column",
1562
+ alignItems: getAlign(align, x3 >= 0),
1563
+ justifyContent: "center",
1564
+ }}
1565
+ >
1566
+ {_showName && (
1567
+ <TextOverflow
1568
+ type={textOverflow}
1569
+ value={
1570
+ displayName + (showValue || showPercent ? ":" : "")
1571
+ }
1572
+ speed={speed}
1573
+ style={{
1574
+ maxWidth,
1575
+ ...getFontStyle(nameFont),
1576
+ float: mode == "horizontal" ? "left" : "none",
1577
+ transform: getTranslate(nameTranslate, x3 >= 0),
1578
+ }}
1579
+ ></TextOverflow>
1580
+ )}
1581
+ {showValue && (
1582
+ <span
1583
+ style={{
1584
+ ...getFontStyle(valueFont),
1585
+ transform: getTranslate(valueTranslate, x3 >= 0),
1586
+ color: valueSameColor ? pure : valueFont.color,
1587
+ }}
1588
+ >
1589
+ {realData.y}
1590
+ {showSuffix && (
1591
+ <span
1592
+ style={{
1593
+ position: "relative",
1594
+ fontSize: suffixFontSize,
1595
+ marginLeft: suffixTranslateX,
1596
+ top: suffixTranslateY,
1597
+ }}
1598
+ >
1599
+ {text}
1600
+ </span>
1601
+ )}
1602
+ </span>
1603
+ )}
1604
+ {showPercent && (
1605
+ <span
1606
+ style={{
1607
+ ...getFontStyle(percentFont),
1608
+ transform: getTranslate(percentTranslate, x3 >= 0),
1609
+ color: percentSameColor ? pure : percentFont.color,
1610
+ }}
1611
+ >
1612
+ {(_showValue ? "(" : "") +
1613
+ percent +
1614
+ "%" +
1615
+ (_showValue ? ")" : "")}
1616
+ </span>
1617
+ )}
1618
+ </div>
1619
+ </foreignObject>
1620
+ </g>
1621
+ )
1622
+ );
1623
+ },
1624
+ )}
1625
+ </g>
1626
+ );
1627
+ };
1628
+
1629
+ export default Mapping(Carousel(Component));