@orbcharts/plugins-basic 3.0.0-alpha.41 → 3.0.0-alpha.43

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 (84) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +5477 -5426
  3. package/dist/orbcharts-plugins-basic.umd.js +8 -8
  4. package/dist/src/base/BaseBarStack.d.ts +1 -1
  5. package/dist/src/base/BaseBars.d.ts +1 -1
  6. package/dist/src/base/BaseBarsTriangle.d.ts +1 -1
  7. package/dist/src/base/BaseDots.d.ts +1 -1
  8. package/dist/src/base/BaseLineAreas.d.ts +1 -1
  9. package/dist/src/base/BaseLines.d.ts +1 -1
  10. package/dist/src/multiGrid/defaults.d.ts +2 -1
  11. package/dist/src/multiGrid/index.d.ts +1 -0
  12. package/dist/src/multiGrid/plugins/MultiLineAreas.d.ts +1 -0
  13. package/dist/src/multiGrid/types.d.ts +4 -4
  14. package/package.json +42 -42
  15. package/src/base/BaseBarStack.ts +881 -879
  16. package/src/base/BaseBars.ts +750 -748
  17. package/src/base/BaseBarsTriangle.ts +659 -657
  18. package/src/base/BaseDots.ts +639 -637
  19. package/src/base/BaseGroupAxis.ts +496 -496
  20. package/src/base/BaseLegend.ts +636 -636
  21. package/src/base/BaseLineAreas.ts +621 -624
  22. package/src/base/BaseLines.ts +692 -695
  23. package/src/base/BaseValueAxis.ts +479 -479
  24. package/src/base/types.ts +2 -2
  25. package/src/grid/defaults.ts +121 -121
  26. package/src/grid/gridObservables.ts +263 -263
  27. package/src/grid/index.ts +15 -15
  28. package/src/grid/plugins/BarStack.ts +37 -37
  29. package/src/grid/plugins/Bars.ts +37 -37
  30. package/src/grid/plugins/BarsDiverging.ts +39 -39
  31. package/src/grid/plugins/BarsTriangle.ts +34 -34
  32. package/src/grid/plugins/Dots.ts +35 -35
  33. package/src/grid/plugins/GridLegend.ts +58 -58
  34. package/src/grid/plugins/GroupAux.ts +643 -643
  35. package/src/grid/plugins/GroupAxis.ts +30 -30
  36. package/src/grid/plugins/LineAreas.ts +36 -36
  37. package/src/grid/plugins/Lines.ts +35 -35
  38. package/src/grid/plugins/ScalingArea.ts +174 -174
  39. package/src/grid/plugins/ValueAxis.ts +31 -31
  40. package/src/grid/plugins/ValueStackAxis.ts +70 -70
  41. package/src/grid/types.ts +120 -120
  42. package/src/index.ts +9 -9
  43. package/src/multiGrid/defaults.ts +147 -140
  44. package/src/multiGrid/index.ts +11 -10
  45. package/src/multiGrid/multiGridObservables.ts +289 -278
  46. package/src/multiGrid/plugins/MultiBarStack.ts +60 -60
  47. package/src/multiGrid/plugins/MultiBars.ts +59 -59
  48. package/src/multiGrid/plugins/MultiBarsTriangle.ts +58 -58
  49. package/src/multiGrid/plugins/MultiDots.ts +58 -58
  50. package/src/multiGrid/plugins/MultiGridLegend.ts +88 -88
  51. package/src/multiGrid/plugins/MultiGroupAxis.ts +53 -53
  52. package/src/multiGrid/plugins/MultiLineAreas.ts +59 -0
  53. package/src/multiGrid/plugins/MultiLines.ts +58 -58
  54. package/src/multiGrid/plugins/MultiValueAxis.ts +53 -53
  55. package/src/multiGrid/plugins/OverlappingValueAxes.ts +164 -165
  56. package/src/multiGrid/types.ts +67 -67
  57. package/src/noneData/defaults.ts +61 -61
  58. package/src/noneData/index.ts +3 -3
  59. package/src/noneData/plugins/Container.ts +10 -10
  60. package/src/noneData/plugins/Tooltip.ts +304 -304
  61. package/src/noneData/types.ts +26 -26
  62. package/src/series/defaults.ts +99 -99
  63. package/src/series/index.ts +6 -6
  64. package/src/series/plugins/Bubbles.ts +551 -549
  65. package/src/series/plugins/Pie.ts +600 -598
  66. package/src/series/plugins/PieEventTexts.ts +194 -194
  67. package/src/series/plugins/PieLabels.ts +288 -285
  68. package/src/series/plugins/SeriesLegend.ts +58 -58
  69. package/src/series/seriesUtils.ts +50 -50
  70. package/src/series/types.ts +67 -67
  71. package/src/tree/defaults.ts +22 -22
  72. package/src/tree/index.ts +3 -3
  73. package/src/tree/plugins/TreeLegend.ts +58 -58
  74. package/src/tree/plugins/TreeMap.ts +302 -300
  75. package/src/tree/types.ts +23 -23
  76. package/src/utils/commonUtils.ts +21 -21
  77. package/src/utils/d3Graphics.ts +124 -124
  78. package/src/utils/d3Utils.ts +73 -73
  79. package/src/utils/observables.ts +14 -14
  80. package/src/utils/orbchartsUtils.ts +100 -100
  81. package/tsconfig.dev.json +16 -16
  82. package/tsconfig.json +13 -13
  83. package/tsconfig.prod.json +13 -13
  84. package/vite.config.js +49 -49
@@ -1,637 +1,637 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- map,
5
- switchMap,
6
- takeUntil,
7
- shareReplay,
8
- Observable,
9
- Subject } from 'rxjs'
10
- import type { BasePluginFn } from './types'
11
- import type {
12
- ChartParams, Layout, ColorType } from '@orbcharts/core'
13
- import { getClassName, getColor, getDatumColor } from '../utils/orbchartsUtils'
14
- import { measureTextWidth } from '../utils/commonUtils'
15
-
16
- export interface BaseLegendParams {
17
- position: 'top' | 'bottom' | 'left' | 'right'
18
- justify: 'start' | 'center' | 'end'
19
- padding: number
20
- // offset: [number, number]
21
- backgroundFill: ColorType
22
- backgroundStroke: ColorType
23
- textColorType: ColorType
24
- gap: number
25
- seriesList: Array<{
26
- listRectWidth: number
27
- listRectHeight: number
28
- listRectRadius: number
29
- }>
30
- // highlightEvent: boolean
31
- }
32
-
33
- interface BaseLegendContext {
34
- rootSelection: d3.Selection<any, unknown, any, unknown>
35
- seriesLabels$: Observable<string[]>
36
- fullParams$: Observable<BaseLegendParams>
37
- layout$: Observable<Layout>
38
- fullChartParams$: Observable<ChartParams>
39
- }
40
-
41
- // 第1層 - 定位的容器
42
- interface RootPosition {
43
- x:number
44
- y:number
45
- }
46
-
47
- // 第2層 - 卡片
48
- interface LegendCard {
49
- width: number
50
- height: number
51
- translateX:number
52
- translateY:number
53
- }
54
-
55
- // 第3層 - 圖例列表
56
- interface LegendList {
57
- direction: 'row' | 'column'
58
- width: number
59
- height: number
60
- translateX:number
61
- translateY:number
62
- // list: LegendItem[][]
63
- }
64
-
65
- // 第4層 - 圖例項目
66
- interface LegendItem {
67
- id: string // seriesLabel
68
- seriesLabel: string
69
- seriesIndex: number
70
- lineIndex: number
71
- itemIndex: number // 行內的item
72
- text: string
73
- itemWidth: number
74
- translateX: number
75
- translateY: number
76
- color: string
77
- listRectWidth: number
78
- listRectHeight: number
79
- listRectRadius: number
80
- }
81
-
82
- interface ListStyle {
83
- listRectWidth: number
84
- listRectHeight: number
85
- listRectRadius: number
86
- }
87
-
88
- const defaultListStyle: ListStyle = {
89
- listRectWidth: 14,
90
- listRectHeight: 14,
91
- listRectRadius: 0,
92
- }
93
-
94
- function getSeriesColor (seriesIndex: number, fullChartParams: ChartParams) {
95
- const colorIndex = seriesIndex < fullChartParams.colors[fullChartParams.colorScheme].series.length
96
- ? seriesIndex
97
- : seriesIndex % fullChartParams.colors[fullChartParams.colorScheme].series.length
98
- return fullChartParams.colors[fullChartParams.colorScheme].series[colorIndex]
99
- }
100
-
101
- function getLegendColor () {
102
-
103
- }
104
-
105
- export const createBaseLegend: BasePluginFn<BaseLegendContext> = (pluginName: string, {
106
- rootSelection,
107
- seriesLabels$,
108
- fullParams$,
109
- layout$,
110
- fullChartParams$
111
- }) => {
112
-
113
- const rootPositionClassName = getClassName(pluginName, 'root-position')
114
- const legendCardClassName = getClassName(pluginName, 'legend-card')
115
- const legendListClassName = getClassName(pluginName, 'legend-list')
116
- const legendItemClassName = getClassName(pluginName, 'legend-item')
117
-
118
- const destroy$ = new Subject()
119
-
120
- // const seriesLabels$: Observable<string[]> = SeriesDataMap$.pipe(
121
- // takeUntil(destroy$),
122
- // map(data => {
123
- // return Array.from(data.keys())
124
- // })
125
- // )
126
-
127
- const SeriesLabelColorMap$ = combineLatest({
128
- seriesLabels: seriesLabels$,
129
- fullChartParams: fullChartParams$
130
- }).pipe(
131
- takeUntil(destroy$),
132
- switchMap(async d => d),
133
- map(data => {
134
- const SeriesLabelColorMap: Map<string, string> = new Map()
135
- let accIndex = 0
136
- data.seriesLabels.forEach((label, i) => {
137
- if (!SeriesLabelColorMap.has(label)) {
138
- const color = getSeriesColor(accIndex, data.fullChartParams)
139
- SeriesLabelColorMap.set(label, color)
140
- accIndex ++
141
- }
142
- })
143
- return SeriesLabelColorMap
144
- })
145
- )
146
-
147
- // 對應seriesLabels是否顯示(只顯示不重覆的)
148
- const visibleList$ = seriesLabels$.pipe(
149
- takeUntil(destroy$),
150
- map(data => {
151
- const AccSeriesLabelSet = new Set()
152
- let visibleList: boolean[] = []
153
- data.forEach(d => {
154
- if (AccSeriesLabelSet.has(d)) {
155
- visibleList.push(false) // 已存在則不顯示
156
- } else {
157
- visibleList.push(true)
158
- }
159
- AccSeriesLabelSet.add(d) // 累加已存在的seriesLabel
160
- })
161
- return visibleList
162
- })
163
- )
164
-
165
- const lineDirection$ = fullParams$.pipe(
166
- takeUntil(destroy$),
167
- map(data => {
168
- return data.position === 'bottom' || data.position === 'top'
169
- ? 'row'
170
- : 'column'
171
- })
172
- )
173
-
174
- const lineMaxSize$ = combineLatest({
175
- fullParams: fullParams$,
176
- layout: layout$
177
- }).pipe(
178
- takeUntil(destroy$),
179
- switchMap(async d => d),
180
- map(data => {
181
- const ourterSize = (data.fullParams.padding) * 2 + (data.fullParams.gap * 2) // 卡片離場景的間距 & 卡片內的間距
182
-
183
- return data.fullParams.position === 'bottom' || data.fullParams.position === 'top'
184
- ? data.layout.rootWidth - ourterSize
185
- : data.layout.rootHeight - ourterSize
186
- })
187
- )
188
-
189
- const rootPosition$ = combineLatest({
190
- layout: layout$,
191
- fullParams: fullParams$,
192
- }).pipe(
193
- takeUntil(destroy$),
194
- switchMap(async d => d),
195
- map(data => {
196
- let x = 0
197
- let y = 0
198
- if (data.fullParams.position === 'bottom') {
199
- y = data.layout.rootHeight
200
- if (data.fullParams.justify === 'start') {
201
- x = 0
202
- } else if (data.fullParams.justify === 'center') {
203
- x = data.layout.rootWidth / 2
204
- } else if (data.fullParams.justify === 'end') {
205
- x = data.layout.rootWidth
206
- }
207
- } else if (data.fullParams.position === 'right') {
208
- x = data.layout.rootWidth
209
- if (data.fullParams.justify === 'start') {
210
- y = 0
211
- } else if (data.fullParams.justify === 'center') {
212
- y = data.layout.rootHeight / 2
213
- } else if (data.fullParams.justify === 'end') {
214
- y = data.layout.rootHeight
215
- }
216
- } else if (data.fullParams.position === 'top') {
217
- y = 0
218
- if (data.fullParams.justify === 'start') {
219
- x = 0
220
- } else if (data.fullParams.justify === 'center') {
221
- x = data.layout.rootWidth / 2
222
- } else if (data.fullParams.justify === 'end') {
223
- x = data.layout.rootWidth
224
- }
225
- } else if (data.fullParams.position === 'left') {
226
- x = 0
227
- if (data.fullParams.justify === 'start') {
228
- y = 0
229
- } else if (data.fullParams.justify === 'center') {
230
- y = data.layout.rootHeight / 2
231
- } else if (data.fullParams.justify === 'end') {
232
- y = data.layout.rootHeight
233
- }
234
- }
235
-
236
- return {
237
- x,
238
- y
239
- }
240
- })
241
- )
242
-
243
- const rootPositionSelection$: Observable<d3.Selection<SVGGElement, RootPosition, any, any>> = rootPosition$.pipe(
244
- takeUntil(destroy$),
245
- map(data => {
246
-
247
- return rootSelection
248
- .selectAll<SVGGElement, RootPosition>(`g.${rootPositionClassName}`)
249
- .data([data])
250
- .join(
251
- enter => {
252
- return enter
253
- .append('g')
254
- .classed(rootPositionClassName, true)
255
- .attr('transform', d => `translate(${d.x}, ${d.y})`)
256
- },
257
- update => {
258
- return update
259
- .transition()
260
- .attr('transform', d => `translate(${d.x}, ${d.y})`)
261
- },
262
- exit => exit.remove()
263
- )
264
- })
265
- )
266
-
267
- const defaultListStyle$ = fullParams$.pipe(
268
- takeUntil(destroy$),
269
- map(data => {
270
- return data.seriesList[0] ? data.seriesList[0] : defaultListStyle
271
- })
272
- )
273
-
274
- // 先計算list內每個item
275
- const lengendItems$: Observable<LegendItem[][]> = combineLatest({
276
- visibleList: visibleList$,
277
- fullParams: fullParams$,
278
- fullChartParams: fullChartParams$,
279
- seriesLabels: seriesLabels$,
280
- lineDirection: lineDirection$,
281
- lineMaxSize: lineMaxSize$,
282
- defaultListStyle: defaultListStyle$,
283
- SeriesLabelColorMap: SeriesLabelColorMap$
284
- }).pipe(
285
- takeUntil(destroy$),
286
- switchMap(async d => d),
287
- map(data => {
288
- return data.seriesLabels.reduce((prev: LegendItem[][], current, currentIndex) => {
289
- // visible為flase則不加入
290
- if (!data.visibleList[currentIndex]) {
291
- return prev
292
- }
293
-
294
- const textWidth = measureTextWidth(current, data.fullChartParams.styles.textSize)
295
- const itemWidth = (data.fullChartParams.styles.textSize * 1.5) + textWidth
296
- // const color = getSeriesColor(currentIndex, data.fullChartParams)
297
- const color = data.SeriesLabelColorMap.get(current)
298
- const lastItem: LegendItem | null = prev[0] && prev[0][0]
299
- ? prev[prev.length - 1][prev[prev.length - 1].length - 1]
300
- : null
301
-
302
- const { translateX, translateY, lineIndex, itemIndex } = ((_data, _prev, _lastItem) => {
303
- let translateX = 0
304
- let translateY = 0
305
- let lineIndex = 0
306
- let itemIndex = 0
307
-
308
- if (_data.lineDirection === 'column') {
309
- let tempTranslateY = _lastItem
310
- ? _lastItem.translateY + _data.fullChartParams.styles.textSize + _data.fullParams.gap
311
- : 0
312
-
313
- if ((tempTranslateY + _data.fullChartParams.styles.textSize) > _data.lineMaxSize) {
314
- // 換行
315
- lineIndex = _lastItem.lineIndex + 1
316
- itemIndex = 0
317
- translateY = 0
318
- // 前一行最寬寬度
319
- const maxItemWidthInLastLine = _prev[_prev.length - 1].reduce((p, c) => {
320
- return c.itemWidth > p ? c.itemWidth : p
321
- }, 0)
322
- translateX = _lastItem.translateX + maxItemWidthInLastLine + _data.fullParams.gap
323
- } else {
324
- lineIndex = _lastItem ? _lastItem.lineIndex : 0
325
- itemIndex = _lastItem ? _lastItem.itemIndex + 1 : 0
326
- translateY = tempTranslateY
327
- translateX = _lastItem ? _lastItem.translateX : 0
328
- }
329
- } else {
330
- let tempTranslateX = _lastItem
331
- ? _lastItem.translateX + _lastItem.itemWidth + _data.fullParams.gap
332
- : 0
333
- if ((tempTranslateX + itemWidth) > _data.lineMaxSize) {
334
- // 換行
335
- lineIndex = _lastItem.lineIndex + 1
336
- itemIndex = 0
337
- translateX = 0
338
- } else {
339
- lineIndex = _lastItem ? _lastItem.lineIndex : 0
340
- itemIndex = _lastItem ? _lastItem.itemIndex + 1 : 0
341
- translateX = tempTranslateX
342
- }
343
- translateY = (_data.fullChartParams.styles.textSize + _data.fullParams.gap) * lineIndex
344
- }
345
-
346
- return { translateX, translateY, lineIndex, itemIndex }
347
- })(data, prev, lastItem)
348
-
349
- if (!prev[lineIndex]) {
350
- prev[lineIndex] = []
351
- }
352
-
353
- const listStyle = data.fullParams.seriesList[itemIndex] ? data.fullParams.seriesList[itemIndex] : data.defaultListStyle
354
-
355
- prev[lineIndex].push({
356
- id: current,
357
- seriesLabel: current,
358
- seriesIndex: currentIndex,
359
- lineIndex,
360
- itemIndex,
361
- text: current,
362
- itemWidth,
363
- translateX,
364
- translateY,
365
- color,
366
- listRectWidth: listStyle.listRectWidth,
367
- listRectHeight: listStyle.listRectHeight,
368
- listRectRadius: listStyle.listRectRadius
369
- })
370
-
371
- return prev
372
- }, [])
373
- }),
374
- shareReplay(1)
375
- )
376
-
377
- // 依list計算出來的排序位置來計算整體容器的尺寸
378
- const lengendList$: Observable<LegendList> = combineLatest({
379
- fullParams: fullParams$,
380
- fullChartParams: fullChartParams$,
381
- lineDirection: lineDirection$,
382
- lengendItems: lengendItems$
383
- }).pipe(
384
- takeUntil(destroy$),
385
- switchMap(async d => d),
386
- map(data => {
387
- // 依list計算出來的排序位置來計算整體容器的偏移位置
388
- const { width, height } = ((_data, _lengendItems) => {
389
- let width = 0
390
- let height = 0
391
-
392
- if (!_lengendItems.length || !_lengendItems[0].length) {
393
- return { width, height }
394
- }
395
-
396
- const firstLineLastItem = _lengendItems[0][_lengendItems[0].length - 1]
397
- if (_data.lineDirection === 'column') {
398
- width = _lengendItems.reduce((p, c) => {
399
- const maxWidthInLine = c.reduce((_p, _c) => {
400
- // 找出最寬的寬度
401
- return _c.itemWidth > _p ? _c.itemWidth : _p
402
- }, 0)
403
- // 每行寬度加總
404
- return p + maxWidthInLine
405
- }, 0)
406
- width += _data.fullParams.gap * (_lengendItems.length - 1)
407
- height = firstLineLastItem.translateY + _data.fullChartParams.styles.textSize
408
- } else {
409
- width = firstLineLastItem.translateX + firstLineLastItem.itemWidth
410
- height = (_data.fullChartParams.styles.textSize * _lengendItems.length) + (_data.fullParams.gap * (_lengendItems.length - 1))
411
- }
412
-
413
- return { width, height }
414
- })(data, data.lengendItems)
415
-
416
- return <LegendList>{
417
- direction: data.lineDirection,
418
- width,
419
- height,
420
- translateX: data.fullParams.gap,
421
- translateY: data.fullParams.gap
422
- }
423
- }),
424
- shareReplay(1)
425
- )
426
-
427
- const legendCard$: Observable<LegendCard> = combineLatest({
428
- fullParams: fullParams$,
429
- lengendList: lengendList$
430
- }).pipe(
431
- takeUntil(destroy$),
432
- switchMap(async d => d),
433
- map(data => {
434
- const width = data.lengendList.width + (data.fullParams.gap * 2)
435
- const height = data.lengendList.height + (data.fullParams.gap * 2)
436
- let translateX = 0
437
- let translateY = 0
438
-
439
- if (data.fullParams.position === 'left') {
440
- if (data.fullParams.justify === 'start') {
441
- translateX = data.fullParams.padding
442
- translateY = data.fullParams.padding
443
- } else if (data.fullParams.justify === 'center') {
444
- translateX = data.fullParams.padding
445
- translateY = - height / 2
446
- } else if (data.fullParams.justify === 'end') {
447
- translateX = data.fullParams.padding
448
- translateY = - height - data.fullParams.padding
449
- }
450
- } else if (data.fullParams.position === 'right') {
451
- if (data.fullParams.justify === 'start') {
452
- translateX = - width - data.fullParams.padding
453
- translateY = data.fullParams.padding
454
- } else if (data.fullParams.justify === 'center') {
455
- translateX = - width - data.fullParams.padding
456
- translateY = - height / 2
457
- } else if (data.fullParams.justify === 'end') {
458
- translateX = - width - data.fullParams.padding
459
- translateY = - height - data.fullParams.padding
460
- }
461
- } else if (data.fullParams.position === 'top') {
462
- if (data.fullParams.justify === 'start') {
463
- translateX = data.fullParams.padding
464
- translateY = data.fullParams.padding
465
- } else if (data.fullParams.justify === 'center') {
466
- translateX = - width / 2
467
- translateY = data.fullParams.padding
468
- } else if (data.fullParams.justify === 'end') {
469
- translateX = - width - data.fullParams.padding
470
- translateY = data.fullParams.padding
471
- }
472
- } else {
473
- if (data.fullParams.justify === 'start') {
474
- translateX = data.fullParams.padding
475
- translateY = - height - data.fullParams.padding
476
- } else if (data.fullParams.justify === 'center') {
477
- translateX = - width / 2
478
- translateY = - height - data.fullParams.padding
479
- } else if (data.fullParams.justify === 'end') {
480
- translateX = - width - data.fullParams.padding
481
- translateY = - height - data.fullParams.padding
482
- }
483
- }
484
- // translateX += _data.fullParams.offset[0]
485
- // translateY += _data.fullParams.offset[1]
486
-
487
- return {
488
- width,
489
- height,
490
- translateX,
491
- translateY
492
- }
493
- })
494
- )
495
-
496
- const lengendCardSelection$ = combineLatest({
497
- rootPositionSelection: rootPositionSelection$,
498
- fullParams: fullParams$,
499
- fullChartParams: fullChartParams$,
500
- legendCard: legendCard$
501
- }).pipe(
502
- takeUntil(destroy$),
503
- switchMap(async d => d),
504
- map(data => {
505
- return data.rootPositionSelection
506
- .selectAll<SVGGElement, BaseLegendParams>('g')
507
- .data([data.legendCard])
508
- .join(
509
- enter => {
510
- return enter
511
- .append('g')
512
- .classed(legendCardClassName, true)
513
- .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
514
- },
515
- update => {
516
- return update
517
- .transition()
518
- .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
519
- },
520
- exit => exit.remove()
521
- )
522
- .each((d, i, g) => {
523
- const rect = d3.select(g[i])
524
- .selectAll('rect')
525
- .data([d])
526
- .join('rect')
527
- .attr('width', d => d.width)
528
- .attr('height', d => d.height)
529
- .attr('fill', getColor(data.fullParams.backgroundFill, data.fullChartParams))
530
- .attr('stroke', getColor(data.fullParams.backgroundStroke, data.fullChartParams))
531
- })
532
- })
533
- )
534
-
535
-
536
- const lengendListSelection$ = combineLatest({
537
- lengendCardSelection: lengendCardSelection$,
538
- fullParams: fullParams$,
539
- lengendList: lengendList$
540
- }).pipe(
541
- takeUntil(destroy$),
542
- switchMap(async d => d),
543
- map(data => {
544
- return data.lengendCardSelection
545
- .selectAll<SVGGElement, BaseLegendParams>('g')
546
- .data([data.lengendList])
547
- .join(
548
- enter => {
549
- return enter
550
- .append('g')
551
- .classed(legendListClassName, true)
552
- .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
553
- },
554
- update => {
555
- return update
556
- .transition()
557
- .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
558
- },
559
- exit => exit.remove()
560
- )
561
- })
562
- )
563
-
564
- const itemSelection$ = combineLatest({
565
- lengendListSelection: lengendListSelection$,
566
- fullParams: fullParams$,
567
- fullChartParams: fullChartParams$,
568
- lengendItems: lengendItems$
569
- }).pipe(
570
- takeUntil(destroy$),
571
- switchMap(async d => d),
572
- map(data => {
573
- const items = data.lengendItems[0] ? data.lengendItems.flat() : []
574
-
575
- return data.lengendListSelection
576
- .selectAll<SVGGElement, string>(`g.${legendItemClassName}`)
577
- .data(items)
578
- .join(
579
- enter => {
580
- return enter
581
- .append('g')
582
- .classed(legendItemClassName, true)
583
- .attr('cursor', 'default')
584
- },
585
- update => update,
586
- exit => exit.remove()
587
- )
588
- .attr('transform', (d, i) => {
589
- return `translate(${d.translateX}, ${d.translateY})`
590
- })
591
- .each((d, i, g) => {
592
- const rectCenterX = data.fullChartParams.styles.textSize / 2
593
- const transformRectWidth = - d.listRectWidth / 2
594
- const transformRectHeight = - d.listRectHeight / 2
595
- // 方塊
596
- d3.select(g[i])
597
- .selectAll('rect')
598
- .data([d])
599
- .join('rect')
600
- .attr('x', rectCenterX)
601
- .attr('y', rectCenterX)
602
- .attr('width', _d => _d.listRectWidth)
603
- .attr('height', _d => _d.listRectHeight)
604
- .attr('transform', _d => `translate(${transformRectWidth}, ${transformRectHeight})`)
605
- .attr('fill', _d => _d.color)
606
- .attr('rx', _d => _d.listRectRadius)
607
- // 文字
608
- d3.select(g[i])
609
- .selectAll('text')
610
- .data([d])
611
- .join(
612
- enter => {
613
- return enter
614
- .append('text')
615
- .attr('dominant-baseline', 'hanging')
616
- },
617
- update => {
618
- return update
619
- },
620
- exit => exit.remove()
621
- )
622
- .attr('x', data.fullChartParams.styles.textSize * 1.5)
623
- .attr('font-size', data.fullChartParams.styles.textSize)
624
- .attr('fill', d => data.fullParams.textColorType === 'series'
625
- ? getSeriesColor(d.seriesIndex, data.fullChartParams)
626
- : getColor(data.fullParams.textColorType, data.fullChartParams))
627
- .text(d => d.text)
628
- })
629
- })
630
- )
631
-
632
- itemSelection$.subscribe()
633
-
634
- return () => {
635
- destroy$.next(undefined)
636
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ switchMap,
6
+ takeUntil,
7
+ shareReplay,
8
+ Observable,
9
+ Subject } from 'rxjs'
10
+ import type { BasePluginFn } from './types'
11
+ import type {
12
+ ChartParams, Layout, ColorType } from '@orbcharts/core'
13
+ import { getClassName, getColor, getDatumColor } from '../utils/orbchartsUtils'
14
+ import { measureTextWidth } from '../utils/commonUtils'
15
+
16
+ export interface BaseLegendParams {
17
+ position: 'top' | 'bottom' | 'left' | 'right'
18
+ justify: 'start' | 'center' | 'end'
19
+ padding: number
20
+ // offset: [number, number]
21
+ backgroundFill: ColorType
22
+ backgroundStroke: ColorType
23
+ textColorType: ColorType
24
+ gap: number
25
+ seriesList: Array<{
26
+ listRectWidth: number
27
+ listRectHeight: number
28
+ listRectRadius: number
29
+ }>
30
+ // highlightEvent: boolean
31
+ }
32
+
33
+ interface BaseLegendContext {
34
+ rootSelection: d3.Selection<any, unknown, any, unknown>
35
+ seriesLabels$: Observable<string[]>
36
+ fullParams$: Observable<BaseLegendParams>
37
+ layout$: Observable<Layout>
38
+ fullChartParams$: Observable<ChartParams>
39
+ }
40
+
41
+ // 第1層 - 定位的容器
42
+ interface RootPosition {
43
+ x:number
44
+ y:number
45
+ }
46
+
47
+ // 第2層 - 卡片
48
+ interface LegendCard {
49
+ width: number
50
+ height: number
51
+ translateX:number
52
+ translateY:number
53
+ }
54
+
55
+ // 第3層 - 圖例列表
56
+ interface LegendList {
57
+ direction: 'row' | 'column'
58
+ width: number
59
+ height: number
60
+ translateX:number
61
+ translateY:number
62
+ // list: LegendItem[][]
63
+ }
64
+
65
+ // 第4層 - 圖例項目
66
+ interface LegendItem {
67
+ id: string // seriesLabel
68
+ seriesLabel: string
69
+ seriesIndex: number
70
+ lineIndex: number
71
+ itemIndex: number // 行內的item
72
+ text: string
73
+ itemWidth: number
74
+ translateX: number
75
+ translateY: number
76
+ color: string
77
+ listRectWidth: number
78
+ listRectHeight: number
79
+ listRectRadius: number
80
+ }
81
+
82
+ interface ListStyle {
83
+ listRectWidth: number
84
+ listRectHeight: number
85
+ listRectRadius: number
86
+ }
87
+
88
+ const defaultListStyle: ListStyle = {
89
+ listRectWidth: 14,
90
+ listRectHeight: 14,
91
+ listRectRadius: 0,
92
+ }
93
+
94
+ function getSeriesColor (seriesIndex: number, fullChartParams: ChartParams) {
95
+ const colorIndex = seriesIndex < fullChartParams.colors[fullChartParams.colorScheme].series.length
96
+ ? seriesIndex
97
+ : seriesIndex % fullChartParams.colors[fullChartParams.colorScheme].series.length
98
+ return fullChartParams.colors[fullChartParams.colorScheme].series[colorIndex]
99
+ }
100
+
101
+ function getLegendColor () {
102
+
103
+ }
104
+
105
+ export const createBaseLegend: BasePluginFn<BaseLegendContext> = (pluginName: string, {
106
+ rootSelection,
107
+ seriesLabels$,
108
+ fullParams$,
109
+ layout$,
110
+ fullChartParams$
111
+ }) => {
112
+
113
+ const rootPositionClassName = getClassName(pluginName, 'root-position')
114
+ const legendCardClassName = getClassName(pluginName, 'legend-card')
115
+ const legendListClassName = getClassName(pluginName, 'legend-list')
116
+ const legendItemClassName = getClassName(pluginName, 'legend-item')
117
+
118
+ const destroy$ = new Subject()
119
+
120
+ // const seriesLabels$: Observable<string[]> = SeriesDataMap$.pipe(
121
+ // takeUntil(destroy$),
122
+ // map(data => {
123
+ // return Array.from(data.keys())
124
+ // })
125
+ // )
126
+
127
+ const SeriesLabelColorMap$ = combineLatest({
128
+ seriesLabels: seriesLabels$,
129
+ fullChartParams: fullChartParams$
130
+ }).pipe(
131
+ takeUntil(destroy$),
132
+ switchMap(async d => d),
133
+ map(data => {
134
+ const SeriesLabelColorMap: Map<string, string> = new Map()
135
+ let accIndex = 0
136
+ data.seriesLabels.forEach((label, i) => {
137
+ if (!SeriesLabelColorMap.has(label)) {
138
+ const color = getSeriesColor(accIndex, data.fullChartParams)
139
+ SeriesLabelColorMap.set(label, color)
140
+ accIndex ++
141
+ }
142
+ })
143
+ return SeriesLabelColorMap
144
+ })
145
+ )
146
+
147
+ // 對應seriesLabels是否顯示(只顯示不重覆的)
148
+ const visibleList$ = seriesLabels$.pipe(
149
+ takeUntil(destroy$),
150
+ map(data => {
151
+ const AccSeriesLabelSet = new Set()
152
+ let visibleList: boolean[] = []
153
+ data.forEach(d => {
154
+ if (AccSeriesLabelSet.has(d)) {
155
+ visibleList.push(false) // 已存在則不顯示
156
+ } else {
157
+ visibleList.push(true)
158
+ }
159
+ AccSeriesLabelSet.add(d) // 累加已存在的seriesLabel
160
+ })
161
+ return visibleList
162
+ })
163
+ )
164
+
165
+ const lineDirection$ = fullParams$.pipe(
166
+ takeUntil(destroy$),
167
+ map(data => {
168
+ return data.position === 'bottom' || data.position === 'top'
169
+ ? 'row'
170
+ : 'column'
171
+ })
172
+ )
173
+
174
+ const lineMaxSize$ = combineLatest({
175
+ fullParams: fullParams$,
176
+ layout: layout$
177
+ }).pipe(
178
+ takeUntil(destroy$),
179
+ switchMap(async d => d),
180
+ map(data => {
181
+ const ourterSize = (data.fullParams.padding) * 2 + (data.fullParams.gap * 2) // 卡片離場景的間距 & 卡片內的間距
182
+
183
+ return data.fullParams.position === 'bottom' || data.fullParams.position === 'top'
184
+ ? data.layout.rootWidth - ourterSize
185
+ : data.layout.rootHeight - ourterSize
186
+ })
187
+ )
188
+
189
+ const rootPosition$ = combineLatest({
190
+ layout: layout$,
191
+ fullParams: fullParams$,
192
+ }).pipe(
193
+ takeUntil(destroy$),
194
+ switchMap(async d => d),
195
+ map(data => {
196
+ let x = 0
197
+ let y = 0
198
+ if (data.fullParams.position === 'bottom') {
199
+ y = data.layout.rootHeight
200
+ if (data.fullParams.justify === 'start') {
201
+ x = 0
202
+ } else if (data.fullParams.justify === 'center') {
203
+ x = data.layout.rootWidth / 2
204
+ } else if (data.fullParams.justify === 'end') {
205
+ x = data.layout.rootWidth
206
+ }
207
+ } else if (data.fullParams.position === 'right') {
208
+ x = data.layout.rootWidth
209
+ if (data.fullParams.justify === 'start') {
210
+ y = 0
211
+ } else if (data.fullParams.justify === 'center') {
212
+ y = data.layout.rootHeight / 2
213
+ } else if (data.fullParams.justify === 'end') {
214
+ y = data.layout.rootHeight
215
+ }
216
+ } else if (data.fullParams.position === 'top') {
217
+ y = 0
218
+ if (data.fullParams.justify === 'start') {
219
+ x = 0
220
+ } else if (data.fullParams.justify === 'center') {
221
+ x = data.layout.rootWidth / 2
222
+ } else if (data.fullParams.justify === 'end') {
223
+ x = data.layout.rootWidth
224
+ }
225
+ } else if (data.fullParams.position === 'left') {
226
+ x = 0
227
+ if (data.fullParams.justify === 'start') {
228
+ y = 0
229
+ } else if (data.fullParams.justify === 'center') {
230
+ y = data.layout.rootHeight / 2
231
+ } else if (data.fullParams.justify === 'end') {
232
+ y = data.layout.rootHeight
233
+ }
234
+ }
235
+
236
+ return {
237
+ x,
238
+ y
239
+ }
240
+ })
241
+ )
242
+
243
+ const rootPositionSelection$: Observable<d3.Selection<SVGGElement, RootPosition, any, any>> = rootPosition$.pipe(
244
+ takeUntil(destroy$),
245
+ map(data => {
246
+
247
+ return rootSelection
248
+ .selectAll<SVGGElement, RootPosition>(`g.${rootPositionClassName}`)
249
+ .data([data])
250
+ .join(
251
+ enter => {
252
+ return enter
253
+ .append('g')
254
+ .classed(rootPositionClassName, true)
255
+ .attr('transform', d => `translate(${d.x}, ${d.y})`)
256
+ },
257
+ update => {
258
+ return update
259
+ .transition()
260
+ .attr('transform', d => `translate(${d.x}, ${d.y})`)
261
+ },
262
+ exit => exit.remove()
263
+ )
264
+ })
265
+ )
266
+
267
+ const defaultListStyle$ = fullParams$.pipe(
268
+ takeUntil(destroy$),
269
+ map(data => {
270
+ return data.seriesList[0] ? data.seriesList[0] : defaultListStyle
271
+ })
272
+ )
273
+
274
+ // 先計算list內每個item
275
+ const lengendItems$: Observable<LegendItem[][]> = combineLatest({
276
+ visibleList: visibleList$,
277
+ fullParams: fullParams$,
278
+ fullChartParams: fullChartParams$,
279
+ seriesLabels: seriesLabels$,
280
+ lineDirection: lineDirection$,
281
+ lineMaxSize: lineMaxSize$,
282
+ defaultListStyle: defaultListStyle$,
283
+ SeriesLabelColorMap: SeriesLabelColorMap$
284
+ }).pipe(
285
+ takeUntil(destroy$),
286
+ switchMap(async d => d),
287
+ map(data => {
288
+ return data.seriesLabels.reduce((prev: LegendItem[][], current, currentIndex) => {
289
+ // visible為flase則不加入
290
+ if (!data.visibleList[currentIndex]) {
291
+ return prev
292
+ }
293
+
294
+ const textWidth = measureTextWidth(current, data.fullChartParams.styles.textSize)
295
+ const itemWidth = (data.fullChartParams.styles.textSize * 1.5) + textWidth
296
+ // const color = getSeriesColor(currentIndex, data.fullChartParams)
297
+ const color = data.SeriesLabelColorMap.get(current)
298
+ const lastItem: LegendItem | null = prev[0] && prev[0][0]
299
+ ? prev[prev.length - 1][prev[prev.length - 1].length - 1]
300
+ : null
301
+
302
+ const { translateX, translateY, lineIndex, itemIndex } = ((_data, _prev, _lastItem) => {
303
+ let translateX = 0
304
+ let translateY = 0
305
+ let lineIndex = 0
306
+ let itemIndex = 0
307
+
308
+ if (_data.lineDirection === 'column') {
309
+ let tempTranslateY = _lastItem
310
+ ? _lastItem.translateY + _data.fullChartParams.styles.textSize + _data.fullParams.gap
311
+ : 0
312
+
313
+ if ((tempTranslateY + _data.fullChartParams.styles.textSize) > _data.lineMaxSize) {
314
+ // 換行
315
+ lineIndex = _lastItem.lineIndex + 1
316
+ itemIndex = 0
317
+ translateY = 0
318
+ // 前一行最寬寬度
319
+ const maxItemWidthInLastLine = _prev[_prev.length - 1].reduce((p, c) => {
320
+ return c.itemWidth > p ? c.itemWidth : p
321
+ }, 0)
322
+ translateX = _lastItem.translateX + maxItemWidthInLastLine + _data.fullParams.gap
323
+ } else {
324
+ lineIndex = _lastItem ? _lastItem.lineIndex : 0
325
+ itemIndex = _lastItem ? _lastItem.itemIndex + 1 : 0
326
+ translateY = tempTranslateY
327
+ translateX = _lastItem ? _lastItem.translateX : 0
328
+ }
329
+ } else {
330
+ let tempTranslateX = _lastItem
331
+ ? _lastItem.translateX + _lastItem.itemWidth + _data.fullParams.gap
332
+ : 0
333
+ if ((tempTranslateX + itemWidth) > _data.lineMaxSize) {
334
+ // 換行
335
+ lineIndex = _lastItem.lineIndex + 1
336
+ itemIndex = 0
337
+ translateX = 0
338
+ } else {
339
+ lineIndex = _lastItem ? _lastItem.lineIndex : 0
340
+ itemIndex = _lastItem ? _lastItem.itemIndex + 1 : 0
341
+ translateX = tempTranslateX
342
+ }
343
+ translateY = (_data.fullChartParams.styles.textSize + _data.fullParams.gap) * lineIndex
344
+ }
345
+
346
+ return { translateX, translateY, lineIndex, itemIndex }
347
+ })(data, prev, lastItem)
348
+
349
+ if (!prev[lineIndex]) {
350
+ prev[lineIndex] = []
351
+ }
352
+
353
+ const listStyle = data.fullParams.seriesList[itemIndex] ? data.fullParams.seriesList[itemIndex] : data.defaultListStyle
354
+
355
+ prev[lineIndex].push({
356
+ id: current,
357
+ seriesLabel: current,
358
+ seriesIndex: currentIndex,
359
+ lineIndex,
360
+ itemIndex,
361
+ text: current,
362
+ itemWidth,
363
+ translateX,
364
+ translateY,
365
+ color,
366
+ listRectWidth: listStyle.listRectWidth,
367
+ listRectHeight: listStyle.listRectHeight,
368
+ listRectRadius: listStyle.listRectRadius
369
+ })
370
+
371
+ return prev
372
+ }, [])
373
+ }),
374
+ shareReplay(1)
375
+ )
376
+
377
+ // 依list計算出來的排序位置來計算整體容器的尺寸
378
+ const lengendList$: Observable<LegendList> = combineLatest({
379
+ fullParams: fullParams$,
380
+ fullChartParams: fullChartParams$,
381
+ lineDirection: lineDirection$,
382
+ lengendItems: lengendItems$
383
+ }).pipe(
384
+ takeUntil(destroy$),
385
+ switchMap(async d => d),
386
+ map(data => {
387
+ // 依list計算出來的排序位置來計算整體容器的偏移位置
388
+ const { width, height } = ((_data, _lengendItems) => {
389
+ let width = 0
390
+ let height = 0
391
+
392
+ if (!_lengendItems.length || !_lengendItems[0].length) {
393
+ return { width, height }
394
+ }
395
+
396
+ const firstLineLastItem = _lengendItems[0][_lengendItems[0].length - 1]
397
+ if (_data.lineDirection === 'column') {
398
+ width = _lengendItems.reduce((p, c) => {
399
+ const maxWidthInLine = c.reduce((_p, _c) => {
400
+ // 找出最寬的寬度
401
+ return _c.itemWidth > _p ? _c.itemWidth : _p
402
+ }, 0)
403
+ // 每行寬度加總
404
+ return p + maxWidthInLine
405
+ }, 0)
406
+ width += _data.fullParams.gap * (_lengendItems.length - 1)
407
+ height = firstLineLastItem.translateY + _data.fullChartParams.styles.textSize
408
+ } else {
409
+ width = firstLineLastItem.translateX + firstLineLastItem.itemWidth
410
+ height = (_data.fullChartParams.styles.textSize * _lengendItems.length) + (_data.fullParams.gap * (_lengendItems.length - 1))
411
+ }
412
+
413
+ return { width, height }
414
+ })(data, data.lengendItems)
415
+
416
+ return <LegendList>{
417
+ direction: data.lineDirection,
418
+ width,
419
+ height,
420
+ translateX: data.fullParams.gap,
421
+ translateY: data.fullParams.gap
422
+ }
423
+ }),
424
+ shareReplay(1)
425
+ )
426
+
427
+ const legendCard$: Observable<LegendCard> = combineLatest({
428
+ fullParams: fullParams$,
429
+ lengendList: lengendList$
430
+ }).pipe(
431
+ takeUntil(destroy$),
432
+ switchMap(async d => d),
433
+ map(data => {
434
+ const width = data.lengendList.width + (data.fullParams.gap * 2)
435
+ const height = data.lengendList.height + (data.fullParams.gap * 2)
436
+ let translateX = 0
437
+ let translateY = 0
438
+
439
+ if (data.fullParams.position === 'left') {
440
+ if (data.fullParams.justify === 'start') {
441
+ translateX = data.fullParams.padding
442
+ translateY = data.fullParams.padding
443
+ } else if (data.fullParams.justify === 'center') {
444
+ translateX = data.fullParams.padding
445
+ translateY = - height / 2
446
+ } else if (data.fullParams.justify === 'end') {
447
+ translateX = data.fullParams.padding
448
+ translateY = - height - data.fullParams.padding
449
+ }
450
+ } else if (data.fullParams.position === 'right') {
451
+ if (data.fullParams.justify === 'start') {
452
+ translateX = - width - data.fullParams.padding
453
+ translateY = data.fullParams.padding
454
+ } else if (data.fullParams.justify === 'center') {
455
+ translateX = - width - data.fullParams.padding
456
+ translateY = - height / 2
457
+ } else if (data.fullParams.justify === 'end') {
458
+ translateX = - width - data.fullParams.padding
459
+ translateY = - height - data.fullParams.padding
460
+ }
461
+ } else if (data.fullParams.position === 'top') {
462
+ if (data.fullParams.justify === 'start') {
463
+ translateX = data.fullParams.padding
464
+ translateY = data.fullParams.padding
465
+ } else if (data.fullParams.justify === 'center') {
466
+ translateX = - width / 2
467
+ translateY = data.fullParams.padding
468
+ } else if (data.fullParams.justify === 'end') {
469
+ translateX = - width - data.fullParams.padding
470
+ translateY = data.fullParams.padding
471
+ }
472
+ } else {
473
+ if (data.fullParams.justify === 'start') {
474
+ translateX = data.fullParams.padding
475
+ translateY = - height - data.fullParams.padding
476
+ } else if (data.fullParams.justify === 'center') {
477
+ translateX = - width / 2
478
+ translateY = - height - data.fullParams.padding
479
+ } else if (data.fullParams.justify === 'end') {
480
+ translateX = - width - data.fullParams.padding
481
+ translateY = - height - data.fullParams.padding
482
+ }
483
+ }
484
+ // translateX += _data.fullParams.offset[0]
485
+ // translateY += _data.fullParams.offset[1]
486
+
487
+ return {
488
+ width,
489
+ height,
490
+ translateX,
491
+ translateY
492
+ }
493
+ })
494
+ )
495
+
496
+ const lengendCardSelection$ = combineLatest({
497
+ rootPositionSelection: rootPositionSelection$,
498
+ fullParams: fullParams$,
499
+ fullChartParams: fullChartParams$,
500
+ legendCard: legendCard$
501
+ }).pipe(
502
+ takeUntil(destroy$),
503
+ switchMap(async d => d),
504
+ map(data => {
505
+ return data.rootPositionSelection
506
+ .selectAll<SVGGElement, BaseLegendParams>('g')
507
+ .data([data.legendCard])
508
+ .join(
509
+ enter => {
510
+ return enter
511
+ .append('g')
512
+ .classed(legendCardClassName, true)
513
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
514
+ },
515
+ update => {
516
+ return update
517
+ .transition()
518
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
519
+ },
520
+ exit => exit.remove()
521
+ )
522
+ .each((d, i, g) => {
523
+ const rect = d3.select(g[i])
524
+ .selectAll('rect')
525
+ .data([d])
526
+ .join('rect')
527
+ .attr('width', d => d.width)
528
+ .attr('height', d => d.height)
529
+ .attr('fill', getColor(data.fullParams.backgroundFill, data.fullChartParams))
530
+ .attr('stroke', getColor(data.fullParams.backgroundStroke, data.fullChartParams))
531
+ })
532
+ })
533
+ )
534
+
535
+
536
+ const lengendListSelection$ = combineLatest({
537
+ lengendCardSelection: lengendCardSelection$,
538
+ fullParams: fullParams$,
539
+ lengendList: lengendList$
540
+ }).pipe(
541
+ takeUntil(destroy$),
542
+ switchMap(async d => d),
543
+ map(data => {
544
+ return data.lengendCardSelection
545
+ .selectAll<SVGGElement, BaseLegendParams>('g')
546
+ .data([data.lengendList])
547
+ .join(
548
+ enter => {
549
+ return enter
550
+ .append('g')
551
+ .classed(legendListClassName, true)
552
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
553
+ },
554
+ update => {
555
+ return update
556
+ .transition()
557
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
558
+ },
559
+ exit => exit.remove()
560
+ )
561
+ })
562
+ )
563
+
564
+ const itemSelection$ = combineLatest({
565
+ lengendListSelection: lengendListSelection$,
566
+ fullParams: fullParams$,
567
+ fullChartParams: fullChartParams$,
568
+ lengendItems: lengendItems$
569
+ }).pipe(
570
+ takeUntil(destroy$),
571
+ switchMap(async d => d),
572
+ map(data => {
573
+ const items = data.lengendItems[0] ? data.lengendItems.flat() : []
574
+
575
+ return data.lengendListSelection
576
+ .selectAll<SVGGElement, string>(`g.${legendItemClassName}`)
577
+ .data(items)
578
+ .join(
579
+ enter => {
580
+ return enter
581
+ .append('g')
582
+ .classed(legendItemClassName, true)
583
+ .attr('cursor', 'default')
584
+ },
585
+ update => update,
586
+ exit => exit.remove()
587
+ )
588
+ .attr('transform', (d, i) => {
589
+ return `translate(${d.translateX}, ${d.translateY})`
590
+ })
591
+ .each((d, i, g) => {
592
+ const rectCenterX = data.fullChartParams.styles.textSize / 2
593
+ const transformRectWidth = - d.listRectWidth / 2
594
+ const transformRectHeight = - d.listRectHeight / 2
595
+ // 方塊
596
+ d3.select(g[i])
597
+ .selectAll('rect')
598
+ .data([d])
599
+ .join('rect')
600
+ .attr('x', rectCenterX)
601
+ .attr('y', rectCenterX)
602
+ .attr('width', _d => _d.listRectWidth)
603
+ .attr('height', _d => _d.listRectHeight)
604
+ .attr('transform', _d => `translate(${transformRectWidth}, ${transformRectHeight})`)
605
+ .attr('fill', _d => _d.color)
606
+ .attr('rx', _d => _d.listRectRadius)
607
+ // 文字
608
+ d3.select(g[i])
609
+ .selectAll('text')
610
+ .data([d])
611
+ .join(
612
+ enter => {
613
+ return enter
614
+ .append('text')
615
+ .attr('dominant-baseline', 'hanging')
616
+ },
617
+ update => {
618
+ return update
619
+ },
620
+ exit => exit.remove()
621
+ )
622
+ .attr('x', data.fullChartParams.styles.textSize * 1.5)
623
+ .attr('font-size', data.fullChartParams.styles.textSize)
624
+ .attr('fill', d => data.fullParams.textColorType === 'series'
625
+ ? getSeriesColor(d.seriesIndex, data.fullChartParams)
626
+ : getColor(data.fullParams.textColorType, data.fullChartParams))
627
+ .text(d => d.text)
628
+ })
629
+ })
630
+ )
631
+
632
+ itemSelection$.subscribe()
633
+
634
+ return () => {
635
+ destroy$.next(undefined)
636
+ }
637
637
  }