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

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