@orbcharts/plugins-basic 3.0.0-alpha.58 → 3.0.0-alpha.59

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. package/LICENSE +200 -200
  2. package/package.json +43 -43
  3. package/src/base/BaseBarStack.ts +778 -778
  4. package/src/base/BaseBars.ts +764 -764
  5. package/src/base/BaseBarsTriangle.ts +672 -672
  6. package/src/base/BaseDots.ts +513 -513
  7. package/src/base/BaseGroupAxis.ts +558 -558
  8. package/src/base/BaseLegend.ts +641 -641
  9. package/src/base/BaseLineAreas.ts +628 -628
  10. package/src/base/BaseLines.ts +704 -704
  11. package/src/base/BaseValueAxis.ts +478 -478
  12. package/src/base/types.ts +2 -2
  13. package/src/grid/defaults.ts +128 -128
  14. package/src/grid/gridObservables.ts +541 -541
  15. package/src/grid/index.ts +15 -15
  16. package/src/grid/plugins/BarStack.ts +43 -43
  17. package/src/grid/plugins/Bars.ts +44 -44
  18. package/src/grid/plugins/BarsPN.ts +41 -41
  19. package/src/grid/plugins/BarsTriangle.ts +42 -42
  20. package/src/grid/plugins/Dots.ts +37 -37
  21. package/src/grid/plugins/GridLegend.ts +59 -59
  22. package/src/grid/plugins/GroupAux.ts +976 -976
  23. package/src/grid/plugins/GroupAxis.ts +35 -35
  24. package/src/grid/plugins/LineAreas.ts +40 -40
  25. package/src/grid/plugins/Lines.ts +40 -40
  26. package/src/grid/plugins/ScalingArea.ts +173 -173
  27. package/src/grid/plugins/ValueAxis.ts +36 -36
  28. package/src/grid/plugins/ValueStackAxis.ts +38 -38
  29. package/src/grid/types.ts +123 -123
  30. package/src/index.ts +9 -9
  31. package/src/multiGrid/defaults.ts +158 -158
  32. package/src/multiGrid/index.ts +13 -13
  33. package/src/multiGrid/multiGridObservables.ts +49 -49
  34. package/src/multiGrid/plugins/MultiBarStack.ts +78 -78
  35. package/src/multiGrid/plugins/MultiBars.ts +77 -77
  36. package/src/multiGrid/plugins/MultiBarsTriangle.ts +77 -77
  37. package/src/multiGrid/plugins/MultiDots.ts +65 -65
  38. package/src/multiGrid/plugins/MultiGridLegend.ts +89 -89
  39. package/src/multiGrid/plugins/MultiGroupAxis.ts +69 -69
  40. package/src/multiGrid/plugins/MultiLineAreas.ts +77 -77
  41. package/src/multiGrid/plugins/MultiLines.ts +77 -77
  42. package/src/multiGrid/plugins/MultiValueAxis.ts +69 -69
  43. package/src/multiGrid/plugins/MultiValueStackAxis.ts +69 -69
  44. package/src/multiGrid/plugins/OverlappingValueAxes.ts +167 -167
  45. package/src/multiGrid/plugins/OverlappingValueStackAxes.ts +168 -168
  46. package/src/multiGrid/types.ts +72 -72
  47. package/src/noneData/defaults.ts +102 -102
  48. package/src/noneData/index.ts +3 -3
  49. package/src/noneData/plugins/Container.ts +10 -10
  50. package/src/noneData/plugins/Tooltip.ts +310 -310
  51. package/src/noneData/types.ts +26 -26
  52. package/src/series/defaults.ts +144 -144
  53. package/src/series/index.ts +9 -9
  54. package/src/series/plugins/Bubbles.ts +545 -545
  55. package/src/series/plugins/Pie.ts +576 -576
  56. package/src/series/plugins/PieEventTexts.ts +262 -262
  57. package/src/series/plugins/PieLabels.ts +304 -304
  58. package/src/series/plugins/Rose.ts +472 -472
  59. package/src/series/plugins/RoseLabels.ts +362 -362
  60. package/src/series/plugins/SeriesLegend.ts +59 -59
  61. package/src/series/seriesObservables.ts +145 -145
  62. package/src/series/seriesUtils.ts +51 -51
  63. package/src/series/types.ts +83 -83
  64. package/src/tree/defaults.ts +23 -23
  65. package/src/tree/index.ts +3 -3
  66. package/src/tree/plugins/TreeLegend.ts +59 -59
  67. package/src/tree/plugins/TreeMap.ts +305 -305
  68. package/src/tree/types.ts +23 -23
  69. package/src/utils/commonUtils.ts +21 -21
  70. package/src/utils/d3Graphics.ts +124 -124
  71. package/src/utils/d3Utils.ts +73 -73
  72. package/src/utils/observables.ts +14 -14
  73. package/src/utils/orbchartsUtils.ts +100 -100
  74. package/tsconfig.base.json +13 -13
  75. package/{tsconfig.json.bak.vite-plugin-tsconfig → tsconfig.json} +7 -7
  76. package/tsconfig.prod.json +2 -2
  77. package/vite.config.mjs +40 -40
@@ -1,705 +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
- 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
- }
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
+ }
705
705
  }