@orbcharts/plugins-basic 3.0.0-alpha.49 → 3.0.0-alpha.51

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +6325 -6272
  3. package/dist/orbcharts-plugins-basic.umd.js +8 -8
  4. package/dist/src/base/BaseGroupAxis.d.ts +1 -0
  5. package/dist/src/grid/index.d.ts +1 -1
  6. package/dist/src/grid/plugins/BarsPN.d.ts +1 -0
  7. package/dist/src/grid/types.d.ts +3 -2
  8. package/dist/src/multiGrid/defaults.d.ts +2 -1
  9. package/dist/src/multiGrid/index.d.ts +2 -0
  10. package/dist/src/multiGrid/multiGridObservables.d.ts +1 -1
  11. package/dist/src/multiGrid/plugins/MultiValueStackAxis.d.ts +1 -0
  12. package/dist/src/multiGrid/plugins/OverlappingValueStackAxes.d.ts +1 -0
  13. package/dist/src/multiGrid/types.d.ts +3 -0
  14. package/dist/src/series/types.d.ts +2 -3
  15. package/package.json +42 -42
  16. package/src/base/BaseBarStack.ts +778 -778
  17. package/src/base/BaseBars.ts +764 -764
  18. package/src/base/BaseBarsTriangle.ts +672 -672
  19. package/src/base/BaseDots.ts +513 -502
  20. package/src/base/BaseGroupAxis.ts +562 -496
  21. package/src/base/BaseLegend.ts +641 -641
  22. package/src/base/BaseLineAreas.ts +625 -625
  23. package/src/base/BaseLines.ts +699 -699
  24. package/src/base/BaseValueAxis.ts +478 -478
  25. package/src/base/types.ts +2 -2
  26. package/src/grid/defaults.ts +125 -121
  27. package/src/grid/gridObservables.ts +248 -247
  28. package/src/grid/index.ts +15 -15
  29. package/src/grid/plugins/BarStack.ts +43 -50
  30. package/src/grid/plugins/Bars.ts +44 -51
  31. package/src/grid/plugins/{BarsDiverging.ts → BarsPN.ts} +41 -41
  32. package/src/grid/plugins/BarsTriangle.ts +42 -50
  33. package/src/grid/plugins/Dots.ts +37 -37
  34. package/src/grid/plugins/GridLegend.ts +59 -59
  35. package/src/grid/plugins/GroupAux.ts +645 -645
  36. package/src/grid/plugins/GroupAxis.ts +35 -42
  37. package/src/grid/plugins/LineAreas.ts +39 -39
  38. package/src/grid/plugins/Lines.ts +38 -38
  39. package/src/grid/plugins/ScalingArea.ts +173 -173
  40. package/src/grid/plugins/ValueAxis.ts +36 -43
  41. package/src/grid/plugins/ValueStackAxis.ts +38 -79
  42. package/src/grid/types.ts +122 -120
  43. package/src/index.ts +9 -9
  44. package/src/multiGrid/defaults.ts +152 -147
  45. package/src/multiGrid/index.ts +14 -12
  46. package/src/multiGrid/multiGridObservables.ts +44 -43
  47. package/src/multiGrid/plugins/MultiBarStack.ts +78 -78
  48. package/src/multiGrid/plugins/MultiBars.ts +77 -77
  49. package/src/multiGrid/plugins/MultiBarsTriangle.ts +77 -77
  50. package/src/multiGrid/plugins/MultiDots.ts +65 -65
  51. package/src/multiGrid/plugins/MultiGridLegend.ts +89 -89
  52. package/src/multiGrid/plugins/MultiGroupAxis.ts +69 -69
  53. package/src/multiGrid/plugins/MultiLineAreas.ts +67 -67
  54. package/src/multiGrid/plugins/MultiLines.ts +66 -66
  55. package/src/multiGrid/plugins/MultiValueAxis.ts +69 -69
  56. package/src/multiGrid/plugins/MultiValueStackAxis.ts +69 -0
  57. package/src/multiGrid/plugins/OverlappingValueAxes.ts +166 -173
  58. package/src/multiGrid/plugins/OverlappingValueStackAxes.ts +167 -0
  59. package/src/multiGrid/types.ts +71 -67
  60. package/src/noneData/defaults.ts +64 -64
  61. package/src/noneData/index.ts +3 -3
  62. package/src/noneData/plugins/Container.ts +10 -10
  63. package/src/noneData/plugins/Tooltip.ts +310 -310
  64. package/src/noneData/types.ts +26 -26
  65. package/src/series/defaults.ts +126 -126
  66. package/src/series/index.ts +9 -9
  67. package/src/series/plugins/Bubbles.ts +545 -602
  68. package/src/series/plugins/Pie.ts +576 -576
  69. package/src/series/plugins/PieEventTexts.ts +262 -262
  70. package/src/series/plugins/PieLabels.ts +304 -304
  71. package/src/series/plugins/Rose.ts +472 -472
  72. package/src/series/plugins/RoseLabels.ts +362 -362
  73. package/src/series/plugins/SeriesLegend.ts +59 -59
  74. package/src/series/seriesObservables.ts +145 -145
  75. package/src/series/seriesUtils.ts +51 -51
  76. package/src/series/types.ts +83 -83
  77. package/src/tree/defaults.ts +22 -22
  78. package/src/tree/index.ts +3 -3
  79. package/src/tree/plugins/TreeLegend.ts +59 -59
  80. package/src/tree/plugins/TreeMap.ts +305 -305
  81. package/src/tree/types.ts +23 -23
  82. package/src/utils/commonUtils.ts +21 -21
  83. package/src/utils/d3Graphics.ts +124 -124
  84. package/src/utils/d3Utils.ts +73 -73
  85. package/src/utils/observables.ts +14 -14
  86. package/src/utils/orbchartsUtils.ts +100 -100
  87. package/tsconfig.dev.json +16 -16
  88. package/tsconfig.json +13 -13
  89. package/tsconfig.prod.json +13 -13
  90. package/vite.config.js +49 -49
  91. package/dist/src/grid/plugins/BarsDiverging.d.ts +0 -1
@@ -1,700 +1,700 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- map,
5
- filter,
6
- switchMap,
7
- takeUntil,
8
- distinctUntilChanged,
9
- Observable,
10
- Subject } from 'rxjs'
11
- import type { BasePluginFn } from './types'
12
- import type {
13
- ComputedDatumGrid,
14
- ComputedDataGrid,
15
- ComputedLayoutDatumGrid,
16
- ComputedLayoutDataGrid,
17
- DataFormatterGrid,
18
- EventGrid,
19
- GridContainerPosition,
20
- ChartParams,
21
- Layout,
22
- TransformData } from '@orbcharts/core'
23
- import { getD3TransitionEase } from '../utils/d3Utils'
24
- import { getClassName, getUniID } from '../utils/orbchartsUtils'
25
- import { gridGroupPositionFnObservable } from '../grid/gridObservables'
26
- import { gridSelectionsObservable } from '../grid/gridObservables'
27
-
28
- export interface BaseLinesParams {
29
- // lineType: LineType
30
- lineCurve: string
31
- lineWidth: number
32
- // labelFn: (d: ComputedDatumSeries) => string
33
- // labelPositionFn: (d: ComputedDatumSeries) => 'top' | 'bottom' | 'left' | 'right' | 'center'
34
- // labelStyleFn: (d: ComputedDatumSeries) => string
35
- // labelFontSizeFn: (d: ComputedDatumSeries) => number
36
- // labelColorFn: (d: ComputedDatumSeries) => string
37
- // labelPadding: number
38
- }
39
-
40
- interface BaseLinesContext {
41
- selection: d3.Selection<any, unknown, any, unknown>
42
- computedData$: Observable<ComputedDataGrid>
43
- computedLayoutData$: Observable<ComputedLayoutDataGrid>
44
- visibleComputedData$: Observable<ComputedDatumGrid[][]>
45
- visibleComputedLayoutData$: Observable<ComputedLayoutDataGrid>
46
- seriesLabels$: Observable<string[]>
47
- SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
48
- GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
49
- fullDataFormatter$: Observable<DataFormatterGrid>
50
- fullParams$: Observable<BaseLinesParams>
51
- fullChartParams$: Observable<ChartParams>
52
- gridAxesTransform$: Observable<TransformData>
53
- gridGraphicTransform$: Observable<TransformData>
54
- gridAxesSize$: Observable<{
55
- width: number;
56
- height: number;
57
- }>
58
- gridHighlight$: Observable<ComputedDatumGrid[]>
59
- gridContainerPosition$: Observable<GridContainerPosition[]>
60
- event$: Subject<EventGrid>
61
- }
62
-
63
-
64
- type ClipPathDatum = {
65
- id: string;
66
- // x: number;
67
- // y: number;
68
- width: number;
69
- height: number;
70
- }
71
-
72
- // const pluginName = 'Lines'
73
- // const pathClassName = getClassName(pluginName, 'path')
74
-
75
-
76
- function createLinePath (lineCurve: string = 'curveLinear'): d3.Line<ComputedLayoutDatumGrid> {
77
- return d3.line<ComputedLayoutDatumGrid>()
78
- .x((d) => d.axisX)
79
- .y((d) => d.axisY)
80
- .curve((d3 as any)[lineCurve])
81
- }
82
-
83
- // 依無值的資料分段
84
- function makeSegmentData (data: ComputedLayoutDatumGrid[]): ComputedLayoutDatumGrid[][] {
85
- let segmentData: ComputedLayoutDatumGrid[][] = [[]]
86
-
87
- let currentIndex = 0
88
- for (let i in data) {
89
- if (data[i].visible == false || data[i].value === undefined || data[i].value === null) {
90
- // 換下一段的 index
91
- if (segmentData[currentIndex].length) {
92
- currentIndex ++
93
- segmentData[currentIndex] = []
94
- }
95
- continue
96
- }
97
- segmentData[currentIndex].push(data[i])
98
- }
99
-
100
- return segmentData
101
- }
102
-
103
-
104
- function renderLines ({ selection, pathClassName, segmentData, linePath, params }: {
105
- selection: d3.Selection<SVGGElement, unknown, any, unknown>
106
- pathClassName: string
107
- segmentData: ComputedLayoutDatumGrid[][]
108
- linePath: d3.Line<ComputedLayoutDatumGrid>
109
- params: BaseLinesParams
110
- }): d3.Selection<SVGPathElement, ComputedLayoutDatumGrid[], any, any> {
111
- // if (!data[0]) {
112
- // return undefined
113
- // }
114
-
115
- const lines = selection
116
- .selectAll<SVGPathElement, ComputedLayoutDatumGrid[]>('path')
117
- .data(segmentData, (d, i) => d.length ? `${d[0].id}_${d[d.length - 1].id}` : i) // 以線段起迄id結合為線段id
118
- .join(
119
- enter => {
120
- return enter
121
- .append<SVGPathElement>('path')
122
- .classed(pathClassName, true)
123
- .attr("fill","none")
124
- .attr('pointer-events', 'visibleStroke') // 只對線條產生事件
125
- .style('vector-effect', 'non-scaling-stroke')
126
- .style('cursor', 'pointer')
127
- },
128
- update => update,
129
- exit => exit.remove()
130
- )
131
- .attr("stroke-width", params.lineWidth)
132
- .attr("stroke", (d, i) => d[0] && d[0].color)
133
- .attr("d", (d) => {
134
- return linePath(d)
135
- })
136
-
137
- return lines
138
- }
139
-
140
- function highlightLines ({ selection, seriesLabel, fullChartParams }: {
141
- selection: d3.Selection<any, string, any, any>
142
- seriesLabel: string | null
143
- fullChartParams: ChartParams
144
- }) {
145
- selection.interrupt('highlight')
146
- if (!seriesLabel) {
147
- // remove highlight
148
- selection
149
- .transition('highlight')
150
- .duration(200)
151
- .style('opacity', 1)
152
- return
153
- }
154
-
155
- selection
156
- .each((currentSeriesLabel, i, n) => {
157
- // const currentSeriesLabel = d[0] ? d[0].seriesLabel : ''
158
-
159
- if (currentSeriesLabel === seriesLabel) {
160
- d3.select(n[i])
161
- .style('opacity', 1)
162
- } else {
163
- d3.select(n[i])
164
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
165
- }
166
- })
167
- }
168
-
169
- function renderClipPath ({ defsSelection, clipPathData, transitionDuration, transitionEase }: {
170
- defsSelection: d3.Selection<SVGDefsElement, any, any, any>
171
- clipPathData: ClipPathDatum[]
172
- transitionDuration: number
173
- transitionEase: string
174
- }) {
175
- const clipPath = defsSelection
176
- .selectAll<SVGClipPathElement, Layout>('clipPath')
177
- .data(clipPathData)
178
- .join(
179
- enter => {
180
- return enter
181
- .append('clipPath')
182
- },
183
- update => update,
184
- exit => exit.remove()
185
- )
186
- .attr('id', d => d.id)
187
- .each((d, i, g) => {
188
- const rect = d3.select(g[i])
189
- .selectAll<SVGRectElement, typeof d>('rect')
190
- .data([d])
191
- .join(
192
- enter => {
193
- const enterSelection = enter
194
- .append('rect')
195
- enterSelection
196
- .transition()
197
- .duration(transitionDuration)
198
- .ease(getD3TransitionEase(transitionEase))
199
- // .delay(100) // @Q@ 不知為何如果沒加 delay位置會有點跑掉
200
- .tween('tween', (_d, _i, _g) => {
201
- return (t) => {
202
- const transitionWidth = _d.width * t
203
-
204
- enterSelection
205
- .attr('x', 0)
206
- .attr('y', 0)
207
- .attr('width', _d => transitionWidth)
208
- .attr('height', _d => _d.height)
209
- }
210
- })
211
- return enterSelection
212
- },
213
- update => {
214
- return update
215
- .attr('x', 0)
216
- .attr('y', 0)
217
- .attr('width', _d => _d.width)
218
- .attr('height', _d => _d.height)
219
- },
220
- exit => exit.remove()
221
- )
222
- })
223
-
224
- }
225
-
226
- export const createBaseLines: BasePluginFn<BaseLinesContext> = (pluginName: string, {
227
- selection,
228
- computedData$,
229
- computedLayoutData$,
230
- visibleComputedData$,
231
- visibleComputedLayoutData$,
232
- seriesLabels$,
233
- SeriesDataMap$,
234
- GroupDataMap$,
235
- fullParams$,
236
- fullDataFormatter$,
237
- fullChartParams$,
238
- gridAxesTransform$,
239
- gridGraphicTransform$,
240
- gridAxesSize$,
241
- gridHighlight$,
242
- gridContainerPosition$,
243
- event$
244
- }) => {
245
-
246
- const destroy$ = new Subject()
247
-
248
- const clipPathID = getUniID(pluginName, 'clipPath-box')
249
- const pathClassName = getClassName(pluginName, 'path')
250
- // const clipPathSeriesID = getUniID(pluginName, 'clipPath')
251
-
252
- // const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection
253
- // .append('g')
254
- // .attr('clip-path', `url(#${clipPathID})`)
255
- // const defsSelection: d3.Selection<SVGDefsElement, any, any, any> = axisSelection.append('defs')
256
- // const graphicGSelection: d3.Selection<SVGGElement, any, any, any> = axisSelection.append('g')
257
- // const graphicSelection$: Subject<d3.Selection<SVGGElement, string, any, any>> = new Subject()
258
-
259
-
260
- // gridAxesTransform$
261
- // .pipe(
262
- // takeUntil(destroy$),
263
- // map(d => d.value),
264
- // distinctUntilChanged()
265
- // ).subscribe(d => {
266
- // axisSelection
267
- // .style('transform', d)
268
- // })
269
-
270
- // gridGraphicTransform$
271
- // .pipe(
272
- // takeUntil(destroy$),
273
- // map(d => d.value),
274
- // distinctUntilChanged()
275
- // ).subscribe(d => {
276
- // graphicGSelection
277
- // .transition()
278
- // .duration(50)
279
- // .style('transform', d)
280
- // })
281
-
282
- // const seriesSelection$ = computedData$.pipe(
283
- // takeUntil(destroy$),
284
- // distinctUntilChanged((a, b) => {
285
- // // 只有當series的數量改變時,才重新計算
286
- // return a.length === b.length
287
- // }),
288
- // map((computedData, i) => {
289
- // return selection
290
- // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${seriesClassName}`)
291
- // .data(computedData, d => d[0] ? d[0].seriesIndex : i)
292
- // .join(
293
- // enter => {
294
- // return enter
295
- // .append('g')
296
- // .classed(seriesClassName, true)
297
- // .each((d, i, g) => {
298
- // const axesSelection = d3.select(g[i])
299
- // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${axesClassName}`)
300
- // .data([i])
301
- // .join(
302
- // enter => {
303
- // return enter
304
- // .append('g')
305
- // .classed(axesClassName, true)
306
- // .attr('clip-path', `url(#${clipPathID})`)
307
- // .each((d, i, g) => {
308
- // const defsSelection = d3.select(g[i])
309
- // .selectAll<SVGDefsElement, any>('defs')
310
- // .data([i])
311
- // .join('defs')
312
-
313
- // const graphicGSelection = d3.select(g[i])
314
- // .selectAll<SVGGElement, any>('g')
315
- // .data([i])
316
- // .join('g')
317
- // .classed(graphicClassName, true)
318
- // })
319
- // },
320
- // update => update,
321
- // exit => exit.remove()
322
- // )
323
- // })
324
- // },
325
- // update => update,
326
- // exit => exit.remove()
327
- // )
328
- // })
329
- // )
330
-
331
- // combineLatest({
332
- // seriesSelection: seriesSelection$,
333
- // gridContainerPosition: gridContainerPosition$
334
- // }).pipe(
335
- // takeUntil(destroy$),
336
- // switchMap(async d => d)
337
- // ).subscribe(data => {
338
- // data.seriesSelection
339
- // .transition()
340
- // .attr('transform', (d, i) => {
341
- // const translate = data.gridContainerPosition[i].translate
342
- // const scale = data.gridContainerPosition[i].scale
343
- // return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
344
- // })
345
- // })
346
-
347
-
348
- // const axesSelection$ = combineLatest({
349
- // seriesSelection: seriesSelection$,
350
- // gridAxesTransform: gridAxesTransform$
351
- // }).pipe(
352
- // takeUntil(destroy$),
353
- // switchMap(async d => d),
354
- // map(data => {
355
- // return data.seriesSelection
356
- // .select<SVGGElement>(`g.${axesClassName}`)
357
- // .style('transform', data.gridAxesTransform.value)
358
- // })
359
- // )
360
- // const defsSelection$ = axesSelection$.pipe(
361
- // takeUntil(destroy$),
362
- // map(axesSelection => {
363
- // return axesSelection.select<SVGDefsElement>('defs')
364
- // })
365
- // )
366
- // const graphicGSelection$ = combineLatest({
367
- // axesSelection: axesSelection$,
368
- // gridGraphicTransform: gridGraphicTransform$
369
- // }).pipe(
370
- // takeUntil(destroy$),
371
- // switchMap(async d => d),
372
- // map(data => {
373
- // const graphicGSelection = data.axesSelection
374
- // .select<SVGGElement>(`g.${graphicClassName}`)
375
- // .attr('clip-path', (d) => `url(#${clipPathSeriesID}-${d[0] ? d[0].seriesLabel : ''})`)
376
- // graphicGSelection
377
- // .transition()
378
- // .duration(50)
379
- // .style('transform', data.gridGraphicTransform.value)
380
- // return graphicGSelection
381
- // })
382
- // )
383
-
384
- const {
385
- seriesSelection$,
386
- axesSelection$,
387
- defsSelection$,
388
- graphicGSelection$
389
- } = gridSelectionsObservable({
390
- selection,
391
- pluginName,
392
- clipPathID,
393
- seriesLabels$,
394
- gridContainerPosition$,
395
- gridAxesTransform$,
396
- gridGraphicTransform$
397
- })
398
-
399
- const linePath$: Observable<d3.Line<ComputedLayoutDatumGrid>> = new Observable(subscriber => {
400
- const paramsSubscription = fullParams$
401
- .pipe(
402
- takeUntil(destroy$)
403
- )
404
- .subscribe(d => {
405
- if (!d) return
406
- const linePath = createLinePath(d.lineCurve)
407
- subscriber.next(linePath)
408
- })
409
- return () => {
410
- paramsSubscription.unsubscribe()
411
- }
412
- })
413
-
414
- // // 顯示範圍內的series labels
415
- // const seriesLabels$: Observable<string[]> = new Observable(subscriber => {
416
- // computedData$.pipe(
417
- // takeUntil(destroy$),
418
- // switchMap(async (d) => d),
419
- // ).subscribe(data => {
420
- // const labels = data[0] && data[0][0]
421
- // ? data.map(d => d[0].seriesLabel)
422
- // : []
423
- // subscriber.next(labels)
424
- // })
425
- // })
426
-
427
- // const axisSize$ = gridAxisSizeObservable({
428
- // fullDataFormatter$,
429
- // computedLayout$
430
- // })
431
-
432
- const transitionDuration$ = fullChartParams$
433
- .pipe(
434
- map(d => d.transitionDuration),
435
- distinctUntilChanged()
436
- )
437
-
438
- const transitionEase$ = fullChartParams$
439
- .pipe(
440
- map(d => d.transitionEase),
441
- distinctUntilChanged()
442
- )
443
-
444
- const clipPathSubscription = combineLatest({
445
- defsSelection: defsSelection$,
446
- seriesLabels: seriesLabels$,
447
- axisSize: gridAxesSize$,
448
- transitionDuration: transitionDuration$,
449
- transitionEase: transitionEase$
450
- }).pipe(
451
- takeUntil(destroy$),
452
- switchMap(async (d) => d),
453
- ).subscribe(data => {
454
- // 外層的遮罩
455
- const clipPathBox = [{
456
- id: clipPathID,
457
- width: data.axisSize.width,
458
- height: data.axisSize.height
459
- }]
460
- // 各別線條的遮罩(各別動畫)
461
- const clipPathData = clipPathBox.concat(
462
- data.seriesLabels.map(d => {
463
- return {
464
- id: `orbcharts__clipPath_${d}`,
465
- width: data.axisSize.width,
466
- height: data.axisSize.height
467
- }
468
- })
469
- )
470
- renderClipPath({
471
- defsSelection: data.defsSelection,
472
- clipPathData,
473
- transitionDuration: data.transitionDuration,
474
- transitionEase: data.transitionEase
475
- })
476
- })
477
-
478
- // const SeriesDataMap$ = computedData$.pipe(
479
- // map(d => makeGridSeriesDataMap(d))
480
- // )
481
-
482
- // const GroupDataMap$ = computedData$.pipe(
483
- // map(d => makeGridGroupDataMap(d))
484
- // )
485
-
486
- const DataMap$ = computedData$.pipe(
487
- map(d => {
488
- const DataMap: Map<string, ComputedDatumGrid> = new Map()
489
- d.flat().forEach(_d => DataMap.set(_d.id, _d))
490
- return DataMap
491
- })
492
- )
493
-
494
- // 取得事件座標的group資料
495
- const gridGroupPositionFn$ = gridGroupPositionFnObservable({
496
- fullDataFormatter$,
497
- gridAxesSize$: gridAxesSize$,
498
- computedData$: computedData$,
499
- fullChartParams$: fullChartParams$
500
- })
501
-
502
- const highlightTarget$ = fullChartParams$.pipe(
503
- takeUntil(destroy$),
504
- map(d => d.highlightTarget),
505
- distinctUntilChanged()
506
- )
507
-
508
- const pathSelectionArr$ = combineLatest({
509
- graphicGSelection: graphicGSelection$,
510
- visibleComputedLayoutData: visibleComputedLayoutData$,
511
- linePath: linePath$,
512
- params: fullParams$,
513
- }).pipe(
514
- takeUntil(destroy$),
515
- switchMap(async (d) => d),
516
- map(data => {
517
- // const updateGraphic = data.graphicGSelection
518
- // .selectAll<SVGGElement, number>('g')
519
- // .data(data.seriesLabels, (d, i) => d)
520
- // const enterGraphic = updateGraphic.enter()
521
- // .append('g')
522
- // .classed(graphicClassName, true)
523
- // updateGraphic.exit().remove()
524
- // const graphicSelection = updateGraphic.merge(enterGraphic)
525
- // .attr('clip-path', (d, i) => `url(#orbcharts__clipPath_${d})`)
526
- let pathSelectionArr: d3.Selection<SVGPathElement, ComputedLayoutDatumGrid[], any, any>[] = []
527
-
528
- // 繪圖
529
- data.graphicGSelection.each((d, i, all) => {
530
- // 將資料分段
531
- const segmentData = makeSegmentData(data.visibleComputedLayoutData[i] ?? [])
532
-
533
- pathSelectionArr[i] = renderLines({
534
- selection: d3.select(all[i]),
535
- pathClassName,
536
- linePath: data.linePath,
537
- segmentData: segmentData,
538
- params: data.params
539
- })
540
- })
541
-
542
- return pathSelectionArr
543
- })
544
-
545
-
546
- )
547
-
548
- combineLatest({
549
- pathSelectionArr: pathSelectionArr$,
550
- computedData: computedData$,
551
- SeriesDataMap: SeriesDataMap$,
552
- GroupDataMap: GroupDataMap$,
553
- highlightTarget: highlightTarget$,
554
- gridGroupPositionFn: gridGroupPositionFn$,
555
- }).pipe(
556
- takeUntil(destroy$),
557
- switchMap(async (d) => d)
558
- ).subscribe(data => {
559
- data.pathSelectionArr.forEach(pathSelection => {
560
- pathSelection
561
- .on('mouseover', (event, datum) => {
562
- event.stopPropagation()
563
-
564
- const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
565
- const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
566
- const groupData = data.GroupDataMap.get(groupLabel)!
567
- const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
568
- const _datum = targetDatum ?? datum[0]
569
-
570
- event$.next({
571
- type: 'grid',
572
- eventName: 'mouseover',
573
- pluginName,
574
- highlightTarget: data.highlightTarget,
575
- datum: _datum,
576
- gridIndex: _datum.gridIndex,
577
- series: data.SeriesDataMap.get(_datum.seriesLabel)!,
578
- seriesIndex: _datum.seriesIndex,
579
- seriesLabel: _datum.seriesLabel,
580
- groups: data.GroupDataMap.get(_datum.groupLabel)!,
581
- groupIndex: _datum.groupIndex,
582
- groupLabel: _datum.groupLabel,
583
- event,
584
- data: data.computedData
585
- })
586
- })
587
- .on('mousemove', (event, datum) => {
588
- event.stopPropagation()
589
-
590
- const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
591
- const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
592
- const groupData = data.GroupDataMap.get(groupLabel)!
593
- const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
594
- const _datum = targetDatum ?? datum[0]
595
-
596
- event$.next({
597
- type: 'grid',
598
- eventName: 'mousemove',
599
- pluginName,
600
- highlightTarget: data.highlightTarget,
601
- datum: _datum,
602
- gridIndex: _datum.gridIndex,
603
- series: data.SeriesDataMap.get(_datum.seriesLabel)!,
604
- seriesIndex: _datum.seriesIndex,
605
- seriesLabel: _datum.seriesLabel,
606
- groups: data.GroupDataMap.get(_datum.groupLabel)!,
607
- groupIndex: _datum.groupIndex,
608
- groupLabel: _datum.groupLabel,
609
- event,
610
- data: data.computedData
611
- })
612
- })
613
- .on('mouseout', (event, datum) => {
614
- event.stopPropagation()
615
-
616
- const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
617
- const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
618
- const groupData = data.GroupDataMap.get(groupLabel)!
619
- const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
620
- const _datum = targetDatum ?? datum[0]
621
-
622
- event$.next({
623
- type: 'grid',
624
- eventName: 'mouseout',
625
- pluginName,
626
- highlightTarget: data.highlightTarget,
627
- datum: _datum,
628
- gridIndex: _datum.gridIndex,
629
- series: data.SeriesDataMap.get(_datum.seriesLabel)!,
630
- seriesIndex: _datum.seriesIndex,
631
- seriesLabel: _datum.seriesLabel,
632
- groups: data.GroupDataMap.get(_datum.groupLabel)!,
633
- groupIndex: _datum.groupIndex,
634
- groupLabel: _datum.groupLabel,
635
- event,
636
- data: data.computedData
637
- })
638
- })
639
- .on('click', (event, datum) => {
640
- event.stopPropagation()
641
-
642
- const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
643
- const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
644
- const groupData = data.GroupDataMap.get(groupLabel)!
645
- const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
646
- const _datum = targetDatum ?? datum[0]
647
-
648
- event$.next({
649
- type: 'grid',
650
- eventName: 'click',
651
- pluginName,
652
- highlightTarget: data.highlightTarget,
653
- datum: _datum,
654
- gridIndex: _datum.gridIndex,
655
- series: data.SeriesDataMap.get(_datum.seriesLabel)!,
656
- seriesIndex: _datum.seriesIndex,
657
- seriesLabel: _datum.seriesLabel,
658
- groups: data.GroupDataMap.get(_datum.groupLabel)!,
659
- groupIndex: _datum.groupIndex,
660
- groupLabel: _datum.groupLabel,
661
- event,
662
- data: data.computedData
663
- })
664
- })
665
- })
666
- })
667
-
668
- // const datumList$ = computedData$.pipe(
669
- // takeUntil(destroy$),
670
- // map(d => d.flat())
671
- // )
672
- // const highlight$ = highlightObservable({ datumList$, fullChartParams$, event$: store.event$ })
673
- // const highlightSubscription = gridHighlight$.subscribe()
674
-
675
- fullChartParams$.pipe(
676
- takeUntil(destroy$),
677
- filter(d => d.highlightTarget === 'series'),
678
- switchMap(d => combineLatest({
679
- graphicGSelection: graphicGSelection$,
680
- gridHighlight: gridHighlight$,
681
- DataMap: DataMap$,
682
- fullChartParams: fullChartParams$
683
- }).pipe(
684
- takeUntil(destroy$),
685
- switchMap(async d => d)
686
- ))
687
- ).subscribe(data => {
688
- const seriesLabel = data.gridHighlight[0] ? data.gridHighlight[0].seriesLabel : null
689
- highlightLines({
690
- selection: data.graphicGSelection,
691
- seriesLabel,
692
- fullChartParams: data.fullChartParams
693
- })
694
- })
695
-
696
- return () => {
697
- destroy$.next(undefined)
698
- // highlightSubscription.unsubscribe()
699
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ filter,
6
+ switchMap,
7
+ takeUntil,
8
+ distinctUntilChanged,
9
+ Observable,
10
+ Subject } from 'rxjs'
11
+ import type { BasePluginFn } from './types'
12
+ import type {
13
+ ComputedDatumGrid,
14
+ ComputedDataGrid,
15
+ ComputedLayoutDatumGrid,
16
+ ComputedLayoutDataGrid,
17
+ DataFormatterGrid,
18
+ EventGrid,
19
+ GridContainerPosition,
20
+ ChartParams,
21
+ Layout,
22
+ TransformData } from '@orbcharts/core'
23
+ import { getD3TransitionEase } from '../utils/d3Utils'
24
+ import { getClassName, getUniID } from '../utils/orbchartsUtils'
25
+ import { gridGroupPositionFnObservable } from '../grid/gridObservables'
26
+ import { gridSelectionsObservable } from '../grid/gridObservables'
27
+
28
+ export interface BaseLinesParams {
29
+ // lineType: LineType
30
+ lineCurve: string
31
+ lineWidth: number
32
+ // labelFn: (d: ComputedDatumSeries) => string
33
+ // labelPositionFn: (d: ComputedDatumSeries) => 'top' | 'bottom' | 'left' | 'right' | 'center'
34
+ // labelStyleFn: (d: ComputedDatumSeries) => string
35
+ // labelFontSizeFn: (d: ComputedDatumSeries) => number
36
+ // labelColorFn: (d: ComputedDatumSeries) => string
37
+ // labelPadding: number
38
+ }
39
+
40
+ interface BaseLinesContext {
41
+ selection: d3.Selection<any, unknown, any, unknown>
42
+ computedData$: Observable<ComputedDataGrid>
43
+ computedLayoutData$: Observable<ComputedLayoutDataGrid>
44
+ visibleComputedData$: Observable<ComputedDatumGrid[][]>
45
+ visibleComputedLayoutData$: Observable<ComputedLayoutDataGrid>
46
+ seriesLabels$: Observable<string[]>
47
+ SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
48
+ GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
49
+ fullDataFormatter$: Observable<DataFormatterGrid>
50
+ fullParams$: Observable<BaseLinesParams>
51
+ fullChartParams$: Observable<ChartParams>
52
+ gridAxesTransform$: Observable<TransformData>
53
+ gridGraphicTransform$: Observable<TransformData>
54
+ gridAxesSize$: Observable<{
55
+ width: number;
56
+ height: number;
57
+ }>
58
+ gridHighlight$: Observable<ComputedDatumGrid[]>
59
+ gridContainerPosition$: Observable<GridContainerPosition[]>
60
+ event$: Subject<EventGrid>
61
+ }
62
+
63
+
64
+ type ClipPathDatum = {
65
+ id: string;
66
+ // x: number;
67
+ // y: number;
68
+ width: number;
69
+ height: number;
70
+ }
71
+
72
+ // const pluginName = 'Lines'
73
+ // const pathClassName = getClassName(pluginName, 'path')
74
+
75
+
76
+ function createLinePath (lineCurve: string = 'curveLinear'): d3.Line<ComputedLayoutDatumGrid> {
77
+ return d3.line<ComputedLayoutDatumGrid>()
78
+ .x((d) => d.axisX)
79
+ .y((d) => d.axisY)
80
+ .curve((d3 as any)[lineCurve])
81
+ }
82
+
83
+ // 依無值的資料分段
84
+ function makeSegmentData (data: ComputedLayoutDatumGrid[]): ComputedLayoutDatumGrid[][] {
85
+ let segmentData: ComputedLayoutDatumGrid[][] = [[]]
86
+
87
+ let currentIndex = 0
88
+ for (let i in data) {
89
+ if (data[i].visible == false || data[i].value === undefined || data[i].value === null) {
90
+ // 換下一段的 index
91
+ if (segmentData[currentIndex].length) {
92
+ currentIndex ++
93
+ segmentData[currentIndex] = []
94
+ }
95
+ continue
96
+ }
97
+ segmentData[currentIndex].push(data[i])
98
+ }
99
+
100
+ return segmentData
101
+ }
102
+
103
+
104
+ function renderLines ({ selection, pathClassName, segmentData, linePath, params }: {
105
+ selection: d3.Selection<SVGGElement, unknown, any, unknown>
106
+ pathClassName: string
107
+ segmentData: ComputedLayoutDatumGrid[][]
108
+ linePath: d3.Line<ComputedLayoutDatumGrid>
109
+ params: BaseLinesParams
110
+ }): d3.Selection<SVGPathElement, ComputedLayoutDatumGrid[], any, any> {
111
+ // if (!data[0]) {
112
+ // return undefined
113
+ // }
114
+
115
+ const lines = selection
116
+ .selectAll<SVGPathElement, ComputedLayoutDatumGrid[]>('path')
117
+ .data(segmentData, (d, i) => d.length ? `${d[0].id}_${d[d.length - 1].id}` : i) // 以線段起迄id結合為線段id
118
+ .join(
119
+ enter => {
120
+ return enter
121
+ .append<SVGPathElement>('path')
122
+ .classed(pathClassName, true)
123
+ .attr("fill","none")
124
+ .attr('pointer-events', 'visibleStroke') // 只對線條產生事件
125
+ .style('vector-effect', 'non-scaling-stroke')
126
+ .style('cursor', 'pointer')
127
+ },
128
+ update => update,
129
+ exit => exit.remove()
130
+ )
131
+ .attr("stroke-width", params.lineWidth)
132
+ .attr("stroke", (d, i) => d[0] && d[0].color)
133
+ .attr("d", (d) => {
134
+ return linePath(d)
135
+ })
136
+
137
+ return lines
138
+ }
139
+
140
+ function highlightLines ({ selection, seriesLabel, fullChartParams }: {
141
+ selection: d3.Selection<any, string, any, any>
142
+ seriesLabel: string | null
143
+ fullChartParams: ChartParams
144
+ }) {
145
+ selection.interrupt('highlight')
146
+ if (!seriesLabel) {
147
+ // remove highlight
148
+ selection
149
+ .transition('highlight')
150
+ .duration(200)
151
+ .style('opacity', 1)
152
+ return
153
+ }
154
+
155
+ selection
156
+ .each((currentSeriesLabel, i, n) => {
157
+ // const currentSeriesLabel = d[0] ? d[0].seriesLabel : ''
158
+
159
+ if (currentSeriesLabel === seriesLabel) {
160
+ d3.select(n[i])
161
+ .style('opacity', 1)
162
+ } else {
163
+ d3.select(n[i])
164
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
165
+ }
166
+ })
167
+ }
168
+
169
+ function renderClipPath ({ defsSelection, clipPathData, transitionDuration, transitionEase }: {
170
+ defsSelection: d3.Selection<SVGDefsElement, any, any, any>
171
+ clipPathData: ClipPathDatum[]
172
+ transitionDuration: number
173
+ transitionEase: string
174
+ }) {
175
+ const clipPath = defsSelection
176
+ .selectAll<SVGClipPathElement, Layout>('clipPath')
177
+ .data(clipPathData)
178
+ .join(
179
+ enter => {
180
+ return enter
181
+ .append('clipPath')
182
+ },
183
+ update => update,
184
+ exit => exit.remove()
185
+ )
186
+ .attr('id', d => d.id)
187
+ .each((d, i, g) => {
188
+ const rect = d3.select(g[i])
189
+ .selectAll<SVGRectElement, typeof d>('rect')
190
+ .data([d])
191
+ .join(
192
+ enter => {
193
+ const enterSelection = enter
194
+ .append('rect')
195
+ enterSelection
196
+ .transition()
197
+ .duration(transitionDuration)
198
+ .ease(getD3TransitionEase(transitionEase))
199
+ // .delay(100) // @Q@ 不知為何如果沒加 delay位置會有點跑掉
200
+ .tween('tween', (_d, _i, _g) => {
201
+ return (t) => {
202
+ const transitionWidth = _d.width * t
203
+
204
+ enterSelection
205
+ .attr('x', 0)
206
+ .attr('y', 0)
207
+ .attr('width', _d => transitionWidth)
208
+ .attr('height', _d => _d.height)
209
+ }
210
+ })
211
+ return enterSelection
212
+ },
213
+ update => {
214
+ return update
215
+ .attr('x', 0)
216
+ .attr('y', 0)
217
+ .attr('width', _d => _d.width)
218
+ .attr('height', _d => _d.height)
219
+ },
220
+ exit => exit.remove()
221
+ )
222
+ })
223
+
224
+ }
225
+
226
+ export const createBaseLines: BasePluginFn<BaseLinesContext> = (pluginName: string, {
227
+ selection,
228
+ computedData$,
229
+ computedLayoutData$,
230
+ visibleComputedData$,
231
+ visibleComputedLayoutData$,
232
+ seriesLabels$,
233
+ SeriesDataMap$,
234
+ GroupDataMap$,
235
+ fullParams$,
236
+ fullDataFormatter$,
237
+ fullChartParams$,
238
+ gridAxesTransform$,
239
+ gridGraphicTransform$,
240
+ gridAxesSize$,
241
+ gridHighlight$,
242
+ gridContainerPosition$,
243
+ event$
244
+ }) => {
245
+
246
+ const destroy$ = new Subject()
247
+
248
+ const clipPathID = getUniID(pluginName, 'clipPath-box')
249
+ const pathClassName = getClassName(pluginName, 'path')
250
+ // const clipPathSeriesID = getUniID(pluginName, 'clipPath')
251
+
252
+ // const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection
253
+ // .append('g')
254
+ // .attr('clip-path', `url(#${clipPathID})`)
255
+ // const defsSelection: d3.Selection<SVGDefsElement, any, any, any> = axisSelection.append('defs')
256
+ // const graphicGSelection: d3.Selection<SVGGElement, any, any, any> = axisSelection.append('g')
257
+ // const graphicSelection$: Subject<d3.Selection<SVGGElement, string, any, any>> = new Subject()
258
+
259
+
260
+ // gridAxesTransform$
261
+ // .pipe(
262
+ // takeUntil(destroy$),
263
+ // map(d => d.value),
264
+ // distinctUntilChanged()
265
+ // ).subscribe(d => {
266
+ // axisSelection
267
+ // .style('transform', d)
268
+ // })
269
+
270
+ // gridGraphicTransform$
271
+ // .pipe(
272
+ // takeUntil(destroy$),
273
+ // map(d => d.value),
274
+ // distinctUntilChanged()
275
+ // ).subscribe(d => {
276
+ // graphicGSelection
277
+ // .transition()
278
+ // .duration(50)
279
+ // .style('transform', d)
280
+ // })
281
+
282
+ // const seriesSelection$ = computedData$.pipe(
283
+ // takeUntil(destroy$),
284
+ // distinctUntilChanged((a, b) => {
285
+ // // 只有當series的數量改變時,才重新計算
286
+ // return a.length === b.length
287
+ // }),
288
+ // map((computedData, i) => {
289
+ // return selection
290
+ // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${seriesClassName}`)
291
+ // .data(computedData, d => d[0] ? d[0].seriesIndex : i)
292
+ // .join(
293
+ // enter => {
294
+ // return enter
295
+ // .append('g')
296
+ // .classed(seriesClassName, true)
297
+ // .each((d, i, g) => {
298
+ // const axesSelection = d3.select(g[i])
299
+ // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${axesClassName}`)
300
+ // .data([i])
301
+ // .join(
302
+ // enter => {
303
+ // return enter
304
+ // .append('g')
305
+ // .classed(axesClassName, true)
306
+ // .attr('clip-path', `url(#${clipPathID})`)
307
+ // .each((d, i, g) => {
308
+ // const defsSelection = d3.select(g[i])
309
+ // .selectAll<SVGDefsElement, any>('defs')
310
+ // .data([i])
311
+ // .join('defs')
312
+
313
+ // const graphicGSelection = d3.select(g[i])
314
+ // .selectAll<SVGGElement, any>('g')
315
+ // .data([i])
316
+ // .join('g')
317
+ // .classed(graphicClassName, true)
318
+ // })
319
+ // },
320
+ // update => update,
321
+ // exit => exit.remove()
322
+ // )
323
+ // })
324
+ // },
325
+ // update => update,
326
+ // exit => exit.remove()
327
+ // )
328
+ // })
329
+ // )
330
+
331
+ // combineLatest({
332
+ // seriesSelection: seriesSelection$,
333
+ // gridContainerPosition: gridContainerPosition$
334
+ // }).pipe(
335
+ // takeUntil(destroy$),
336
+ // switchMap(async d => d)
337
+ // ).subscribe(data => {
338
+ // data.seriesSelection
339
+ // .transition()
340
+ // .attr('transform', (d, i) => {
341
+ // const translate = data.gridContainerPosition[i].translate
342
+ // const scale = data.gridContainerPosition[i].scale
343
+ // return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
344
+ // })
345
+ // })
346
+
347
+
348
+ // const axesSelection$ = combineLatest({
349
+ // seriesSelection: seriesSelection$,
350
+ // gridAxesTransform: gridAxesTransform$
351
+ // }).pipe(
352
+ // takeUntil(destroy$),
353
+ // switchMap(async d => d),
354
+ // map(data => {
355
+ // return data.seriesSelection
356
+ // .select<SVGGElement>(`g.${axesClassName}`)
357
+ // .style('transform', data.gridAxesTransform.value)
358
+ // })
359
+ // )
360
+ // const defsSelection$ = axesSelection$.pipe(
361
+ // takeUntil(destroy$),
362
+ // map(axesSelection => {
363
+ // return axesSelection.select<SVGDefsElement>('defs')
364
+ // })
365
+ // )
366
+ // const graphicGSelection$ = combineLatest({
367
+ // axesSelection: axesSelection$,
368
+ // gridGraphicTransform: gridGraphicTransform$
369
+ // }).pipe(
370
+ // takeUntil(destroy$),
371
+ // switchMap(async d => d),
372
+ // map(data => {
373
+ // const graphicGSelection = data.axesSelection
374
+ // .select<SVGGElement>(`g.${graphicClassName}`)
375
+ // .attr('clip-path', (d) => `url(#${clipPathSeriesID}-${d[0] ? d[0].seriesLabel : ''})`)
376
+ // graphicGSelection
377
+ // .transition()
378
+ // .duration(50)
379
+ // .style('transform', data.gridGraphicTransform.value)
380
+ // return graphicGSelection
381
+ // })
382
+ // )
383
+
384
+ const {
385
+ seriesSelection$,
386
+ axesSelection$,
387
+ defsSelection$,
388
+ graphicGSelection$
389
+ } = gridSelectionsObservable({
390
+ selection,
391
+ pluginName,
392
+ clipPathID,
393
+ seriesLabels$,
394
+ gridContainerPosition$,
395
+ gridAxesTransform$,
396
+ gridGraphicTransform$
397
+ })
398
+
399
+ const linePath$: Observable<d3.Line<ComputedLayoutDatumGrid>> = new Observable(subscriber => {
400
+ const paramsSubscription = fullParams$
401
+ .pipe(
402
+ takeUntil(destroy$)
403
+ )
404
+ .subscribe(d => {
405
+ if (!d) return
406
+ const linePath = createLinePath(d.lineCurve)
407
+ subscriber.next(linePath)
408
+ })
409
+ return () => {
410
+ paramsSubscription.unsubscribe()
411
+ }
412
+ })
413
+
414
+ // // 顯示範圍內的series labels
415
+ // const seriesLabels$: Observable<string[]> = new Observable(subscriber => {
416
+ // computedData$.pipe(
417
+ // takeUntil(destroy$),
418
+ // switchMap(async (d) => d),
419
+ // ).subscribe(data => {
420
+ // const labels = data[0] && data[0][0]
421
+ // ? data.map(d => d[0].seriesLabel)
422
+ // : []
423
+ // subscriber.next(labels)
424
+ // })
425
+ // })
426
+
427
+ // const axisSize$ = gridAxisSizeObservable({
428
+ // fullDataFormatter$,
429
+ // computedLayout$
430
+ // })
431
+
432
+ const transitionDuration$ = fullChartParams$
433
+ .pipe(
434
+ map(d => d.transitionDuration),
435
+ distinctUntilChanged()
436
+ )
437
+
438
+ const transitionEase$ = fullChartParams$
439
+ .pipe(
440
+ map(d => d.transitionEase),
441
+ distinctUntilChanged()
442
+ )
443
+
444
+ const clipPathSubscription = combineLatest({
445
+ defsSelection: defsSelection$,
446
+ seriesLabels: seriesLabels$,
447
+ axisSize: gridAxesSize$,
448
+ transitionDuration: transitionDuration$,
449
+ transitionEase: transitionEase$
450
+ }).pipe(
451
+ takeUntil(destroy$),
452
+ switchMap(async (d) => d),
453
+ ).subscribe(data => {
454
+ // 外層的遮罩
455
+ const clipPathBox = [{
456
+ id: clipPathID,
457
+ width: data.axisSize.width,
458
+ height: data.axisSize.height
459
+ }]
460
+ // 各別線條的遮罩(各別動畫)
461
+ const clipPathData = clipPathBox.concat(
462
+ data.seriesLabels.map(d => {
463
+ return {
464
+ id: `orbcharts__clipPath_${d}`,
465
+ width: data.axisSize.width,
466
+ height: data.axisSize.height
467
+ }
468
+ })
469
+ )
470
+ renderClipPath({
471
+ defsSelection: data.defsSelection,
472
+ clipPathData,
473
+ transitionDuration: data.transitionDuration,
474
+ transitionEase: data.transitionEase
475
+ })
476
+ })
477
+
478
+ // const SeriesDataMap$ = computedData$.pipe(
479
+ // map(d => makeGridSeriesDataMap(d))
480
+ // )
481
+
482
+ // const GroupDataMap$ = computedData$.pipe(
483
+ // map(d => makeGridGroupDataMap(d))
484
+ // )
485
+
486
+ const DataMap$ = computedData$.pipe(
487
+ map(d => {
488
+ const DataMap: Map<string, ComputedDatumGrid> = new Map()
489
+ d.flat().forEach(_d => DataMap.set(_d.id, _d))
490
+ return DataMap
491
+ })
492
+ )
493
+
494
+ // 取得事件座標的group資料
495
+ const gridGroupPositionFn$ = gridGroupPositionFnObservable({
496
+ fullDataFormatter$,
497
+ gridAxesSize$: gridAxesSize$,
498
+ computedData$: computedData$,
499
+ fullChartParams$: fullChartParams$
500
+ })
501
+
502
+ const highlightTarget$ = fullChartParams$.pipe(
503
+ takeUntil(destroy$),
504
+ map(d => d.highlightTarget),
505
+ distinctUntilChanged()
506
+ )
507
+
508
+ const pathSelectionArr$ = combineLatest({
509
+ graphicGSelection: graphicGSelection$,
510
+ visibleComputedLayoutData: visibleComputedLayoutData$,
511
+ linePath: linePath$,
512
+ params: fullParams$,
513
+ }).pipe(
514
+ takeUntil(destroy$),
515
+ switchMap(async (d) => d),
516
+ map(data => {
517
+ // const updateGraphic = data.graphicGSelection
518
+ // .selectAll<SVGGElement, number>('g')
519
+ // .data(data.seriesLabels, (d, i) => d)
520
+ // const enterGraphic = updateGraphic.enter()
521
+ // .append('g')
522
+ // .classed(graphicClassName, true)
523
+ // updateGraphic.exit().remove()
524
+ // const graphicSelection = updateGraphic.merge(enterGraphic)
525
+ // .attr('clip-path', (d, i) => `url(#orbcharts__clipPath_${d})`)
526
+ let pathSelectionArr: d3.Selection<SVGPathElement, ComputedLayoutDatumGrid[], any, any>[] = []
527
+
528
+ // 繪圖
529
+ data.graphicGSelection.each((d, i, all) => {
530
+ // 將資料分段
531
+ const segmentData = makeSegmentData(data.visibleComputedLayoutData[i] ?? [])
532
+
533
+ pathSelectionArr[i] = renderLines({
534
+ selection: d3.select(all[i]),
535
+ pathClassName,
536
+ linePath: data.linePath,
537
+ segmentData: segmentData,
538
+ params: data.params
539
+ })
540
+ })
541
+
542
+ return pathSelectionArr
543
+ })
544
+
545
+
546
+ )
547
+
548
+ combineLatest({
549
+ pathSelectionArr: pathSelectionArr$,
550
+ computedData: computedData$,
551
+ SeriesDataMap: SeriesDataMap$,
552
+ GroupDataMap: GroupDataMap$,
553
+ highlightTarget: highlightTarget$,
554
+ gridGroupPositionFn: gridGroupPositionFn$,
555
+ }).pipe(
556
+ takeUntil(destroy$),
557
+ switchMap(async (d) => d)
558
+ ).subscribe(data => {
559
+ data.pathSelectionArr.forEach(pathSelection => {
560
+ pathSelection
561
+ .on('mouseover', (event, datum) => {
562
+ event.stopPropagation()
563
+
564
+ const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
565
+ const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
566
+ const groupData = data.GroupDataMap.get(groupLabel)!
567
+ const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
568
+ const _datum = targetDatum ?? datum[0]
569
+
570
+ event$.next({
571
+ type: 'grid',
572
+ eventName: 'mouseover',
573
+ pluginName,
574
+ highlightTarget: data.highlightTarget,
575
+ datum: _datum,
576
+ gridIndex: _datum.gridIndex,
577
+ series: data.SeriesDataMap.get(_datum.seriesLabel)!,
578
+ seriesIndex: _datum.seriesIndex,
579
+ seriesLabel: _datum.seriesLabel,
580
+ groups: data.GroupDataMap.get(_datum.groupLabel)!,
581
+ groupIndex: _datum.groupIndex,
582
+ groupLabel: _datum.groupLabel,
583
+ event,
584
+ data: data.computedData
585
+ })
586
+ })
587
+ .on('mousemove', (event, datum) => {
588
+ event.stopPropagation()
589
+
590
+ const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
591
+ const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
592
+ const groupData = data.GroupDataMap.get(groupLabel)!
593
+ const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
594
+ const _datum = targetDatum ?? datum[0]
595
+
596
+ event$.next({
597
+ type: 'grid',
598
+ eventName: 'mousemove',
599
+ pluginName,
600
+ highlightTarget: data.highlightTarget,
601
+ datum: _datum,
602
+ gridIndex: _datum.gridIndex,
603
+ series: data.SeriesDataMap.get(_datum.seriesLabel)!,
604
+ seriesIndex: _datum.seriesIndex,
605
+ seriesLabel: _datum.seriesLabel,
606
+ groups: data.GroupDataMap.get(_datum.groupLabel)!,
607
+ groupIndex: _datum.groupIndex,
608
+ groupLabel: _datum.groupLabel,
609
+ event,
610
+ data: data.computedData
611
+ })
612
+ })
613
+ .on('mouseout', (event, datum) => {
614
+ event.stopPropagation()
615
+
616
+ const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
617
+ const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
618
+ const groupData = data.GroupDataMap.get(groupLabel)!
619
+ const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
620
+ const _datum = targetDatum ?? datum[0]
621
+
622
+ event$.next({
623
+ type: 'grid',
624
+ eventName: 'mouseout',
625
+ pluginName,
626
+ highlightTarget: data.highlightTarget,
627
+ datum: _datum,
628
+ gridIndex: _datum.gridIndex,
629
+ series: data.SeriesDataMap.get(_datum.seriesLabel)!,
630
+ seriesIndex: _datum.seriesIndex,
631
+ seriesLabel: _datum.seriesLabel,
632
+ groups: data.GroupDataMap.get(_datum.groupLabel)!,
633
+ groupIndex: _datum.groupIndex,
634
+ groupLabel: _datum.groupLabel,
635
+ event,
636
+ data: data.computedData
637
+ })
638
+ })
639
+ .on('click', (event, datum) => {
640
+ event.stopPropagation()
641
+
642
+ const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
643
+ const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
644
+ const groupData = data.GroupDataMap.get(groupLabel)!
645
+ const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
646
+ const _datum = targetDatum ?? datum[0]
647
+
648
+ event$.next({
649
+ type: 'grid',
650
+ eventName: 'click',
651
+ pluginName,
652
+ highlightTarget: data.highlightTarget,
653
+ datum: _datum,
654
+ gridIndex: _datum.gridIndex,
655
+ series: data.SeriesDataMap.get(_datum.seriesLabel)!,
656
+ seriesIndex: _datum.seriesIndex,
657
+ seriesLabel: _datum.seriesLabel,
658
+ groups: data.GroupDataMap.get(_datum.groupLabel)!,
659
+ groupIndex: _datum.groupIndex,
660
+ groupLabel: _datum.groupLabel,
661
+ event,
662
+ data: data.computedData
663
+ })
664
+ })
665
+ })
666
+ })
667
+
668
+ // const datumList$ = computedData$.pipe(
669
+ // takeUntil(destroy$),
670
+ // map(d => d.flat())
671
+ // )
672
+ // const highlight$ = highlightObservable({ datumList$, fullChartParams$, event$: store.event$ })
673
+ // const highlightSubscription = gridHighlight$.subscribe()
674
+
675
+ fullChartParams$.pipe(
676
+ takeUntil(destroy$),
677
+ filter(d => d.highlightTarget === 'series'),
678
+ switchMap(d => combineLatest({
679
+ graphicGSelection: graphicGSelection$,
680
+ gridHighlight: gridHighlight$,
681
+ DataMap: DataMap$,
682
+ fullChartParams: fullChartParams$
683
+ }).pipe(
684
+ takeUntil(destroy$),
685
+ switchMap(async d => d)
686
+ ))
687
+ ).subscribe(data => {
688
+ const seriesLabel = data.gridHighlight[0] ? data.gridHighlight[0].seriesLabel : null
689
+ highlightLines({
690
+ selection: data.graphicGSelection,
691
+ seriesLabel,
692
+ fullChartParams: data.fullChartParams
693
+ })
694
+ })
695
+
696
+ return () => {
697
+ destroy$.next(undefined)
698
+ // highlightSubscription.unsubscribe()
699
+ }
700
700
  }