@orbcharts/plugins-basic 3.0.0-alpha.26 → 3.0.0-alpha.28

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ import { BasePluginFn } from './types';
2
+ import { ColorType } from '@orbcharts/core';
3
+
4
+ export interface BaseLegendParams {
5
+ position: 'top' | 'bottom' | 'left' | 'right';
6
+ justify: 'start' | 'center' | 'end';
7
+ padding: number;
8
+ backgroundFill: ColorType;
9
+ backgroundStroke: ColorType;
10
+ gap: number;
11
+ listRectWidth: number;
12
+ listRectHeight: number;
13
+ listRectRadius: number;
14
+ }
15
+ export declare const createBaseLegend: BasePluginFn;
@@ -0,0 +1,3 @@
1
+ export interface BasePluginFn {
2
+ (pluginName: string, context: any): () => void;
3
+ }
@@ -1,4 +1,4 @@
1
- import { LinesPluginParams, GroupAreaPluginParams, DotsPluginParams, BarsPluginParams, BarStackPluginParams, BarsTrianglePluginParams, GroupingAxisParams, ValueAxisParams, ValueStackAxisParams, ScalingAreaParams } from './types';
1
+ import { LinesPluginParams, GroupAreaPluginParams, DotsPluginParams, BarsPluginParams, BarStackPluginParams, BarsTrianglePluginParams, GroupAxisParams, ValueAxisParams, ValueStackAxisParams, ScalingAreaParams, GridLegendParams } from './types';
2
2
 
3
3
  export declare const DEFAULT_LINES_PLUGIN_PARAMS: LinesPluginParams;
4
4
  export declare const DEFAULT_DOTS_PLUGIN_PARAMS: DotsPluginParams;
@@ -6,7 +6,8 @@ export declare const DEFAULT_GROUP_AREA_PLUGIN_PARAMS: GroupAreaPluginParams;
6
6
  export declare const DEFAULT_BARS_PLUGIN_PARAMS: BarsPluginParams;
7
7
  export declare const DEFAULT_BAR_STACK_PLUGIN_PARAMS: BarStackPluginParams;
8
8
  export declare const DEFAULT_BARS_TRIANGLE_PLUGIN_PARAMS: BarsTrianglePluginParams;
9
- export declare const DEFAULT_GROUPING_AXIS_PLUGIN_PARAMS: GroupingAxisParams;
9
+ export declare const DEFAULT_GROUPING_AXIS_PLUGIN_PARAMS: GroupAxisParams;
10
10
  export declare const DEFAULT_VALUE_AXIS_PLUGIN_PARAMS: ValueAxisParams;
11
11
  export declare const DEFAULT_VALUE_STACK_AXIS_PLUGIN_PARAMS: ValueStackAxisParams;
12
12
  export declare const DEFAULT_SCALING_AREA_PLUGIN_PARAMS: ScalingAreaParams;
13
+ export declare const DEFAULT_GRID_LEGEND_PARAMS: GridLegendParams;
@@ -5,6 +5,7 @@ export { Bars } from './plugins/Bars';
5
5
  export { BarStack } from './plugins/BarStack';
6
6
  export { BarsTriangle } from './plugins/BarsTriangle';
7
7
  export { Dots } from './plugins/Dots';
8
+ export { GridLegend } from './plugins/GridLegend';
8
9
  export { GroupAxis } from './plugins/GroupAxis';
9
10
  export { ValueAxis } from './plugins/ValueAxis';
10
11
  export { ValueStackAxis } from './plugins/ValueStackAxis';
@@ -0,0 +1 @@
1
+ export declare const GridLegend: import('@orbcharts/core').PluginConstructor<"grid", string, import('..').GridLegendParams>;
@@ -1,3 +1,3 @@
1
- import { GroupingAxisParams } from '../types';
1
+ import { GroupAxisParams } from '../types';
2
2
 
3
- export declare const GroupAxis: import('@orbcharts/core').PluginConstructor<"grid", string, GroupingAxisParams>;
3
+ export declare const GroupAxis: import('@orbcharts/core').PluginConstructor<"grid", string, GroupAxisParams>;
@@ -1,4 +1,5 @@
1
1
  import { ColorType } from '@orbcharts/core';
2
+ import { BaseLegendParams } from '../base/BaseLegend';
2
3
 
3
4
  export interface LinesPluginParams {
4
5
  lineCurve: string;
@@ -38,7 +39,7 @@ export interface BarsTrianglePluginParams {
38
39
  barGroupPadding: number;
39
40
  linearGradientOpacity: [number, number];
40
41
  }
41
- export interface GroupingAxisParams {
42
+ export interface GroupAxisParams {
42
43
  labelOffset: [number, number];
43
44
  labelColorType: ColorType;
44
45
  axisLineVisible: boolean;
@@ -71,3 +72,5 @@ export interface ValueStackAxisParams extends ValueAxisParams {
71
72
  }
72
73
  export interface ScalingAreaParams {
73
74
  }
75
+ export interface GridLegendParams extends BaseLegendParams {
76
+ }
@@ -1,3 +1 @@
1
- import { SeriesLegendParams } from '../types';
2
-
3
- export declare const SeriesLegend: import('@orbcharts/core').PluginConstructor<"series", string, SeriesLegendParams>;
1
+ export declare const SeriesLegend: import('@orbcharts/core').PluginConstructor<"series", string, import('..').SeriesLegendParams>;
@@ -1,6 +1,7 @@
1
1
  import { ComputedDatumSeries, EventSeries, EventName, ColorType } from '@orbcharts/core';
2
+ import { BaseLegendParams } from '../base/BaseLegend';
2
3
 
3
- export type ScaleType = 'area' | 'radius';
4
+ export type BubbleScaleType = 'area' | 'radius';
4
5
  export interface BubblesPluginParams {
5
6
  force: {
6
7
  strength: number;
@@ -13,7 +14,7 @@ export interface BubblesPluginParams {
13
14
  lineLengthMin: number;
14
15
  };
15
16
  highlightRIncrease: number;
16
- scaleType: ScaleType;
17
+ bubbleScaleType: BubbleScaleType;
17
18
  }
18
19
  export interface PiePluginParams {
19
20
  outerRadius: number;
@@ -43,10 +44,5 @@ export interface PieLabelsPluginParams {
43
44
  labelFn: ((d: ComputedDatumSeries) => string);
44
45
  labelColorType: ColorType;
45
46
  }
46
- export interface SeriesLegendParams {
47
- position: 'top' | 'bottom' | 'left' | 'right';
48
- justify: 'start' | 'center' | 'end';
49
- padding: number;
50
- gap: number;
51
- rectRadius: number;
47
+ export interface SeriesLegendParams extends BaseLegendParams {
52
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbcharts/plugins-basic",
3
- "version": "3.0.0-alpha.26",
3
+ "version": "3.0.0-alpha.28",
4
4
  "description": "plugins for OrbCharts",
5
5
  "author": "Blue Planet Inc.",
6
6
  "license": "Apache-2.0",
@@ -35,7 +35,7 @@
35
35
  "vite-plugin-dts": "^3.7.3"
36
36
  },
37
37
  "dependencies": {
38
- "@orbcharts/core": "^3.0.0-alpha.22",
38
+ "@orbcharts/core": "^3.0.0-alpha.24",
39
39
  "d3": "^7.8.5",
40
40
  "rxjs": "^7.8.1"
41
41
  }
@@ -0,0 +1,543 @@
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ switchMap,
6
+ takeUntil,
7
+ Observable,
8
+ Subject } from 'rxjs'
9
+ import type { BasePluginFn } from './types'
10
+ import type {
11
+ ChartParams, Layout, ColorType } from '@orbcharts/core'
12
+ import { getSeriesColor, getClassName, getColor } from '../utils/orbchartsUtils'
13
+ import { measureTextWidth } from '../utils/commonUtils'
14
+
15
+ export interface BaseLegendParams {
16
+ position: 'top' | 'bottom' | 'left' | 'right'
17
+ justify: 'start' | 'center' | 'end'
18
+ padding: number
19
+ // offset: [number, number]
20
+ backgroundFill: ColorType
21
+ backgroundStroke: ColorType
22
+ gap: number
23
+ listRectWidth: number
24
+ listRectHeight: number
25
+ listRectRadius: number
26
+ // highlightEvent: boolean
27
+ }
28
+
29
+ // 第1層 - 定位的容器
30
+ interface RootPosition {
31
+ x:number
32
+ y:number
33
+ }
34
+
35
+ // 第2層 - 卡片
36
+ interface LegendCard {
37
+ width: number
38
+ height: number
39
+ translateX:number
40
+ translateY:number
41
+ }
42
+
43
+ // 第3層 - 圖例列表
44
+ interface LegendList {
45
+ direction: 'row' | 'column'
46
+ width: number
47
+ height: number
48
+ translateX:number
49
+ translateY:number
50
+ // list: LegendItem[][]
51
+ }
52
+
53
+ // 第4層 - 圖例項目
54
+ interface LegendItem {
55
+ id: string // seriesLabel
56
+ seriesLabel: string
57
+ seriesIndex: number
58
+ lineIndex: number
59
+ itemIndex: number // 行內的item
60
+ text: string
61
+ itemWidth: number
62
+ translateX: number
63
+ translateY: number
64
+ color: string
65
+ // fontSize: number
66
+ // listRectRadius: number
67
+ }
68
+
69
+ export const createBaseLegend: BasePluginFn = (pluginName: string, {
70
+ rootSelection,
71
+ seriesLabels$,
72
+ fullParams$,
73
+ layout$,
74
+ fullChartParams$
75
+ }: {
76
+ rootSelection: d3.Selection<any, unknown, any, unknown>
77
+ seriesLabels$: Observable<string[]>
78
+ fullParams$: Observable<BaseLegendParams>
79
+ layout$: Observable<Layout>
80
+ fullChartParams$: Observable<ChartParams>
81
+ }) => {
82
+
83
+ const rootPositionClassName = getClassName(pluginName, 'root-position')
84
+ const legendCardClassName = getClassName(pluginName, 'legend-card')
85
+ const legendListClassName = getClassName(pluginName, 'legend-list')
86
+ const legendItemClassName = getClassName(pluginName, 'legend-item')
87
+
88
+ const destroy$ = new Subject()
89
+
90
+ // const seriesLabels$: Observable<string[]> = SeriesDataMap$.pipe(
91
+ // takeUntil(destroy$),
92
+ // map(data => {
93
+ // return Array.from(data.keys())
94
+ // })
95
+ // )
96
+
97
+ const lineDirection$ = fullParams$.pipe(
98
+ takeUntil(destroy$),
99
+ map(data => {
100
+ return data.position === 'bottom' || data.position === 'top'
101
+ ? 'row'
102
+ : 'column'
103
+ })
104
+ )
105
+
106
+ const lineMaxSize$ = combineLatest({
107
+ fullParams: fullParams$,
108
+ layout: layout$
109
+ }).pipe(
110
+ takeUntil(destroy$),
111
+ switchMap(async d => d),
112
+ map(data => {
113
+ const ourterSize = (data.fullParams.padding) * 2 + (data.fullParams.gap * 2) // 卡片離場景的間距 & 卡片內的間距
114
+
115
+ return data.fullParams.position === 'bottom' || data.fullParams.position === 'top'
116
+ ? data.layout.rootWidth - ourterSize
117
+ : data.layout.rootHeight - ourterSize
118
+ })
119
+ )
120
+
121
+ const rootPosition$ = combineLatest({
122
+ layout: layout$,
123
+ fullParams: fullParams$,
124
+ }).pipe(
125
+ takeUntil(destroy$),
126
+ switchMap(async d => d),
127
+ map(data => {
128
+ let x = 0
129
+ let y = 0
130
+ if (data.fullParams.position === 'bottom') {
131
+ y = data.layout.rootHeight
132
+ if (data.fullParams.justify === 'start') {
133
+ x = 0
134
+ } else if (data.fullParams.justify === 'center') {
135
+ x = data.layout.rootWidth / 2
136
+ } else if (data.fullParams.justify === 'end') {
137
+ x = data.layout.rootWidth
138
+ }
139
+ } else if (data.fullParams.position === 'right') {
140
+ x = data.layout.rootWidth
141
+ if (data.fullParams.justify === 'start') {
142
+ y = 0
143
+ } else if (data.fullParams.justify === 'center') {
144
+ y = data.layout.rootHeight / 2
145
+ } else if (data.fullParams.justify === 'end') {
146
+ y = data.layout.rootHeight
147
+ }
148
+ } else if (data.fullParams.position === 'top') {
149
+ y = 0
150
+ if (data.fullParams.justify === 'start') {
151
+ x = 0
152
+ } else if (data.fullParams.justify === 'center') {
153
+ x = data.layout.rootWidth / 2
154
+ } else if (data.fullParams.justify === 'end') {
155
+ x = data.layout.rootWidth
156
+ }
157
+ } else if (data.fullParams.position === 'left') {
158
+ x = 0
159
+ if (data.fullParams.justify === 'start') {
160
+ y = 0
161
+ } else if (data.fullParams.justify === 'center') {
162
+ y = data.layout.rootHeight / 2
163
+ } else if (data.fullParams.justify === 'end') {
164
+ y = data.layout.rootHeight
165
+ }
166
+ }
167
+
168
+ return {
169
+ x,
170
+ y
171
+ }
172
+ })
173
+ )
174
+
175
+ const rootPositionSelection$: Observable<d3.Selection<SVGGElement, RootPosition, any, any>> = rootPosition$.pipe(
176
+ takeUntil(destroy$),
177
+ map(data => {
178
+
179
+ return rootSelection
180
+ .selectAll<SVGGElement, RootPosition>(`g.${rootPositionClassName}`)
181
+ .data([data])
182
+ .join(
183
+ enter => {
184
+ return enter
185
+ .append('g')
186
+ .classed(rootPositionClassName, true)
187
+ .attr('transform', d => `translate(${d.x}, ${d.y})`)
188
+ },
189
+ update => {
190
+ return update
191
+ .transition()
192
+ .attr('transform', d => `translate(${d.x}, ${d.y})`)
193
+ },
194
+ exit => exit.remove()
195
+ )
196
+ })
197
+ )
198
+
199
+ // 先計算list內每個item
200
+ const lengendItems$: Observable<LegendItem[][]> = combineLatest({
201
+ fullParams: fullParams$,
202
+ fullChartParams: fullChartParams$,
203
+ seriesLabels: seriesLabels$,
204
+ lineDirection: lineDirection$,
205
+ lineMaxSize: lineMaxSize$
206
+ }).pipe(
207
+ takeUntil(destroy$),
208
+ switchMap(async d => d),
209
+ map(data => {
210
+ return data.seriesLabels.reduce((prev: LegendItem[][], current, currentIndex) => {
211
+ const textWidth = measureTextWidth(current, data.fullChartParams.styles.textSize)
212
+ const itemWidth = (data.fullChartParams.styles.textSize * 1.5) + textWidth
213
+ const color = getSeriesColor(currentIndex, data.fullChartParams)
214
+ const lastItem: LegendItem | null = prev[0] && prev[0][0]
215
+ ? prev[prev.length - 1][prev[prev.length - 1].length - 1]
216
+ : null
217
+
218
+ const { translateX, translateY, lineIndex, itemIndex } = ((_data, _prev, _lastItem) => {
219
+ let translateX = 0
220
+ let translateY = 0
221
+ let lineIndex = 0
222
+ let itemIndex = 0
223
+
224
+ if (_data.lineDirection === 'column') {
225
+ let tempTranslateY = _lastItem
226
+ ? _lastItem.translateY + _data.fullChartParams.styles.textSize + _data.fullParams.gap
227
+ : 0
228
+
229
+ if ((tempTranslateY + _data.fullChartParams.styles.textSize) > _data.lineMaxSize) {
230
+ // 換行
231
+ lineIndex = _lastItem.lineIndex + 1
232
+ itemIndex = 0
233
+ translateY = 0
234
+ // 前一行最寬寬度
235
+ const maxItemWidthInLastLine = _prev[_prev.length - 1].reduce((p, c) => {
236
+ return c.itemWidth > p ? c.itemWidth : p
237
+ }, 0)
238
+ translateX = _lastItem.translateX + maxItemWidthInLastLine + _data.fullParams.gap
239
+ } else {
240
+ lineIndex = _lastItem ? _lastItem.lineIndex : 0
241
+ itemIndex = _lastItem ? _lastItem.itemIndex + 1 : 0
242
+ translateY = tempTranslateY
243
+ translateX = _lastItem ? _lastItem.translateX : 0
244
+ }
245
+ } else {
246
+ let tempTranslateX = _lastItem
247
+ ? _lastItem.translateX + _lastItem.itemWidth + _data.fullParams.gap
248
+ : 0
249
+ if ((tempTranslateX + itemWidth) > _data.lineMaxSize) {
250
+ // 換行
251
+ lineIndex = _lastItem.lineIndex + 1
252
+ itemIndex = 0
253
+ translateX = 0
254
+ } else {
255
+ lineIndex = _lastItem ? _lastItem.lineIndex : 0
256
+ itemIndex = _lastItem ? _lastItem.itemIndex + 1 : 0
257
+ translateX = tempTranslateX
258
+ }
259
+ translateY = (_data.fullChartParams.styles.textSize + _data.fullParams.gap) * lineIndex
260
+ }
261
+
262
+ return { translateX, translateY, lineIndex, itemIndex }
263
+ })(data, prev, lastItem)
264
+
265
+ if (!prev[lineIndex]) {
266
+ prev[lineIndex] = []
267
+ }
268
+
269
+ prev[lineIndex].push({
270
+ id: current,
271
+ seriesLabel: current,
272
+ seriesIndex: currentIndex,
273
+ lineIndex,
274
+ itemIndex,
275
+ text: current,
276
+ itemWidth,
277
+ translateX,
278
+ translateY,
279
+ color,
280
+ })
281
+ console.log('items', prev)
282
+ return prev
283
+ }, [])
284
+ })
285
+ )
286
+
287
+ // 依list計算出來的排序位置來計算整體容器的尺寸
288
+ const lengendList$: Observable<LegendList> = combineLatest({
289
+ fullParams: fullParams$,
290
+ fullChartParams: fullChartParams$,
291
+ lineDirection: lineDirection$,
292
+ lengendItems: lengendItems$
293
+ }).pipe(
294
+ takeUntil(destroy$),
295
+ switchMap(async d => d),
296
+ map(data => {
297
+ // 依list計算出來的排序位置來計算整體容器的偏移位置
298
+ const { width, height } = ((_data, _lengendItems) => {
299
+ let width = 0
300
+ let height = 0
301
+
302
+ if (!_lengendItems.length || !_lengendItems[0].length) {
303
+ return { width, height }
304
+ }
305
+
306
+ const firstLineLastItem = _lengendItems[0][_lengendItems[0].length - 1]
307
+ if (_data.lineDirection === 'column') {
308
+ width = _lengendItems.reduce((p, c) => {
309
+ const maxWidthInLine = c.reduce((_p, _c) => {
310
+ // 找出最寬的寬度
311
+ return _c.itemWidth > _p ? _c.itemWidth : _p
312
+ }, 0)
313
+ // 每行寬度加總
314
+ return p + maxWidthInLine
315
+ }, 0)
316
+ width += _data.fullParams.gap * (_lengendItems.length - 1)
317
+ height = firstLineLastItem.translateY + _data.fullChartParams.styles.textSize
318
+ } else {
319
+ width = firstLineLastItem.translateX + firstLineLastItem.itemWidth
320
+ height = (_data.fullChartParams.styles.textSize * _lengendItems.length) + (_data.fullParams.gap * (_lengendItems.length - 1))
321
+ }
322
+
323
+ return { width, height }
324
+ })(data, data.lengendItems)
325
+
326
+ return {
327
+ direction: data.lineDirection,
328
+ width,
329
+ height,
330
+ translateX: data.fullParams.gap,
331
+ translateY: data.fullParams.gap
332
+ }
333
+ })
334
+ )
335
+
336
+ const legendCard$: Observable<LegendCard> = combineLatest({
337
+ fullParams: fullParams$,
338
+ lengendList: lengendList$
339
+ }).pipe(
340
+ takeUntil(destroy$),
341
+ switchMap(async d => d),
342
+ map(data => {
343
+ const width = data.lengendList.width + (data.fullParams.gap * 2)
344
+ const height = data.lengendList.height + (data.fullParams.gap * 2)
345
+ let translateX = 0
346
+ let translateY = 0
347
+
348
+ if (data.fullParams.position === 'left') {
349
+ if (data.fullParams.justify === 'start') {
350
+ translateX = data.fullParams.padding
351
+ translateY = data.fullParams.padding
352
+ } else if (data.fullParams.justify === 'center') {
353
+ translateX = data.fullParams.padding
354
+ translateY = - height / 2
355
+ } else if (data.fullParams.justify === 'end') {
356
+ translateX = data.fullParams.padding
357
+ translateY = - height - data.fullParams.padding
358
+ }
359
+ } else if (data.fullParams.position === 'right') {
360
+ if (data.fullParams.justify === 'start') {
361
+ translateX = - width - data.fullParams.padding
362
+ translateY = data.fullParams.padding
363
+ } else if (data.fullParams.justify === 'center') {
364
+ translateX = - width - data.fullParams.padding
365
+ translateY = - height / 2
366
+ } else if (data.fullParams.justify === 'end') {
367
+ translateX = - width - data.fullParams.padding
368
+ translateY = - height - data.fullParams.padding
369
+ }
370
+ } else if (data.fullParams.position === 'top') {
371
+ if (data.fullParams.justify === 'start') {
372
+ translateX = data.fullParams.padding
373
+ translateY = data.fullParams.padding
374
+ } else if (data.fullParams.justify === 'center') {
375
+ translateX = - width / 2
376
+ translateY = data.fullParams.padding
377
+ } else if (data.fullParams.justify === 'end') {
378
+ translateX = - width - data.fullParams.padding
379
+ translateY = data.fullParams.padding
380
+ }
381
+ } else {
382
+ if (data.fullParams.justify === 'start') {
383
+ translateX = data.fullParams.padding
384
+ translateY = - height - data.fullParams.padding
385
+ } else if (data.fullParams.justify === 'center') {
386
+ translateX = - width / 2
387
+ translateY = - height - data.fullParams.padding
388
+ } else if (data.fullParams.justify === 'end') {
389
+ translateX = - width - data.fullParams.padding
390
+ translateY = - height - data.fullParams.padding
391
+ }
392
+ }
393
+ // translateX += _data.fullParams.offset[0]
394
+ // translateY += _data.fullParams.offset[1]
395
+
396
+ return {
397
+ width,
398
+ height,
399
+ translateX,
400
+ translateY
401
+ }
402
+ })
403
+ )
404
+
405
+ const lengendCardSelection$ = combineLatest({
406
+ rootPositionSelection: rootPositionSelection$,
407
+ fullParams: fullParams$,
408
+ fullChartParams: fullChartParams$,
409
+ legendCard: legendCard$
410
+ }).pipe(
411
+ takeUntil(destroy$),
412
+ switchMap(async d => d),
413
+ map(data => {
414
+ return data.rootPositionSelection
415
+ .selectAll<SVGGElement, BaseLegendParams>('g')
416
+ .data([data.legendCard])
417
+ .join(
418
+ enter => {
419
+ return enter
420
+ .append('g')
421
+ .classed(legendCardClassName, true)
422
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
423
+ },
424
+ update => {
425
+ return update
426
+ .transition()
427
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
428
+ },
429
+ exit => exit.remove()
430
+ )
431
+ .each((d, i, g) => {
432
+ const rect = d3.select(g[i])
433
+ .selectAll('rect')
434
+ .data([d])
435
+ .join('rect')
436
+ .attr('width', d => d.width)
437
+ .attr('height', d => d.height)
438
+ .attr('fill', getColor(data.fullParams.backgroundFill, data.fullChartParams))
439
+ .attr('stroke', getColor(data.fullParams.backgroundStroke, data.fullChartParams))
440
+ })
441
+ })
442
+ )
443
+
444
+
445
+ const lengendListSelection$ = combineLatest({
446
+ lengendCardSelection: lengendCardSelection$,
447
+ fullParams: fullParams$,
448
+ lengendList: lengendList$
449
+ }).pipe(
450
+ takeUntil(destroy$),
451
+ switchMap(async d => d),
452
+ map(data => {
453
+ return data.lengendCardSelection
454
+ .selectAll<SVGGElement, BaseLegendParams>('g')
455
+ .data([data.lengendList])
456
+ .join(
457
+ enter => {
458
+ return enter
459
+ .append('g')
460
+ .classed(legendListClassName, true)
461
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
462
+ },
463
+ update => {
464
+ return update
465
+ .transition()
466
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
467
+ },
468
+ exit => exit.remove()
469
+ )
470
+ })
471
+ )
472
+
473
+ const itemSelection$ = combineLatest({
474
+ lengendListSelection: lengendListSelection$,
475
+ fullParams: fullParams$,
476
+ fullChartParams: fullChartParams$,
477
+ lengendItems: lengendItems$
478
+ }).pipe(
479
+ takeUntil(destroy$),
480
+ switchMap(async d => d),
481
+ map(data => {
482
+ const items = data.lengendItems[0] ? data.lengendItems.flat() : []
483
+
484
+ return data.lengendListSelection
485
+ .selectAll<SVGGElement, string>(`g.${legendItemClassName}`)
486
+ .data(items)
487
+ .join(
488
+ enter => {
489
+ return enter
490
+ .append('g')
491
+ .classed(legendItemClassName, true)
492
+ .attr('cursor', 'default')
493
+ },
494
+ update => update,
495
+ exit => exit.remove()
496
+ )
497
+ .attr('transform', (d, i) => {
498
+ return `translate(${d.translateX}, ${d.translateY})`
499
+ })
500
+ .each((d, i, g) => {
501
+ const rectCenterX = data.fullChartParams.styles.textSize / 2
502
+ const rectWidth = data.fullParams.listRectWidth
503
+ const rectHeight = data.fullParams.listRectHeight
504
+ // 方塊
505
+ d3.select(g[i])
506
+ .selectAll('rect')
507
+ .data([d])
508
+ .join('rect')
509
+ .attr('x', rectCenterX)
510
+ .attr('y', rectCenterX)
511
+ .attr('width', rectWidth)
512
+ .attr('height', rectHeight)
513
+ .attr('transform', `translate(${- rectWidth / 2}, ${- rectHeight / 2})`)
514
+ .attr('fill', _d => _d.color)
515
+ .attr('rx', data.fullParams.listRectRadius)
516
+ // 文字
517
+ d3.select(g[i])
518
+ .selectAll('text')
519
+ .data([d])
520
+ .join(
521
+ enter => {
522
+ return enter
523
+ .append('text')
524
+ .attr('dominant-baseline', 'hanging')
525
+ },
526
+ update => {
527
+ return update
528
+ },
529
+ exit => exit.remove()
530
+ )
531
+ .attr('x', data.fullChartParams.styles.textSize * 1.5)
532
+ .attr('font-size', data.fullChartParams.styles.textSize)
533
+ .text(d => d.text)
534
+ })
535
+ })
536
+ )
537
+
538
+ itemSelection$.subscribe()
539
+
540
+ return () => {
541
+ destroy$.next(undefined)
542
+ }
543
+ }
@@ -0,0 +1,3 @@
1
+ export interface BasePluginFn {
2
+ (pluginName: string, context: any): () => void // return unsubscribe function
3
+ }