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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ })