@orbcharts/plugins-basic 3.0.0-alpha.68 → 3.0.0-alpha.70

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +4838 -4760
  3. package/dist/orbcharts-plugins-basic.umd.js +15 -14
  4. package/dist/src/utils/d3Graphics.d.ts +10 -0
  5. package/package.json +42 -42
  6. package/src/base/BaseBarStack.ts +779 -779
  7. package/src/base/BaseBars.ts +764 -764
  8. package/src/base/BaseBarsTriangle.ts +672 -672
  9. package/src/base/BaseDots.ts +513 -513
  10. package/src/base/BaseGroupAxis.ts +675 -652
  11. package/src/base/BaseLegend.ts +642 -642
  12. package/src/base/BaseLineAreas.ts +628 -628
  13. package/src/base/BaseLines.ts +704 -704
  14. package/src/base/BaseValueAxis.ts +578 -578
  15. package/src/base/types.ts +2 -2
  16. package/src/grid/defaults.ts +128 -128
  17. package/src/grid/gridObservables.ts +543 -543
  18. package/src/grid/index.ts +15 -15
  19. package/src/grid/plugins/BarStack.ts +43 -43
  20. package/src/grid/plugins/Bars.ts +44 -44
  21. package/src/grid/plugins/BarsPN.ts +41 -41
  22. package/src/grid/plugins/BarsTriangle.ts +42 -42
  23. package/src/grid/plugins/Dots.ts +37 -37
  24. package/src/grid/plugins/GridLegend.ts +59 -59
  25. package/src/grid/plugins/GroupAux.ts +1014 -991
  26. package/src/grid/plugins/GroupAxis.ts +36 -36
  27. package/src/grid/plugins/LineAreas.ts +40 -40
  28. package/src/grid/plugins/Lines.ts +40 -40
  29. package/src/grid/plugins/ScalingArea.ts +176 -174
  30. package/src/grid/plugins/ValueAxis.ts +36 -36
  31. package/src/grid/plugins/ValueStackAxis.ts +38 -38
  32. package/src/grid/types.ts +123 -123
  33. package/src/index.ts +9 -9
  34. package/src/multiGrid/defaults.ts +158 -158
  35. package/src/multiGrid/index.ts +13 -13
  36. package/src/multiGrid/multiGridObservables.ts +49 -49
  37. package/src/multiGrid/plugins/MultiBarStack.ts +78 -78
  38. package/src/multiGrid/plugins/MultiBars.ts +77 -77
  39. package/src/multiGrid/plugins/MultiBarsTriangle.ts +77 -77
  40. package/src/multiGrid/plugins/MultiDots.ts +65 -65
  41. package/src/multiGrid/plugins/MultiGridLegend.ts +89 -89
  42. package/src/multiGrid/plugins/MultiGroupAxis.ts +70 -70
  43. package/src/multiGrid/plugins/MultiLineAreas.ts +77 -77
  44. package/src/multiGrid/plugins/MultiLines.ts +77 -77
  45. package/src/multiGrid/plugins/MultiValueAxis.ts +69 -69
  46. package/src/multiGrid/plugins/MultiValueStackAxis.ts +69 -69
  47. package/src/multiGrid/plugins/OverlappingValueAxes.ts +170 -170
  48. package/src/multiGrid/plugins/OverlappingValueStackAxes.ts +169 -169
  49. package/src/multiGrid/types.ts +72 -72
  50. package/src/noneData/defaults.ts +102 -102
  51. package/src/noneData/index.ts +3 -3
  52. package/src/noneData/plugins/Container.ts +10 -10
  53. package/src/noneData/plugins/Tooltip.ts +327 -327
  54. package/src/noneData/types.ts +26 -26
  55. package/src/series/defaults.ts +149 -149
  56. package/src/series/index.ts +9 -9
  57. package/src/series/plugins/Bubbles.ts +545 -545
  58. package/src/series/plugins/Pie.ts +584 -584
  59. package/src/series/plugins/PieEventTexts.ts +262 -262
  60. package/src/series/plugins/PieLabels.ts +604 -598
  61. package/src/series/plugins/Rose.ts +481 -481
  62. package/src/series/plugins/RoseLabels.ts +571 -565
  63. package/src/series/plugins/SeriesLegend.ts +59 -59
  64. package/src/series/seriesObservables.ts +145 -145
  65. package/src/series/seriesUtils.ts +51 -51
  66. package/src/series/types.ts +87 -87
  67. package/src/tree/defaults.ts +23 -23
  68. package/src/tree/index.ts +3 -3
  69. package/src/tree/plugins/TreeLegend.ts +59 -59
  70. package/src/tree/plugins/TreeMap.ts +305 -305
  71. package/src/tree/types.ts +23 -23
  72. package/src/utils/commonUtils.ts +21 -21
  73. package/src/utils/d3Graphics.ts +174 -124
  74. package/src/utils/d3Utils.ts +73 -73
  75. package/src/utils/observables.ts +14 -14
  76. package/src/utils/orbchartsUtils.ts +100 -100
  77. package/tsconfig.base.json +13 -13
  78. package/tsconfig.json +2 -2
  79. package/vite.config.js +22 -22
@@ -1,565 +1,571 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- switchMap,
5
- first,
6
- map,
7
- takeUntil,
8
- Observable,
9
- distinctUntilChanged,
10
- Subject,
11
- BehaviorSubject } from 'rxjs'
12
- import {
13
- defineSeriesPlugin } from '@orbcharts/core'
14
- import type {
15
- ComputedDatumSeries,
16
- SeriesContainerPosition,
17
- EventSeries,
18
- ChartParams } from '@orbcharts/core'
19
- import type { RoseLabelsParams } from '../types'
20
- import type { PieDatum } from '../seriesUtils'
21
- import { DEFAULT_ROSE_LABELS_PARAMS } from '../defaults'
22
- // import { makePieData } from '../seriesUtils'
23
- import { makeD3Arc } from '../../utils/d3Utils'
24
- import { getDatumColor, getClassName } from '../../utils/orbchartsUtils'
25
- import { seriesCenterSelectionObservable } from '../seriesObservables'
26
-
27
- interface RenderDatum {
28
- pieDatum: PieDatum
29
- arcIndex: number
30
- arcLabels: string[]
31
- lineStartX: number
32
- lineStartY: number
33
- lineStartMouseoverX: number
34
- lineStartMouseoverY: number
35
- x: number
36
- y: number
37
- mouseoverX: number
38
- mouseoverY: number
39
- textWidth: number, // 文字寬度
40
- collisionShiftX: number // 避免碰撞的位移
41
- collisionShiftY: number
42
- quadrant: number // 第幾象限
43
- }
44
-
45
- const pluginName = 'RoseLabels'
46
- const labelGClassName = getClassName(pluginName, 'label-g')
47
- const lineGClassName = getClassName(pluginName, 'line-g')
48
- const textClassName = getClassName(pluginName, 'text')
49
-
50
- const pieOuterCentroid = 2
51
-
52
- function makeRenderData ({ pieData, labelCentroid, arcScaleType, maxValue, axisWidth, outerRadius, lineStartCentroid, fullParams }: {
53
- pieData: PieDatum[]
54
- // arc: d3.Arc<any, d3.DefaultArcObject>
55
- labelCentroid: number
56
- arcScaleType: 'area' | 'radius'
57
- maxValue: number
58
- axisWidth: number
59
- outerRadius: number
60
- lineStartCentroid: number
61
- fullParams: RoseLabelsParams
62
- }): RenderDatum[] {
63
-
64
- const outerRadiusWidth = (axisWidth / 2) * outerRadius
65
-
66
- const exponent = arcScaleType === 'area'
67
- ? 0.5 // 比例映射面積(0.5為取平方根)
68
- : 1 // 比例映射半徑
69
-
70
- const arcScale = d3.scalePow()
71
- .domain([0, maxValue])
72
- .range([0, outerRadiusWidth])
73
- .exponent(exponent)
74
-
75
- return pieData
76
- .map((d, i) => {
77
- const eachOuterRadius = arcScale(d.value)
78
-
79
- const arc = d3.arc()
80
- .innerRadius(0)
81
- .outerRadius(eachOuterRadius)
82
- .padAngle(0)
83
- .padRadius(eachOuterRadius)
84
- .cornerRadius(0)
85
-
86
- const [_x, _y] = arc!.centroid(d as any)
87
- const [_mouseoverX, _mouseoverY] = [_x, _y]
88
- const arcLabel = fullParams.labelFn(d.data)
89
- return {
90
- pieDatum: d,
91
- arcIndex: i,
92
- arcLabels: arcLabel.split('\n'),
93
- lineStartX: _x * lineStartCentroid,
94
- lineStartY: _y * lineStartCentroid,
95
- lineStartMouseoverX: _mouseoverX * lineStartCentroid,
96
- lineStartMouseoverY: _mouseoverY * lineStartCentroid,
97
- x: _x * labelCentroid!,
98
- y: _y * labelCentroid!,
99
- mouseoverX: _mouseoverX * labelCentroid!,
100
- mouseoverY: _mouseoverY * labelCentroid!,
101
- textWidth: 0, // 後面再做計算
102
- collisionShiftX: 0, // 後面再做計算
103
- collisionShiftY: 0, // 後面再做計算
104
- quadrant: _x >= 0 && _y <= 0
105
- ? 1
106
- : _x < 0 && _y <= 0
107
- ? 2
108
- : _x < 0 && _y > 0
109
- ? 3
110
- : 4
111
- }
112
- })
113
- .filter(d => d.pieDatum.data.visible)
114
- }
115
-
116
- // 繪製圓餅圖
117
- function renderLabel ({ labelGSelection, data, fullParams, fullChartParams, textSizePx }: {
118
- labelGSelection: d3.Selection<SVGGElement, undefined, any, any>
119
- data: RenderDatum[]
120
- fullParams: RoseLabelsParams
121
- fullChartParams: ChartParams
122
- textSizePx: number
123
- }) {
124
- // console.log(data)
125
- // let update = this.gSelection.selectAll('g').data(pieData)
126
- const textSelection = labelGSelection
127
- .selectAll<SVGTextElement, RenderDatum>('text')
128
- .data(data, d => d.pieDatum.id)
129
- .join('text')
130
- .classed(textClassName, true)
131
- .attr('font-weight', 'bold')
132
- .attr('text-anchor', d => d.quadrant == 1 || d.quadrant == 4 ? 'start' : 'end')
133
- .style('dominant-baseline', d => d.quadrant == 1 || d.quadrant == 2 ? 'auto' : 'hanging')
134
- // .style('pointer-events', 'none')
135
- .style('cursor', d => fullChartParams.highlightTarget && fullChartParams.highlightTarget != 'none'
136
- ? 'pointer'
137
- : 'none')
138
- // .text((d, i) => d.arcLabel)
139
- // .text(d => fullParams.labelFn(d.pieDatum.data))
140
- .attr('font-size', fullChartParams.styles.textSize)
141
- .attr('fill', (d, i) => getDatumColor({ datum: d.pieDatum.data, colorType: fullParams.labelColorType, fullChartParams }))
142
- .each((d, i, n) => {
143
- const textNode = d3.select<SVGTextElement, RenderDatum>(n[i])
144
- .selectAll('tspan')
145
- .data(d.arcLabels)
146
- .join('tspan')
147
- .attr('x', 0)
148
- .attr('y', (_d, _i) => d.quadrant == 1 || d.quadrant == 2
149
- ? - (d.arcLabels.length - 1 - _i) * textSizePx
150
- : _i * textSizePx)
151
- .text(d => d)
152
- })
153
- textSelection
154
- .transition()
155
- .attr('transform', (d) => {
156
- return 'translate(' + d.x + ',' + d.y + ')'
157
- })
158
- // .on('end', () => initHighlight({ labelSelection, data, fullChartParams }))
159
-
160
- // 如無新增資料則不用等動畫
161
- // if (enter.size() == 0) {
162
- // this.initHighlight()
163
- // }
164
-
165
- return textSelection
166
- }
167
-
168
- // 獲取每個文字元素的邊界框並檢查是否重疊
169
- function resolveCollisions(textSelection: d3.Selection<SVGTextElement, RenderDatum, any, any>, data: RenderDatum[], textSizePx: number) {
170
- const textArray = textSelection.nodes();
171
- const padding = textSizePx // 調整文字間的間距
172
-
173
- // 存儲每個標籤的當前位置
174
- const positions = textArray.map((textNode, i) => {
175
- const bbox = textNode.getBBox();
176
- // const arcCentroid = arc.centroid(data[i]);
177
- const arcCentroid = [data[i].x, data[i].y];
178
- return {
179
- node: textNode,
180
- x: arcCentroid[0],
181
- y: arcCentroid[1],
182
- width: bbox.width,
183
- height: bbox.height
184
- }
185
- })
186
-
187
- // 順時針碰撞檢測(只處理 2、4 象限,將較後面的文字碰撞時往外偏移)
188
- for (let i = 0; i < positions.length; i++) {
189
- const a = positions[i]
190
-
191
- for (let j = i + 1; j < positions.length; j++) {
192
- const b = positions[j]
193
-
194
- // 記錄文字寬度
195
- data[i].textWidth = a.width
196
-
197
- const ax = a.x + data[i].collisionShiftX
198
- const ay = a.y + data[i].collisionShiftY
199
- const bx = b.x + data[j].collisionShiftX
200
- const by = b.y + data[j].collisionShiftY
201
-
202
- // 檢查是否重疊
203
- if (!(ax + a.width / 2 < bx - b.width / 2 ||
204
- ax - a.width / 2 > bx + b.width / 2 ||
205
- ay + a.height / 2 < by - b.height / 2 ||
206
- ay - a.height / 2 > by + b.height / 2)) {
207
-
208
- if (data[j].quadrant == 2) {
209
- const moveDown = (by > ay)
210
- ? -padding * 2
211
- : -padding
212
- data[j].collisionShiftY += moveDown // 由後一個累加高度
213
- } else if (data[j].quadrant == 4) {
214
- const moveDown = (by > ay)
215
- ? padding
216
- : padding * 2
217
- data[j].collisionShiftY += moveDown // 由後一個累加高度
218
- }
219
- }
220
- }
221
- }
222
-
223
- // 逆時針碰撞檢測(只處理 1、3 象限,將較前面的文字碰撞時往外偏移)
224
- for (let i = positions.length - 1; i >= 0; i--) {
225
- const a = positions[i]
226
-
227
- for (let j = i - 1; j >= 0; j--) {
228
- const b = positions[j]
229
-
230
- // 記錄文字寬度
231
- data[i].textWidth = a.width
232
-
233
- const ax = a.x + data[i].collisionShiftX
234
- const ay = a.y + data[i].collisionShiftY
235
- const bx = b.x + data[j].collisionShiftX
236
- const by = b.y + data[j].collisionShiftY
237
-
238
- // 檢查是否重疊
239
- if (!(ax + a.width / 2 < bx - b.width / 2 ||
240
- ax - a.width / 2 > bx + b.width / 2 ||
241
- ay + a.height / 2 < by - b.height / 2 ||
242
- ay - a.height / 2 > by + b.height / 2)) {
243
-
244
- if (data[j].quadrant == 1) {
245
- const moveDown = (by > ay)
246
- ? -padding * 2
247
- : -padding
248
- data[j].collisionShiftY += moveDown // 由前一個累加高度
249
- } else if (data[j].quadrant == 3) {
250
- const moveDown = (by > ay)
251
- ? padding
252
- : padding * 2
253
- data[j].collisionShiftY += moveDown // 由前一個累加高度
254
- }
255
- }
256
- }
257
- }
258
-
259
- // 全部算完再來 render
260
- textSelection
261
- .data(data)
262
- .transition()
263
- .attr('transform', (d) => {
264
- return `translate(${d.x + d.collisionShiftX},${d.y + d.collisionShiftY})`
265
- })
266
- }
267
-
268
- function renderLine ({ lineGSelection, data, fullParams, fullChartParams }: {
269
- lineGSelection: d3.Selection<SVGGElement, undefined, any, any>
270
- data: RenderDatum[]
271
- fullParams: RoseLabelsParams
272
- fullChartParams: ChartParams
273
- }) {
274
-
275
- // 只顯示在有偏移的標籤
276
- const filteredData = data.filter(d => d.collisionShiftX || d.collisionShiftY)
277
-
278
- // 添加標籤的連接線
279
- const lines = lineGSelection.selectAll<SVGPolylineElement, RenderDatum>("polyline")
280
- .data(filteredData, d => d.pieDatum.id)
281
- .join("polyline")
282
- .attr("stroke", d => getDatumColor({ datum: d.pieDatum.data, colorType: fullParams.labelColorType, fullChartParams }))
283
- .attr("stroke-width", 1)
284
- .attr("fill", "none")
285
- .attr("points", (d) => {
286
- return [[d.lineStartX, d.lineStartY], [d.lineStartX, d.lineStartY]] as any // 畫出從弧線中心到延伸點的線
287
- })
288
- lines
289
- .transition()
290
- .attr("points", (d) => {
291
- // const pos = arc.centroid(d) // 起點:弧線的中心點
292
- // const outerPos = [pos[0] * 2.5, pos[1] * 2.5] // 外部延伸的點(乘以倍率來延長線段)
293
-
294
- let lineEndX = d.x + d.collisionShiftX
295
- let lineEndY = d.y + d.collisionShiftY
296
- // if (d.lineStartX >= Math.abs(d.lineStartY)) {
297
- // lineEndX -= d.textWidth / 2
298
- // } else if (d.lineStartX <= - Math.abs(d.lineStartY)) {
299
- // lineEndX += d.textWidth / 2
300
- // }
301
-
302
- return [[lineEndX, lineEndY], [d.lineStartX, d.lineStartY]] as any // 畫出從弧線中心到延伸點的線
303
- })
304
-
305
- return lines
306
- }
307
-
308
- function highlight ({ textSelection, lineSelection, ids, fullChartParams }: {
309
- textSelection: d3.Selection<SVGTextElement, RenderDatum, any, any>
310
- lineSelection: d3.Selection<SVGPolylineElement, RenderDatum, any, any>
311
- ids: string[]
312
- fullChartParams: ChartParams
313
- }) {
314
- textSelection.interrupt('highlight')
315
- lineSelection.interrupt('highlight')
316
-
317
- if (!ids.length) {
318
- textSelection
319
- .transition('highlight')
320
- .duration(200)
321
- .attr('transform', (d) => {
322
- return `translate(${d.x + d.collisionShiftX},${d.y + d.collisionShiftY})`
323
- })
324
- .style('opacity', 1)
325
- lineSelection
326
- .transition('highlight')
327
- .duration(200)
328
- .style('opacity', 1)
329
- return
330
- }
331
-
332
- textSelection.each((d, i, n) => {
333
- const segment = d3.select<SVGTextElement, RenderDatum>(n[i])
334
-
335
- if (ids.includes(d.pieDatum.data.id)) {
336
- segment
337
- .style('opacity', 1)
338
- .transition('highlight')
339
- .duration(200)
340
- .attr('transform', (d) => {
341
- return `translate(${d.mouseoverX + d.collisionShiftX},${d.mouseoverY + d.collisionShiftY})`
342
- })
343
- } else {
344
- segment
345
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
346
- .transition('highlight')
347
- .duration(200)
348
- .attr('transform', (d) => {
349
- return `translate(${d.x + d.collisionShiftX},${d.y + d.collisionShiftY})`
350
- })
351
- }
352
- })
353
- lineSelection.each((d, i, n) => {
354
- const segment = d3.select<SVGPolylineElement, RenderDatum>(n[i])
355
-
356
- if (ids.includes(d.pieDatum.data.id)) {
357
- segment
358
- .style('opacity', 1)
359
- .transition('highlight')
360
- .duration(200)
361
- } else {
362
- segment
363
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
364
- .transition('highlight')
365
- .duration(200)
366
- }
367
- })
368
- }
369
-
370
-
371
- function createEachPieLabel (pluginName: string, context: {
372
- containerSelection: d3.Selection<SVGGElement, any, any, unknown>
373
- // computedData$: Observable<ComputedDatumSeries[][]>
374
- visibleComputedLayoutData$: Observable<ComputedDatumSeries[][]>
375
- containerVisibleComputedLayoutData$: Observable<ComputedDatumSeries[]>
376
- // SeriesDataMap$: Observable<Map<string, ComputedDatumSeries[]>>
377
- fullParams$: Observable<RoseLabelsParams>
378
- fullChartParams$: Observable<ChartParams>
379
- textSizePx$: Observable<number>
380
- seriesHighlight$: Observable<ComputedDatumSeries[]>
381
- seriesContainerPosition$: Observable<SeriesContainerPosition>
382
- event$: Subject<EventSeries>
383
- }) {
384
- const destroy$ = new Subject()
385
-
386
- context.containerSelection.selectAll('g').remove()
387
-
388
- const lineGSelection: d3.Selection<SVGGElement, any, any, unknown> = context.containerSelection.append('g')
389
- lineGSelection.classed(lineGClassName, true)
390
- const labelGSelection: d3.Selection<SVGGElement, any, any, unknown> = context.containerSelection.append('g')
391
- labelGSelection.classed(labelGClassName, true)
392
-
393
- const textSelection$: Subject<d3.Selection<SVGTextElement, RenderDatum, any, any>> = new Subject()
394
- const lineSelection$: Subject<d3.Selection<SVGPolylineElement, RenderDatum, any, any>> = new Subject()
395
- let renderData: RenderDatum[] = []
396
-
397
- const shorterSideWith$ = context.seriesContainerPosition$.pipe(
398
- takeUntil(destroy$),
399
- map(d => d.width < d.height ? d.width : d.height),
400
- distinctUntilChanged()
401
- )
402
-
403
- const maxValue$ = context.visibleComputedLayoutData$.pipe(
404
- map(data => Math.max(...data.flat().map(d => d.value))),
405
- distinctUntilChanged()
406
- )
407
-
408
- const lineStartCentroid$ = context.fullParams$.pipe(
409
- takeUntil(destroy$),
410
- map(d => {
411
- return d.labelCentroid >= pieOuterCentroid
412
- ? pieOuterCentroid // 當 label在 pie的外側時,線條從 pie的邊緣開始
413
- : d.labelCentroid // 當 label在 pie的內側時,線條從 label未偏移前的位置開始
414
-
415
- })
416
- )
417
-
418
- combineLatest({
419
- // layout: context.seriesContainerPosition$,
420
- shorterSideWith: shorterSideWith$,
421
- containerVisibleComputedLayoutData: context.containerVisibleComputedLayoutData$,
422
- maxValue: maxValue$,
423
- fullParams: context.fullParams$,
424
- fullChartParams: context.fullChartParams$,
425
- textSizePx: context.textSizePx$,
426
- lineStartCentroid: lineStartCentroid$
427
- }).pipe(
428
- takeUntil(destroy$),
429
- switchMap(async (d) => d),
430
- ).subscribe(data => {
431
-
432
- const eachAngle = Math.PI * 2 / data.containerVisibleComputedLayoutData.length
433
-
434
- const pieData = data.containerVisibleComputedLayoutData.map((d, i) => {
435
- return {
436
- id: d.id,
437
- data: d,
438
- index: i,
439
- value: d.value,
440
- startAngle: eachAngle * i,
441
- endAngle: eachAngle * (i + 1),
442
- padAngle: 0,
443
- // prevValue: lastPieData[i] ? lastPieData[i].value : 0
444
- }
445
- })
446
-
447
- renderData = makeRenderData({
448
- pieData,
449
- labelCentroid: data.fullParams.labelCentroid,
450
- arcScaleType: data.fullParams.arcScaleType,
451
- maxValue: data.maxValue,
452
- axisWidth: data.shorterSideWith,
453
- outerRadius: data.fullParams.outerRadius,
454
- lineStartCentroid: data.lineStartCentroid,
455
- fullParams: data.fullParams
456
- })
457
-
458
- // 先移除線條,等偏移後再重新繪製
459
- lineGSelection.selectAll('polyline').remove()
460
-
461
- const textSelection = renderLabel({
462
- labelGSelection,
463
- data: renderData,
464
- fullParams: data.fullParams,
465
- fullChartParams: data.fullChartParams,
466
- textSizePx: data.textSizePx
467
- })
468
-
469
- // 等 label 本身的 transition 結束後再進行碰撞檢測
470
- setTimeout(() => {
471
- // 偏移 label
472
- resolveCollisions(textSelection, renderData, data.textSizePx)
473
-
474
- const lineSelection = renderLine({ lineGSelection, data: renderData, fullParams: data.fullParams, fullChartParams: data.fullChartParams })
475
-
476
- lineSelection$.next(lineSelection)
477
-
478
- }, 1000)
479
-
480
- textSelection$.next(textSelection)
481
-
482
- })
483
-
484
- combineLatest({
485
- textSelection: textSelection$,
486
- lineSelection: lineSelection$,
487
- highlight: context.seriesHighlight$.pipe(
488
- map(data => data.map(d => d.id))
489
- ),
490
- fullChartParams: context.fullChartParams$,
491
- }).pipe(
492
- takeUntil(destroy$),
493
- switchMap(async d => d)
494
- ).subscribe(data => {
495
- highlight({
496
- textSelection: data.textSelection,
497
- lineSelection: data.lineSelection,
498
- ids: data.highlight,
499
- fullChartParams: data.fullChartParams,
500
- })
501
- })
502
-
503
- return () => {
504
- destroy$.next(undefined)
505
- }
506
- }
507
-
508
-
509
- export const RoseLabels = defineSeriesPlugin(pluginName, DEFAULT_ROSE_LABELS_PARAMS)(({ selection, observer, subject }) => {
510
-
511
- const destroy$ = new Subject()
512
-
513
- const { seriesCenterSelection$ } = seriesCenterSelectionObservable({
514
- selection: selection,
515
- pluginName,
516
- separateSeries$: observer.separateSeries$,
517
- seriesLabels$: observer.seriesLabels$,
518
- seriesContainerPosition$: observer.seriesContainerPosition$
519
- })
520
-
521
- const unsubscribeFnArr: (() => void)[] = []
522
-
523
- seriesCenterSelection$
524
- .pipe(
525
- takeUntil(destroy$)
526
- )
527
- .subscribe(seriesCenterSelection => {
528
- // 每次重新計算時,清除之前的訂閱
529
- unsubscribeFnArr.forEach(fn => fn())
530
-
531
- seriesCenterSelection.each((d, containerIndex, g) => {
532
-
533
- const containerSelection = d3.select(g[containerIndex])
534
-
535
- const containerVisibleComputedLayoutData$ = observer.visibleComputedLayoutData$.pipe(
536
- takeUntil(destroy$),
537
- map(data => JSON.parse(JSON.stringify(data[containerIndex] ?? data[0])))
538
- )
539
-
540
- const containerPosition$ = observer.seriesContainerPosition$.pipe(
541
- takeUntil(destroy$),
542
- map(data => JSON.parse(JSON.stringify(data[containerIndex] ?? data[0])))
543
- )
544
-
545
- unsubscribeFnArr[containerIndex] = createEachPieLabel(pluginName, {
546
- containerSelection: containerSelection,
547
- // computedData$: observer.computedData$,
548
- visibleComputedLayoutData$: observer.visibleComputedLayoutData$,
549
- containerVisibleComputedLayoutData$: containerVisibleComputedLayoutData$,
550
- // SeriesDataMap$: observer.SeriesDataMap$,
551
- fullParams$: observer.fullParams$,
552
- fullChartParams$: observer.fullChartParams$,
553
- textSizePx$: observer.textSizePx$,
554
- seriesHighlight$: observer.seriesHighlight$,
555
- seriesContainerPosition$: containerPosition$,
556
- event$: subject.event$,
557
- })
558
-
559
- })
560
- })
561
-
562
- return () => {
563
- destroy$.next(undefined)
564
- }
565
- })
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ switchMap,
5
+ first,
6
+ map,
7
+ takeUntil,
8
+ Observable,
9
+ distinctUntilChanged,
10
+ Subject,
11
+ BehaviorSubject } from 'rxjs'
12
+ import {
13
+ defineSeriesPlugin } from '@orbcharts/core'
14
+ import type {
15
+ ComputedDatumSeries,
16
+ SeriesContainerPosition,
17
+ EventSeries,
18
+ ChartParams } from '@orbcharts/core'
19
+ import type { RoseLabelsParams } from '../types'
20
+ import type { PieDatum } from '../seriesUtils'
21
+ import { DEFAULT_ROSE_LABELS_PARAMS } from '../defaults'
22
+ // import { makePieData } from '../seriesUtils'
23
+ import { makeD3Arc } from '../../utils/d3Utils'
24
+ import { getDatumColor, getClassName } from '../../utils/orbchartsUtils'
25
+ import { seriesCenterSelectionObservable } from '../seriesObservables'
26
+ import { renderTspansOnQuadrant } from '../../utils/d3Graphics'
27
+
28
+ interface RenderDatum {
29
+ pieDatum: PieDatum
30
+ arcIndex: number
31
+ arcLabels: string[]
32
+ lineStartX: number
33
+ lineStartY: number
34
+ lineStartMouseoverX: number
35
+ lineStartMouseoverY: number
36
+ x: number
37
+ y: number
38
+ mouseoverX: number
39
+ mouseoverY: number
40
+ textWidth: number, // 文字寬度
41
+ collisionShiftX: number // 避免碰撞的位移
42
+ collisionShiftY: number
43
+ quadrant: number // 第幾象限
44
+ }
45
+
46
+ const pluginName = 'RoseLabels'
47
+ const labelGClassName = getClassName(pluginName, 'label-g')
48
+ const lineGClassName = getClassName(pluginName, 'line-g')
49
+ const textClassName = getClassName(pluginName, 'text')
50
+
51
+ const pieOuterCentroid = 2
52
+
53
+ function makeRenderData ({ pieData, labelCentroid, arcScaleType, maxValue, axisWidth, outerRadius, lineStartCentroid, fullParams }: {
54
+ pieData: PieDatum[]
55
+ // arc: d3.Arc<any, d3.DefaultArcObject>
56
+ labelCentroid: number
57
+ arcScaleType: 'area' | 'radius'
58
+ maxValue: number
59
+ axisWidth: number
60
+ outerRadius: number
61
+ lineStartCentroid: number
62
+ fullParams: RoseLabelsParams
63
+ }): RenderDatum[] {
64
+
65
+ const outerRadiusWidth = (axisWidth / 2) * outerRadius
66
+
67
+ const exponent = arcScaleType === 'area'
68
+ ? 0.5 // 比例映射面積(0.5為取平方根)
69
+ : 1 // 比例映射半徑
70
+
71
+ const arcScale = d3.scalePow()
72
+ .domain([0, maxValue])
73
+ .range([0, outerRadiusWidth])
74
+ .exponent(exponent)
75
+
76
+ return pieData
77
+ .map((d, i) => {
78
+ const eachOuterRadius = arcScale(d.value)
79
+
80
+ const arc = d3.arc()
81
+ .innerRadius(0)
82
+ .outerRadius(eachOuterRadius)
83
+ .padAngle(0)
84
+ .padRadius(eachOuterRadius)
85
+ .cornerRadius(0)
86
+
87
+ const [_x, _y] = arc!.centroid(d as any)
88
+ const [_mouseoverX, _mouseoverY] = [_x, _y]
89
+ const arcLabel = fullParams.labelFn(d.data)
90
+ return {
91
+ pieDatum: d,
92
+ arcIndex: i,
93
+ arcLabels: arcLabel.split('\n'),
94
+ lineStartX: _x * lineStartCentroid,
95
+ lineStartY: _y * lineStartCentroid,
96
+ lineStartMouseoverX: _mouseoverX * lineStartCentroid,
97
+ lineStartMouseoverY: _mouseoverY * lineStartCentroid,
98
+ x: _x * labelCentroid!,
99
+ y: _y * labelCentroid!,
100
+ mouseoverX: _mouseoverX * labelCentroid!,
101
+ mouseoverY: _mouseoverY * labelCentroid!,
102
+ textWidth: 0, // 後面再做計算
103
+ collisionShiftX: 0, // 後面再做計算
104
+ collisionShiftY: 0, // 後面再做計算
105
+ quadrant: _x >= 0 && _y <= 0
106
+ ? 1
107
+ : _x < 0 && _y <= 0
108
+ ? 2
109
+ : _x < 0 && _y > 0
110
+ ? 3
111
+ : 4
112
+ }
113
+ })
114
+ .filter(d => d.pieDatum.data.visible)
115
+ }
116
+
117
+ // 繪製圓餅圖
118
+ function renderLabel ({ labelGSelection, data, fullParams, fullChartParams, textSizePx }: {
119
+ labelGSelection: d3.Selection<SVGGElement, undefined, any, any>
120
+ data: RenderDatum[]
121
+ fullParams: RoseLabelsParams
122
+ fullChartParams: ChartParams
123
+ textSizePx: number
124
+ }) {
125
+ // console.log(data)
126
+ // let update = this.gSelection.selectAll('g').data(pieData)
127
+ const textSelection = labelGSelection
128
+ .selectAll<SVGTextElement, RenderDatum>('text')
129
+ .data(data, d => d.pieDatum.id)
130
+ .join('text')
131
+ .classed(textClassName, true)
132
+ .attr('font-weight', 'bold')
133
+ .attr('text-anchor', d => d.quadrant == 1 || d.quadrant == 4 ? 'start' : 'end')
134
+ .style('dominant-baseline', d => d.quadrant == 1 || d.quadrant == 2 ? 'auto' : 'hanging')
135
+ // .style('pointer-events', 'none')
136
+ .style('cursor', d => fullChartParams.highlightTarget && fullChartParams.highlightTarget != 'none'
137
+ ? 'pointer'
138
+ : 'none')
139
+ // .text((d, i) => d.arcLabel)
140
+ // .text(d => fullParams.labelFn(d.pieDatum.data))
141
+ .attr('font-size', fullChartParams.styles.textSize)
142
+ .attr('fill', (d, i) => getDatumColor({ datum: d.pieDatum.data, colorType: fullParams.labelColorType, fullChartParams }))
143
+ .each((d, i, n) => {
144
+ // const textNode = d3.select<SVGTextElement, RenderDatum>(n[i])
145
+ // .selectAll('tspan')
146
+ // .data(d.arcLabels)
147
+ // .join('tspan')
148
+ // .attr('x', 0)
149
+ // .attr('y', (_d, _i) => d.quadrant == 1 || d.quadrant == 2
150
+ // ? - (d.arcLabels.length - 1 - _i) * textSizePx
151
+ // : _i * textSizePx)
152
+ // .text(d => d)
153
+ renderTspansOnQuadrant(d3.select<SVGTextElement, RenderDatum>(n[i]), {
154
+ textArr: d.arcLabels,
155
+ textSizePx,
156
+ quadrant: d.quadrant
157
+ })
158
+ })
159
+ textSelection
160
+ .transition()
161
+ .attr('transform', (d) => {
162
+ return 'translate(' + d.x + ',' + d.y + ')'
163
+ })
164
+ // .on('end', () => initHighlight({ labelSelection, data, fullChartParams }))
165
+
166
+ // 如無新增資料則不用等動畫
167
+ // if (enter.size() == 0) {
168
+ // this.initHighlight()
169
+ // }
170
+
171
+ return textSelection
172
+ }
173
+
174
+ // 獲取每個文字元素的邊界框並檢查是否重疊
175
+ function resolveCollisions(textSelection: d3.Selection<SVGTextElement, RenderDatum, any, any>, data: RenderDatum[], textSizePx: number) {
176
+ const textArray = textSelection.nodes();
177
+ const padding = textSizePx // 調整文字間的間距
178
+
179
+ // 存儲每個標籤的當前位置
180
+ const positions = textArray.map((textNode, i) => {
181
+ const bbox = textNode.getBBox();
182
+ // const arcCentroid = arc.centroid(data[i]);
183
+ const arcCentroid = [data[i].x, data[i].y];
184
+ return {
185
+ node: textNode,
186
+ x: arcCentroid[0],
187
+ y: arcCentroid[1],
188
+ width: bbox.width,
189
+ height: bbox.height
190
+ }
191
+ })
192
+
193
+ // 順時針碰撞檢測(只處理 2、4 象限,將較後面的文字碰撞時往外偏移)
194
+ for (let i = 0; i < positions.length; i++) {
195
+ const a = positions[i]
196
+
197
+ for (let j = i + 1; j < positions.length; j++) {
198
+ const b = positions[j]
199
+
200
+ // 記錄文字寬度
201
+ data[i].textWidth = a.width
202
+
203
+ const ax = a.x + data[i].collisionShiftX
204
+ const ay = a.y + data[i].collisionShiftY
205
+ const bx = b.x + data[j].collisionShiftX
206
+ const by = b.y + data[j].collisionShiftY
207
+
208
+ // 檢查是否重疊
209
+ if (!(ax + a.width / 2 < bx - b.width / 2 ||
210
+ ax - a.width / 2 > bx + b.width / 2 ||
211
+ ay + a.height / 2 < by - b.height / 2 ||
212
+ ay - a.height / 2 > by + b.height / 2)) {
213
+
214
+ if (data[j].quadrant == 2) {
215
+ const moveDown = (by > ay)
216
+ ? -padding * 2
217
+ : -padding
218
+ data[j].collisionShiftY += moveDown // 由後一個累加高度
219
+ } else if (data[j].quadrant == 4) {
220
+ const moveDown = (by > ay)
221
+ ? padding
222
+ : padding * 2
223
+ data[j].collisionShiftY += moveDown // 由後一個累加高度
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ // 逆時針碰撞檢測(只處理 1、3 象限,將較前面的文字碰撞時往外偏移)
230
+ for (let i = positions.length - 1; i >= 0; i--) {
231
+ const a = positions[i]
232
+
233
+ for (let j = i - 1; j >= 0; j--) {
234
+ const b = positions[j]
235
+
236
+ // 記錄文字寬度
237
+ data[i].textWidth = a.width
238
+
239
+ const ax = a.x + data[i].collisionShiftX
240
+ const ay = a.y + data[i].collisionShiftY
241
+ const bx = b.x + data[j].collisionShiftX
242
+ const by = b.y + data[j].collisionShiftY
243
+
244
+ // 檢查是否重疊
245
+ if (!(ax + a.width / 2 < bx - b.width / 2 ||
246
+ ax - a.width / 2 > bx + b.width / 2 ||
247
+ ay + a.height / 2 < by - b.height / 2 ||
248
+ ay - a.height / 2 > by + b.height / 2)) {
249
+
250
+ if (data[j].quadrant == 1) {
251
+ const moveDown = (by > ay)
252
+ ? -padding * 2
253
+ : -padding
254
+ data[j].collisionShiftY += moveDown // 由前一個累加高度
255
+ } else if (data[j].quadrant == 3) {
256
+ const moveDown = (by > ay)
257
+ ? padding
258
+ : padding * 2
259
+ data[j].collisionShiftY += moveDown // 由前一個累加高度
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ // 全部算完再來 render
266
+ textSelection
267
+ .data(data)
268
+ .transition()
269
+ .attr('transform', (d) => {
270
+ return `translate(${d.x + d.collisionShiftX},${d.y + d.collisionShiftY})`
271
+ })
272
+ }
273
+
274
+ function renderLine ({ lineGSelection, data, fullParams, fullChartParams }: {
275
+ lineGSelection: d3.Selection<SVGGElement, undefined, any, any>
276
+ data: RenderDatum[]
277
+ fullParams: RoseLabelsParams
278
+ fullChartParams: ChartParams
279
+ }) {
280
+
281
+ // 只顯示在有偏移的標籤
282
+ const filteredData = data.filter(d => d.collisionShiftX || d.collisionShiftY)
283
+
284
+ // 添加標籤的連接線
285
+ const lines = lineGSelection.selectAll<SVGPolylineElement, RenderDatum>("polyline")
286
+ .data(filteredData, d => d.pieDatum.id)
287
+ .join("polyline")
288
+ .attr("stroke", d => getDatumColor({ datum: d.pieDatum.data, colorType: fullParams.labelColorType, fullChartParams }))
289
+ .attr("stroke-width", 1)
290
+ .attr("fill", "none")
291
+ .attr("points", (d) => {
292
+ return [[d.lineStartX, d.lineStartY], [d.lineStartX, d.lineStartY]] as any // 畫出從弧線中心到延伸點的線
293
+ })
294
+ lines
295
+ .transition()
296
+ .attr("points", (d) => {
297
+ // const pos = arc.centroid(d) // 起點:弧線的中心點
298
+ // const outerPos = [pos[0] * 2.5, pos[1] * 2.5] // 外部延伸的點(乘以倍率來延長線段)
299
+
300
+ let lineEndX = d.x + d.collisionShiftX
301
+ let lineEndY = d.y + d.collisionShiftY
302
+ // if (d.lineStartX >= Math.abs(d.lineStartY)) {
303
+ // lineEndX -= d.textWidth / 2
304
+ // } else if (d.lineStartX <= - Math.abs(d.lineStartY)) {
305
+ // lineEndX += d.textWidth / 2
306
+ // }
307
+
308
+ return [[lineEndX, lineEndY], [d.lineStartX, d.lineStartY]] as any // 畫出從弧線中心到延伸點的線
309
+ })
310
+
311
+ return lines
312
+ }
313
+
314
+ function highlight ({ textSelection, lineSelection, ids, fullChartParams }: {
315
+ textSelection: d3.Selection<SVGTextElement, RenderDatum, any, any>
316
+ lineSelection: d3.Selection<SVGPolylineElement, RenderDatum, any, any>
317
+ ids: string[]
318
+ fullChartParams: ChartParams
319
+ }) {
320
+ textSelection.interrupt('highlight')
321
+ lineSelection.interrupt('highlight')
322
+
323
+ if (!ids.length) {
324
+ textSelection
325
+ .transition('highlight')
326
+ .duration(200)
327
+ .attr('transform', (d) => {
328
+ return `translate(${d.x + d.collisionShiftX},${d.y + d.collisionShiftY})`
329
+ })
330
+ .style('opacity', 1)
331
+ lineSelection
332
+ .transition('highlight')
333
+ .duration(200)
334
+ .style('opacity', 1)
335
+ return
336
+ }
337
+
338
+ textSelection.each((d, i, n) => {
339
+ const segment = d3.select<SVGTextElement, RenderDatum>(n[i])
340
+
341
+ if (ids.includes(d.pieDatum.data.id)) {
342
+ segment
343
+ .style('opacity', 1)
344
+ .transition('highlight')
345
+ .duration(200)
346
+ .attr('transform', (d) => {
347
+ return `translate(${d.mouseoverX + d.collisionShiftX},${d.mouseoverY + d.collisionShiftY})`
348
+ })
349
+ } else {
350
+ segment
351
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
352
+ .transition('highlight')
353
+ .duration(200)
354
+ .attr('transform', (d) => {
355
+ return `translate(${d.x + d.collisionShiftX},${d.y + d.collisionShiftY})`
356
+ })
357
+ }
358
+ })
359
+ lineSelection.each((d, i, n) => {
360
+ const segment = d3.select<SVGPolylineElement, RenderDatum>(n[i])
361
+
362
+ if (ids.includes(d.pieDatum.data.id)) {
363
+ segment
364
+ .style('opacity', 1)
365
+ .transition('highlight')
366
+ .duration(200)
367
+ } else {
368
+ segment
369
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
370
+ .transition('highlight')
371
+ .duration(200)
372
+ }
373
+ })
374
+ }
375
+
376
+
377
+ function createEachPieLabel (pluginName: string, context: {
378
+ containerSelection: d3.Selection<SVGGElement, any, any, unknown>
379
+ // computedData$: Observable<ComputedDatumSeries[][]>
380
+ visibleComputedLayoutData$: Observable<ComputedDatumSeries[][]>
381
+ containerVisibleComputedLayoutData$: Observable<ComputedDatumSeries[]>
382
+ // SeriesDataMap$: Observable<Map<string, ComputedDatumSeries[]>>
383
+ fullParams$: Observable<RoseLabelsParams>
384
+ fullChartParams$: Observable<ChartParams>
385
+ textSizePx$: Observable<number>
386
+ seriesHighlight$: Observable<ComputedDatumSeries[]>
387
+ seriesContainerPosition$: Observable<SeriesContainerPosition>
388
+ event$: Subject<EventSeries>
389
+ }) {
390
+ const destroy$ = new Subject()
391
+
392
+ context.containerSelection.selectAll('g').remove()
393
+
394
+ const lineGSelection: d3.Selection<SVGGElement, any, any, unknown> = context.containerSelection.append('g')
395
+ lineGSelection.classed(lineGClassName, true)
396
+ const labelGSelection: d3.Selection<SVGGElement, any, any, unknown> = context.containerSelection.append('g')
397
+ labelGSelection.classed(labelGClassName, true)
398
+
399
+ const textSelection$: Subject<d3.Selection<SVGTextElement, RenderDatum, any, any>> = new Subject()
400
+ const lineSelection$: Subject<d3.Selection<SVGPolylineElement, RenderDatum, any, any>> = new Subject()
401
+ let renderData: RenderDatum[] = []
402
+
403
+ const shorterSideWith$ = context.seriesContainerPosition$.pipe(
404
+ takeUntil(destroy$),
405
+ map(d => d.width < d.height ? d.width : d.height),
406
+ distinctUntilChanged()
407
+ )
408
+
409
+ const maxValue$ = context.visibleComputedLayoutData$.pipe(
410
+ map(data => Math.max(...data.flat().map(d => d.value))),
411
+ distinctUntilChanged()
412
+ )
413
+
414
+ const lineStartCentroid$ = context.fullParams$.pipe(
415
+ takeUntil(destroy$),
416
+ map(d => {
417
+ return d.labelCentroid >= pieOuterCentroid
418
+ ? pieOuterCentroid // 當 label在 pie的外側時,線條從 pie的邊緣開始
419
+ : d.labelCentroid // label在 pie的內側時,線條從 label未偏移前的位置開始
420
+
421
+ })
422
+ )
423
+
424
+ combineLatest({
425
+ // layout: context.seriesContainerPosition$,
426
+ shorterSideWith: shorterSideWith$,
427
+ containerVisibleComputedLayoutData: context.containerVisibleComputedLayoutData$,
428
+ maxValue: maxValue$,
429
+ fullParams: context.fullParams$,
430
+ fullChartParams: context.fullChartParams$,
431
+ textSizePx: context.textSizePx$,
432
+ lineStartCentroid: lineStartCentroid$
433
+ }).pipe(
434
+ takeUntil(destroy$),
435
+ switchMap(async (d) => d),
436
+ ).subscribe(data => {
437
+
438
+ const eachAngle = Math.PI * 2 / data.containerVisibleComputedLayoutData.length
439
+
440
+ const pieData = data.containerVisibleComputedLayoutData.map((d, i) => {
441
+ return {
442
+ id: d.id,
443
+ data: d,
444
+ index: i,
445
+ value: d.value,
446
+ startAngle: eachAngle * i,
447
+ endAngle: eachAngle * (i + 1),
448
+ padAngle: 0,
449
+ // prevValue: lastPieData[i] ? lastPieData[i].value : 0
450
+ }
451
+ })
452
+
453
+ renderData = makeRenderData({
454
+ pieData,
455
+ labelCentroid: data.fullParams.labelCentroid,
456
+ arcScaleType: data.fullParams.arcScaleType,
457
+ maxValue: data.maxValue,
458
+ axisWidth: data.shorterSideWith,
459
+ outerRadius: data.fullParams.outerRadius,
460
+ lineStartCentroid: data.lineStartCentroid,
461
+ fullParams: data.fullParams
462
+ })
463
+
464
+ // 先移除線條,等偏移後再重新繪製
465
+ lineGSelection.selectAll('polyline').remove()
466
+
467
+ const textSelection = renderLabel({
468
+ labelGSelection,
469
+ data: renderData,
470
+ fullParams: data.fullParams,
471
+ fullChartParams: data.fullChartParams,
472
+ textSizePx: data.textSizePx
473
+ })
474
+
475
+ // 等 label 本身的 transition 結束後再進行碰撞檢測
476
+ setTimeout(() => {
477
+ // 偏移 label
478
+ resolveCollisions(textSelection, renderData, data.textSizePx)
479
+
480
+ const lineSelection = renderLine({ lineGSelection, data: renderData, fullParams: data.fullParams, fullChartParams: data.fullChartParams })
481
+
482
+ lineSelection$.next(lineSelection)
483
+
484
+ }, 1000)
485
+
486
+ textSelection$.next(textSelection)
487
+
488
+ })
489
+
490
+ combineLatest({
491
+ textSelection: textSelection$,
492
+ lineSelection: lineSelection$,
493
+ highlight: context.seriesHighlight$.pipe(
494
+ map(data => data.map(d => d.id))
495
+ ),
496
+ fullChartParams: context.fullChartParams$,
497
+ }).pipe(
498
+ takeUntil(destroy$),
499
+ switchMap(async d => d)
500
+ ).subscribe(data => {
501
+ highlight({
502
+ textSelection: data.textSelection,
503
+ lineSelection: data.lineSelection,
504
+ ids: data.highlight,
505
+ fullChartParams: data.fullChartParams,
506
+ })
507
+ })
508
+
509
+ return () => {
510
+ destroy$.next(undefined)
511
+ }
512
+ }
513
+
514
+
515
+ export const RoseLabels = defineSeriesPlugin(pluginName, DEFAULT_ROSE_LABELS_PARAMS)(({ selection, observer, subject }) => {
516
+
517
+ const destroy$ = new Subject()
518
+
519
+ const { seriesCenterSelection$ } = seriesCenterSelectionObservable({
520
+ selection: selection,
521
+ pluginName,
522
+ separateSeries$: observer.separateSeries$,
523
+ seriesLabels$: observer.seriesLabels$,
524
+ seriesContainerPosition$: observer.seriesContainerPosition$
525
+ })
526
+
527
+ const unsubscribeFnArr: (() => void)[] = []
528
+
529
+ seriesCenterSelection$
530
+ .pipe(
531
+ takeUntil(destroy$)
532
+ )
533
+ .subscribe(seriesCenterSelection => {
534
+ // 每次重新計算時,清除之前的訂閱
535
+ unsubscribeFnArr.forEach(fn => fn())
536
+
537
+ seriesCenterSelection.each((d, containerIndex, g) => {
538
+
539
+ const containerSelection = d3.select(g[containerIndex])
540
+
541
+ const containerVisibleComputedLayoutData$ = observer.visibleComputedLayoutData$.pipe(
542
+ takeUntil(destroy$),
543
+ map(data => JSON.parse(JSON.stringify(data[containerIndex] ?? data[0])))
544
+ )
545
+
546
+ const containerPosition$ = observer.seriesContainerPosition$.pipe(
547
+ takeUntil(destroy$),
548
+ map(data => JSON.parse(JSON.stringify(data[containerIndex] ?? data[0])))
549
+ )
550
+
551
+ unsubscribeFnArr[containerIndex] = createEachPieLabel(pluginName, {
552
+ containerSelection: containerSelection,
553
+ // computedData$: observer.computedData$,
554
+ visibleComputedLayoutData$: observer.visibleComputedLayoutData$,
555
+ containerVisibleComputedLayoutData$: containerVisibleComputedLayoutData$,
556
+ // SeriesDataMap$: observer.SeriesDataMap$,
557
+ fullParams$: observer.fullParams$,
558
+ fullChartParams$: observer.fullChartParams$,
559
+ textSizePx$: observer.textSizePx$,
560
+ seriesHighlight$: observer.seriesHighlight$,
561
+ seriesContainerPosition$: containerPosition$,
562
+ event$: subject.event$,
563
+ })
564
+
565
+ })
566
+ })
567
+
568
+ return () => {
569
+ destroy$.next(undefined)
570
+ }
571
+ })