@orbcharts/plugins-basic 3.0.0-alpha.56 → 3.0.0-alpha.57

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