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

Sign up to get free protection for your applications and to get access to all the features.
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
  }