@orbcharts/plugins-basic 3.0.7 → 3.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +6837 -6656
  3. package/dist/orbcharts-plugins-basic.umd.js +45 -45
  4. package/dist/src/base/BaseStackedBars.d.ts +2 -0
  5. package/dist/src/series/defaults.d.ts +2 -1
  6. package/dist/src/series/index.d.ts +1 -0
  7. package/dist/src/series/plugins/Indicator.d.ts +3 -0
  8. package/lib/core-types.ts +7 -7
  9. package/lib/core.ts +6 -6
  10. package/lib/gridObservables.ts +6 -6
  11. package/lib/plugins-basic-types.ts +6 -6
  12. package/package.json +48 -48
  13. package/src/base/BaseBars.ts +765 -765
  14. package/src/base/BaseBarsTriangle.ts +676 -676
  15. package/src/base/BaseDots.ts +464 -464
  16. package/src/base/BaseGroupAxis.ts +691 -691
  17. package/src/base/BaseLegend.ts +684 -684
  18. package/src/base/BaseLineAreas.ts +629 -629
  19. package/src/base/BaseLines.ts +706 -706
  20. package/src/base/BaseOrdinalBubbles.ts +729 -729
  21. package/src/base/BaseRacingBars.ts +582 -582
  22. package/src/base/BaseRacingLabels.ts +404 -404
  23. package/src/base/BaseRacingValueLabels.ts +403 -403
  24. package/src/base/BaseStackedBars.ts +793 -782
  25. package/src/base/BaseTooltip.ts +408 -408
  26. package/src/base/BaseValueAxis.ts +600 -600
  27. package/src/base/BaseXAxis.ts +427 -427
  28. package/src/base/BaseXZoom.ts +241 -241
  29. package/src/base/BaseYAxis.ts +389 -389
  30. package/src/base/types.ts +2 -2
  31. package/src/const.ts +30 -30
  32. package/src/grid/defaults.ts +213 -213
  33. package/src/grid/gridObservables.ts +635 -635
  34. package/src/grid/index.ts +16 -16
  35. package/src/grid/plugins/Bars.ts +69 -69
  36. package/src/grid/plugins/BarsPN.ts +66 -66
  37. package/src/grid/plugins/BarsTriangle.ts +73 -73
  38. package/src/grid/plugins/Dots.ts +68 -68
  39. package/src/grid/plugins/GridLegend.ts +107 -107
  40. package/src/grid/plugins/GridTooltip.ts +66 -66
  41. package/src/grid/plugins/GroupAux.ts +1095 -1095
  42. package/src/grid/plugins/GroupAxis.ts +73 -73
  43. package/src/grid/plugins/GroupZoom.ts +218 -218
  44. package/src/grid/plugins/LineAreas.ts +65 -65
  45. package/src/grid/plugins/Lines.ts +59 -59
  46. package/src/grid/plugins/StackedBars.ts +66 -64
  47. package/src/grid/plugins/StackedValueAxis.ts +97 -96
  48. package/src/grid/plugins/ValueAxis.ts +94 -94
  49. package/src/index.ts +6 -6
  50. package/src/multiGrid/defaults.ts +244 -244
  51. package/src/multiGrid/index.ts +14 -14
  52. package/src/multiGrid/multiGridObservables.ts +50 -50
  53. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  54. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  55. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  56. package/src/multiGrid/plugins/MultiGridLegend.ts +169 -169
  57. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  58. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  59. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  60. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  61. package/src/multiGrid/plugins/MultiStackedBars.ts +109 -106
  62. package/src/multiGrid/plugins/MultiStackedValueAxis.ts +135 -134
  63. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  64. package/src/multiGrid/plugins/OverlappingStackedValueAxes.ts +300 -300
  65. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  66. package/src/multiValue/defaults.ts +523 -523
  67. package/src/multiValue/index.ts +16 -16
  68. package/src/multiValue/multiValueObservables.ts +781 -781
  69. package/src/multiValue/plugins/MultiValueLegend.ts +107 -107
  70. package/src/multiValue/plugins/MultiValueTooltip.ts +66 -66
  71. package/src/multiValue/plugins/OrdinalAux.ts +660 -660
  72. package/src/multiValue/plugins/OrdinalAxis.ts +524 -524
  73. package/src/multiValue/plugins/OrdinalBubbles.ts +226 -226
  74. package/src/multiValue/plugins/OrdinalZoom.ts +57 -57
  75. package/src/multiValue/plugins/RacingBars.ts +375 -375
  76. package/src/multiValue/plugins/RacingCounterTexts.ts +300 -300
  77. package/src/multiValue/plugins/RacingValueAxis.ts +114 -114
  78. package/src/multiValue/plugins/Scatter.ts +486 -486
  79. package/src/multiValue/plugins/ScatterBubbles.ts +635 -635
  80. package/src/multiValue/plugins/XAxis.ts +107 -107
  81. package/src/multiValue/plugins/XYAux.ts +683 -683
  82. package/src/multiValue/plugins/XYAxes.ts +194 -194
  83. package/src/multiValue/plugins/XYAxes_legacy.ts +683 -683
  84. package/src/multiValue/plugins/XZoom.ts +40 -40
  85. package/src/noneData/defaults.ts +102 -102
  86. package/src/noneData/index.ts +3 -3
  87. package/src/noneData/plugins/Container.ts +27 -27
  88. package/src/noneData/plugins/Tooltip.ts +373 -373
  89. package/src/relationship/defaults.ts +221 -221
  90. package/src/relationship/index.ts +5 -5
  91. package/src/relationship/plugins/ForceDirected.ts +1056 -1056
  92. package/src/relationship/plugins/ForceDirectedBubbles.ts +1294 -1294
  93. package/src/relationship/plugins/RelationshipLegend.ts +100 -100
  94. package/src/relationship/plugins/RelationshipTooltip.ts +66 -66
  95. package/src/relationship/relationshipObservables.ts +49 -49
  96. package/src/series/defaults.ts +236 -224
  97. package/src/series/index.ts +10 -9
  98. package/src/series/plugins/Bubbles.ts +784 -784
  99. package/src/series/plugins/Indicator.ts +292 -0
  100. package/src/series/plugins/Pie.ts +622 -622
  101. package/src/series/plugins/PieEventTexts.ts +283 -283
  102. package/src/series/plugins/PieLabels.ts +639 -639
  103. package/src/series/plugins/Rose.ts +515 -515
  104. package/src/series/plugins/RoseLabels.ts +599 -599
  105. package/src/series/plugins/SeriesLegend.ts +107 -107
  106. package/src/series/plugins/SeriesTooltip.ts +66 -66
  107. package/src/series/seriesObservables.ts +168 -168
  108. package/src/series/seriesUtils.ts +51 -51
  109. package/src/tree/defaults.ts +102 -102
  110. package/src/tree/index.ts +4 -4
  111. package/src/tree/plugins/TreeLegend.ts +100 -100
  112. package/src/tree/plugins/TreeMap.ts +341 -341
  113. package/src/tree/plugins/TreeTooltip.ts +66 -66
  114. package/src/utils/commonUtils.ts +31 -31
  115. package/src/utils/d3Graphics.ts +176 -176
  116. package/src/utils/d3Utils.ts +92 -92
  117. package/src/utils/observables.ts +14 -14
  118. package/src/utils/orbchartsUtils.ts +129 -129
  119. package/tsconfig.base.json +13 -13
  120. package/tsconfig.json +2 -2
  121. package/vite.config.js +22 -22
@@ -1,636 +1,636 @@
1
- import * as d3 from 'd3'
2
- import {
3
- Observable,
4
- Subject,
5
- of,
6
- takeUntil,
7
- filter,
8
- map,
9
- switchMap,
10
- combineLatest,
11
- merge,
12
- shareReplay,
13
- distinctUntilChanged
14
- } from 'rxjs'
15
- import type {
16
- ChartParams,
17
- HighlightTarget,
18
- DataFormatterGrid,
19
- ComputedDataGrid,
20
- ComputedDatumGrid,
21
- ContainerSize,
22
- TransformData,
23
- ContainerPositionScaled,
24
- Layout } from '../../lib/core-types'
25
- import { createAxisToLabelIndexScale } from '../../lib/core'
26
- import { getClassName, getUniID } from '../utils/orbchartsUtils'
27
- import { d3EventObservable } from '../utils/observables'
28
-
29
- // 建立 grid 主要的 selection
30
- export const gridSelectionsObservable = ({ selection, pluginName, clipPathID, seriesLabels$, gridContainerPosition$, gridAxesTransform$, gridGraphicTransform$ }: {
31
- selection: d3.Selection<any, unknown, any, unknown>
32
- pluginName: string
33
- clipPathID: string
34
- // computedData$: Observable<ComputedDataGrid>
35
- seriesLabels$: Observable<string[]>
36
- gridContainerPosition$: Observable<ContainerPositionScaled[]>
37
- gridAxesTransform$: Observable<TransformData>
38
- gridGraphicTransform$: Observable<TransformData>
39
- }) => {
40
- const seriesClassName = getClassName(pluginName, 'series')
41
- const axesClassName = getClassName(pluginName, 'axes')
42
- const graphicClassName = getClassName(pluginName, 'graphic')
43
-
44
- // <g> series selection(container排放位置)
45
- // <g> axes selection(旋轉圖軸方向)
46
- // <defs> clipPath selection
47
- // <g> graphic selection(圖形 scale 範圍的變形)
48
- const seriesSelection$ = seriesLabels$.pipe(
49
- map((seriesLabels, i) => {
50
- return selection
51
- .selectAll<SVGGElement, string>(`g.${seriesClassName}`)
52
- .data(seriesLabels, d => d)
53
- .join(
54
- enter => {
55
- return enter
56
- .append('g')
57
- .classed(seriesClassName, true)
58
- .each((d, i, g) => {
59
- const axesSelection = d3.select(g[i])
60
- .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${axesClassName}`)
61
- .data([i])
62
- .join(
63
- enter => {
64
- return enter
65
- .append('g')
66
- .classed(axesClassName, true)
67
- .attr('clip-path', `url(#${clipPathID})`)
68
- .each((d, i, g) => {
69
- const defsSelection = d3.select(g[i])
70
- .selectAll<SVGDefsElement, any>('defs')
71
- .data([i])
72
- .join('defs')
73
-
74
- const graphicGSelection = d3.select(g[i])
75
- .selectAll<SVGGElement, any>('g')
76
- .data([i])
77
- .join('g')
78
- .classed(graphicClassName, true)
79
- })
80
- },
81
- update => update,
82
- exit => exit.remove()
83
- )
84
- })
85
- },
86
- update => update,
87
- exit => exit.remove()
88
- )
89
- }),
90
- shareReplay(1)
91
- )
92
-
93
- // <g> series selection
94
- combineLatest({
95
- seriesSelection: seriesSelection$,
96
- gridContainerPosition: gridContainerPosition$
97
- }).pipe(
98
- switchMap(async d => d)
99
- ).subscribe(data => {
100
- data.seriesSelection
101
- .transition()
102
- .attr('transform', (d, i) => {
103
- const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
104
- const translate = gridContainerPosition.translate
105
- const scale = gridContainerPosition.scale
106
- return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
107
- })
108
- })
109
-
110
- // <g> axes selection
111
- const axesSelection$ = combineLatest({
112
- seriesSelection: seriesSelection$,
113
- gridAxesTransform: gridAxesTransform$
114
- }).pipe(
115
- switchMap(async d => d),
116
- map(data => {
117
- return data.seriesSelection
118
- .select<SVGGElement>(`g.${axesClassName}`)
119
- .style('transform', data.gridAxesTransform.value)
120
- }),
121
- shareReplay(1)
122
- )
123
-
124
- // <defs> clipPath selection
125
- const defsSelection$ = axesSelection$.pipe(
126
- map(axesSelection => {
127
- return axesSelection.select<SVGDefsElement>('defs')
128
- }),
129
- shareReplay(1)
130
- )
131
-
132
- // <g> graphic selection
133
- const graphicGSelection$ = combineLatest({
134
- axesSelection: axesSelection$,
135
- gridGraphicTransform: gridGraphicTransform$
136
- }).pipe(
137
- switchMap(async d => d),
138
- map(data => {
139
- const graphicGSelection = data.axesSelection
140
- .select<SVGGElement>(`g.${graphicClassName}`)
141
- graphicGSelection
142
- .transition()
143
- .duration(50)
144
- .style('transform', data.gridGraphicTransform.value)
145
- return graphicGSelection
146
- }),
147
- shareReplay(1)
148
- )
149
-
150
- return {
151
- seriesSelection$,
152
- axesSelection$,
153
- defsSelection$,
154
- graphicGSelection$
155
- }
156
- }
157
-
158
- // 建立 grid 主要的 selection - 只取的container
159
- export const gridContainerSelectionsObservable = ({ selection, pluginName, computedData$, gridContainerPosition$, isSeriesSeprate$ }: {
160
- selection: d3.Selection<any, unknown, any, unknown>
161
- pluginName: string
162
- computedData$: Observable<ComputedDataGrid>
163
- gridContainerPosition$: Observable<ContainerPositionScaled[]>
164
- isSeriesSeprate$: Observable<boolean>
165
- }) => {
166
- const containerClassName = getClassName(pluginName, 'container')
167
-
168
- const containerSelection$ = combineLatest({
169
- computedData: computedData$.pipe(
170
- distinctUntilChanged((a, b) => {
171
- // 只有當series的數量改變時,才重新計算
172
- return a.length === b.length
173
- }),
174
- ),
175
- isSeriesSeprate: isSeriesSeprate$
176
- }).pipe(
177
- switchMap(async (d) => d),
178
- map(data => {
179
- return data.isSeriesSeprate
180
- // series分開的時候顯示各別axis
181
- ? data.computedData
182
- // series合併的時候只顯示第一個axis
183
- : [data.computedData[0]]
184
- }),
185
- map((computedData, i) => {
186
- return selection
187
- .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${containerClassName}`)
188
- .data(computedData, d => (d && d[0]) ? d[0].seriesIndex : i)
189
- .join('g')
190
- .classed(containerClassName, true)
191
- }),
192
- shareReplay(1)
193
- )
194
-
195
- combineLatest({
196
- containerSelection: containerSelection$,
197
- gridContainerPosition: gridContainerPosition$
198
- }).pipe(
199
- switchMap(async d => d)
200
- ).subscribe(data => {
201
- data.containerSelection
202
- .attr('transform', (d, i) => {
203
- const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
204
- const translate = gridContainerPosition.translate
205
- const scale = gridContainerPosition.scale
206
- return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
207
- })
208
- // .attr('opacity', 0)
209
- // .transition()
210
- // .attr('opacity', 1)
211
- })
212
-
213
- return containerSelection$
214
- }
215
-
216
- // 由事件取得group data的function
217
- export const gridGroupPositionFnObservable = ({ fullDataFormatter$, gridAxesSize$, computedData$, fullChartParams$, gridContainerPosition$, layout$ }: {
218
- fullDataFormatter$: Observable<DataFormatterGrid>
219
- gridAxesSize$: Observable<{
220
- width: number;
221
- height: number;
222
- }>
223
- computedData$: Observable<ComputedDataGrid>
224
- // GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
225
- fullChartParams$: Observable<ChartParams>
226
- gridContainerPosition$: Observable<ContainerPositionScaled[]>
227
- layout$: Observable<Layout>
228
- }): Observable<(event: any) => { groupIndex: number; groupLabel: string }> => {
229
- const destroy$ = new Subject()
230
-
231
- // 顯示範圍內的group labels
232
- // const scaleRangeGroupLabels$: Observable<string[]> = new Observable(subscriber => {
233
- // combineLatest({
234
- // dataFormatter: fullDataFormatter$,
235
- // computedData: computedData$
236
- // }).pipe(
237
- // takeUntil(destroy$),
238
- // switchMap(async (d) => d),
239
- // ).subscribe(data => {
240
- // const groupMin = 0
241
- // const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
242
- // const groupScaleDomainMin = data.dataFormatter.groupAxis.scaleDomain[0] === 'auto'
243
- // ? groupMin - data.dataFormatter.groupAxis.scalePadding
244
- // : data.dataFormatter.groupAxis.scaleDomain[0] as number - data.dataFormatter.groupAxis.scalePadding
245
- // const groupScaleDomainMax = data.dataFormatter.groupAxis.scaleDomain[1] === 'auto'
246
- // ? groupMax + data.dataFormatter.groupAxis.scalePadding
247
- // : data.dataFormatter.groupAxis.scaleDomain[1] as number + data.dataFormatter.groupAxis.scalePadding
248
-
249
- // // const groupingAmount = data.computedData[0]
250
- // // ? data.computedData[0].length
251
- // // : 0
252
-
253
- // let _labels = data.dataFormatter.seriesDirection === 'row'
254
- // ? (data.computedData[0] ?? []).map(d => d.groupLabel)
255
- // : data.computedData.map(d => d[0].groupLabel)
256
-
257
- // const _axisLabels =
258
- // // new Array(groupingAmount).fill(0)
259
- // // .map((d, i) => {
260
- // // return _labels[i] != null
261
- // // ? _labels[i]
262
- // // : String(i) // 沒有label則用序列號填充
263
- // // })
264
- // _labels
265
- // .filter((d, i) => {
266
- // return i >= groupScaleDomainMin && i <= groupScaleDomainMax
267
- // })
268
- // subscriber.next(_axisLabels)
269
- // })
270
- // })
271
- const groupScaleDomain$ = combineLatest({
272
- fullDataFormatter: fullDataFormatter$,
273
- gridAxesSize: gridAxesSize$,
274
- computedData: computedData$
275
- }).pipe(
276
- switchMap(async (d) => d),
277
- map(data => {
278
- const groupMin = 0
279
- const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
280
- // const groupScaleDomainMin = data.fullDataFormatter.groupAxis.scaleDomain[0] === 'auto'
281
- // ? groupMin - data.fullDataFormatter.groupAxis.scalePadding
282
- // : data.fullDataFormatter.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.groupAxis.scalePadding
283
- const groupScaleDomainMin = data.fullDataFormatter.groupAxis.scaleDomain[0] - data.fullDataFormatter.groupAxis.scalePadding
284
- const groupScaleDomainMax = data.fullDataFormatter.groupAxis.scaleDomain[1] === 'max'
285
- ? groupMax + data.fullDataFormatter.groupAxis.scalePadding
286
- : data.fullDataFormatter.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.groupAxis.scalePadding
287
-
288
- return [groupScaleDomainMin, groupScaleDomainMax]
289
- }),
290
- shareReplay(1)
291
- )
292
-
293
- const groupLabels$ = combineLatest({
294
- fullDataFormatter: fullDataFormatter$,
295
- computedData: computedData$
296
- }).pipe(
297
- switchMap(async d => d),
298
- map(data => {
299
- return data.fullDataFormatter.seriesDirection === 'row'
300
- ? (data.computedData[0] ?? []).map(d => d.groupLabel)
301
- : data.computedData.map(d => d[0].groupLabel)
302
- })
303
- )
304
-
305
- // 顯示範圍內的group labels
306
- const scaleRangeGroupLabels$ = combineLatest({
307
- groupScaleDomain: groupScaleDomain$,
308
- groupLabels: groupLabels$
309
- }).pipe(
310
- switchMap(async d => d),
311
- map(data => {
312
- return data.groupLabels
313
- .filter((d, i) => {
314
- return i >= data.groupScaleDomain[0] && i <= data.groupScaleDomain[1]
315
- })
316
- })
317
- )
318
-
319
- const columnAmount$ = gridContainerPosition$.pipe(
320
- map(gridContainerPosition => {
321
- const maxColumnIndex = gridContainerPosition.reduce((acc, current) => {
322
- return current.columnIndex > acc ? current.columnIndex : acc
323
- }, 0)
324
- return maxColumnIndex + 1
325
- }),
326
- distinctUntilChanged()
327
- )
328
-
329
- const rowAmount$ = gridContainerPosition$.pipe(
330
- map(gridContainerPosition => {
331
- const maxRowIndex = gridContainerPosition.reduce((acc, current) => {
332
- return current.rowIndex > acc ? current.rowIndex : acc
333
- }, 0)
334
- return maxRowIndex + 1
335
- }),
336
- distinctUntilChanged()
337
- )
338
-
339
- return new Observable<(event: any) => { groupIndex: number; groupLabel: string }>(subscriber => {
340
- combineLatest({
341
- dataFormatter: fullDataFormatter$,
342
- axisSize: gridAxesSize$,
343
- fullChartParams: fullChartParams$,
344
- scaleRangeGroupLabels: scaleRangeGroupLabels$,
345
- groupLabels: groupLabels$,
346
- groupScaleDomain: groupScaleDomain$,
347
- columnAmount: columnAmount$,
348
- rowAmount: rowAmount$,
349
- layout: layout$
350
- }).pipe(
351
- takeUntil(destroy$),
352
- switchMap(async (d) => d),
353
- ).subscribe(data => {
354
-
355
- const reverse = data.dataFormatter.valueAxis.position === 'right'
356
- || data.dataFormatter.valueAxis.position === 'bottom'
357
- ? true : false
358
-
359
- // 比例尺座標對應非連續資料索引
360
- const xIndexScale = createAxisToLabelIndexScale({
361
- axisLabels: data.scaleRangeGroupLabels,
362
- axisWidth: data.axisSize.width,
363
- padding: data.dataFormatter.groupAxis.scalePadding,
364
- reverse
365
- })
366
-
367
- // 依比例尺位置計算座標
368
- const axisValuePredicate = (event: any) => {
369
- return data.dataFormatter.groupAxis.position === 'bottom'
370
- || data.dataFormatter.groupAxis.position === 'top'
371
- ? event.offsetX - data.fullChartParams.padding.left
372
- : event.offsetY - data.fullChartParams.padding.top
373
- }
374
-
375
- // 比例尺座標取得groupData的function
376
- const createEventGroupData: (event: MouseEvent) => { groupIndex: number; groupLabel: string } = (event: any) => {
377
- // 由於event座標是基於底層的,但是container會有多欄,所以要重新計算
378
- const eventData = {
379
- offsetX: event.offsetX * data.columnAmount % data.layout.rootWidth,
380
- offsetY: event.offsetY * data.rowAmount % data.layout.rootHeight
381
- }
382
- // console.log('data.columnAmount', data.columnAmount, 'data.rowAmount', data.rowAmount, 'data.layout.rootWidth', data.layout.rootWidth, 'data.layout.rootHeight', data.layout.rootHeight)
383
- const axisValue = axisValuePredicate(eventData)
384
- const xIndex = xIndexScale(axisValue)
385
- const currentxIndexStart = Math.ceil(data.groupScaleDomain[0]) // 因為有padding所以會有小數點,所以要無條件進位
386
- const groupIndex = xIndex + currentxIndexStart
387
-
388
- return {
389
- groupIndex,
390
- groupLabel: data.groupLabels[groupIndex] ?? ''
391
- }
392
- }
393
-
394
- subscriber.next(createEventGroupData)
395
-
396
- return function unsubscribe () {
397
- destroy$.next(undefined)
398
- }
399
- })
400
- })
401
- }
402
-
403
- export const gridGroupPositionObservable = ({ rootSelection, fullDataFormatter$, containerSize$, gridAxesContainerSize$, computedData$, gridContainerPosition$, layout$ }: {
404
- rootSelection: d3.Selection<any, unknown, any, unknown>
405
- fullDataFormatter$: Observable<DataFormatterGrid>
406
- // gridAxesSize$: Observable<ContainerSize>
407
- containerSize$: Observable<ContainerSize>
408
- gridAxesContainerSize$: Observable<ContainerSize>
409
- computedData$: Observable<ComputedDataGrid>
410
- gridContainerPosition$: Observable<ContainerPositionScaled[]>
411
- layout$: Observable<Layout>
412
- }) => {
413
- const rootMousemove$ = d3EventObservable(rootSelection, 'mousemove')
414
-
415
- const groupScaleDomain$ = combineLatest({
416
- fullDataFormatter: fullDataFormatter$,
417
- // gridAxesSize: gridAxesSize$,
418
- computedData: computedData$
419
- }).pipe(
420
- switchMap(async (d) => d),
421
- map(data => {
422
- const groupMin = 0
423
- const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
424
- // const groupScaleDomainMin = data.fullDataFormatter.groupAxis.scaleDomain[0] === 'auto'
425
- // ? groupMin - data.fullDataFormatter.groupAxis.scalePadding
426
- // : data.fullDataFormatter.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.groupAxis.scalePadding
427
- const groupScaleDomainMin = data.fullDataFormatter.groupAxis.scaleDomain[0] - data.fullDataFormatter.groupAxis.scalePadding
428
- const groupScaleDomainMax = data.fullDataFormatter.groupAxis.scaleDomain[1] === 'max'
429
- ? groupMax + data.fullDataFormatter.groupAxis.scalePadding
430
- : data.fullDataFormatter.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.groupAxis.scalePadding
431
-
432
- return [groupScaleDomainMin, groupScaleDomainMax]
433
- }),
434
- shareReplay(1)
435
- )
436
-
437
- const groupLabels$ = combineLatest({
438
- fullDataFormatter: fullDataFormatter$,
439
- computedData: computedData$
440
- }).pipe(
441
- switchMap(async d => d),
442
- map(data => {
443
- return data.fullDataFormatter.seriesDirection === 'row'
444
- ? (data.computedData[0] ?? []).map(d => d.groupLabel)
445
- : data.computedData.map(d => d[0].groupLabel)
446
- })
447
- )
448
-
449
- const scaleRangeGroupLabels$ = combineLatest({
450
- groupScaleDomain: groupScaleDomain$,
451
- groupLabels: groupLabels$
452
- }).pipe(
453
- switchMap(async d => d),
454
- map(data => {
455
- return data.groupLabels
456
- .filter((d, i) => {
457
- return i >= data.groupScaleDomain[0] && i <= data.groupScaleDomain[1]
458
- })
459
- })
460
- )
461
-
462
- const reverse$ = fullDataFormatter$.pipe(
463
- map(d => {
464
- return d.valueAxis.position === 'right' || d.valueAxis.position === 'bottom'
465
- ? true
466
- : false
467
- })
468
- )
469
-
470
- // 比例尺座標對應非連續資料索引
471
- const xIndexScale$ = combineLatest({
472
- reverse: reverse$,
473
- // gridAxesSize: gridAxesSize$,
474
- gridAxesContainerSize: gridAxesContainerSize$,
475
- scaleRangeGroupLabels: scaleRangeGroupLabels$,
476
- fullDataFormatter: fullDataFormatter$
477
- }).pipe(
478
- switchMap(async d => d),
479
- map(data => {
480
- return createAxisToLabelIndexScale({
481
- axisLabels: data.scaleRangeGroupLabels,
482
- axisWidth: data.gridAxesContainerSize.width,
483
- padding: data.fullDataFormatter.groupAxis.scalePadding,
484
- reverse: data.reverse
485
- })
486
- })
487
- )
488
-
489
- // const columnAmount$ = gridContainerPosition$.pipe(
490
- // map(gridContainerPosition => {
491
- // const maxColumnIndex = gridContainerPosition.reduce((acc, current) => {
492
- // return current.columnIndex > acc ? current.columnIndex : acc
493
- // }, 0)
494
- // return maxColumnIndex + 1
495
- // }),
496
- // distinctUntilChanged()
497
- // )
498
-
499
- // const rowAmount$ = gridContainerPosition$.pipe(
500
- // map(gridContainerPosition => {
501
- // const maxRowIndex = gridContainerPosition.reduce((acc, current) => {
502
- // return current.rowIndex > acc ? current.rowIndex : acc
503
- // }, 0)
504
- // return maxRowIndex + 1
505
- // }),
506
- // distinctUntilChanged()
507
- // )
508
-
509
- const axisValue$ = combineLatest({
510
- fullDataFormatter: fullDataFormatter$,
511
- rootMousemove: rootMousemove$,
512
- // containerSize: containerSize$,
513
- gridContainerPosition: gridContainerPosition$,
514
- // columnAmount: columnAmount$,
515
- // rowAmount: rowAmount$,
516
- layout: layout$
517
- }).pipe(
518
- switchMap(async d => d),
519
- map(data => {
520
- // // 由於event座標是基於底層的,但是container會有多欄,所以要重新計算
521
- // const eventData = {
522
- // offsetX: data.rootMousemove.offsetX * data.columnAmount % data.layout.rootWidth,
523
- // offsetY: data.rootMousemove.offsetY * data.rowAmount % data.layout.rootHeight
524
- // }
525
- // return data.fullDataFormatter.groupAxis.position === 'bottom'
526
- // || data.fullDataFormatter.groupAxis.position === 'top'
527
- // ? eventData.offsetX - data.layout.left
528
- // : eventData.offsetY - data.layout.top
529
-
530
- if (data.fullDataFormatter.groupAxis.position === 'bottom' || data.fullDataFormatter.groupAxis.position === 'top') {
531
- let x = data.rootMousemove.offsetX
532
- const rangeArr = data.gridContainerPosition
533
- .map((d, i) => [d.translate[0], data.gridContainerPosition[i + 1]?.translate[0] ?? data.layout.rootWidth])
534
- .filter(d => d[0] < d[1])
535
- const range = rangeArr.find(d => x >= d[0] && x <= d[1])
536
- if (range) {
537
- x = x - range[0]
538
- }
539
- return x - data.layout.left
540
- } else {
541
- let y = data.rootMousemove.offsetY
542
- const rangeArr = data.gridContainerPosition
543
- .map((d, i) => [d.translate[1], data.gridContainerPosition[i + 1]?.translate[1] ?? data.layout.rootHeight])
544
- .filter(d => d[0] < d[1])
545
- const range = rangeArr.find(d => y >= d[0] && y <= d[1])
546
- if (range) {
547
- y = y - range[0]
548
- }
549
- return y - data.layout.top
550
- }
551
- })
552
- )
553
-
554
- const groupIndex$ = combineLatest({
555
- xIndexScale: xIndexScale$,
556
- axisValue: axisValue$,
557
- groupScaleDomain: groupScaleDomain$
558
- }).pipe(
559
- switchMap(async d => d),
560
- map(data => {
561
- const xIndex = data.xIndexScale(data.axisValue)
562
- const currentxIndexStart = Math.ceil(data.groupScaleDomain[0]) // 因為有padding所以會有小數點,所以要無條件進位
563
- return xIndex + currentxIndexStart
564
- })
565
- )
566
-
567
- const groupLabel$ = combineLatest({
568
- groupIndex: groupIndex$,
569
- groupLabels: groupLabels$
570
- }).pipe(
571
- switchMap(async d => d),
572
- map(data => {
573
- return data.groupLabels[data.groupIndex] ?? ''
574
- })
575
- )
576
-
577
- return combineLatest({
578
- groupIndex: groupIndex$,
579
- groupLabel: groupLabel$
580
- }).pipe(
581
- switchMap(async d => d),
582
- map(data => {
583
- return {
584
- groupIndex: data.groupIndex,
585
- groupLabel: data.groupLabel
586
- }
587
- })
588
- )
589
- }
590
-
591
- // const gridContainerEventData$ = ({ eventData$, gridContainerPosition$, layout$ }: {
592
- // eventData$: Observable<any>
593
- // gridContainerPosition$: Observable<ContainerPositionScaled[]>
594
- // layout$: Observable<Layout>
595
- // }): Observable<{
596
- // offsetX: number;
597
- // offsetY: number;
598
- // }> => {
599
- // const columnAmount$ = gridContainerPosition$.pipe(
600
- // map(gridContainerPosition => {
601
- // const maxColumnIndex = gridContainerPosition.reduce((acc, current) => {
602
- // return current.columnIndex > acc ? current.columnIndex : acc
603
- // }, 0)
604
- // return maxColumnIndex + 1
605
- // }),
606
- // distinctUntilChanged()
607
- // )
608
-
609
- // const rowAmount$ = gridContainerPosition$.pipe(
610
- // map(gridContainerPosition => {
611
- // const maxRowIndex = gridContainerPosition.reduce((acc, current) => {
612
- // return current.rowIndex > acc ? current.rowIndex : acc
613
- // }, 0)
614
- // return maxRowIndex + 1
615
- // }),
616
- // distinctUntilChanged()
617
- // )
618
-
619
- // return combineLatest({
620
- // eventData: eventData$,
621
- // gridContainerPosition: gridContainerPosition$,
622
- // layout: layout$,
623
- // columnAmount: columnAmount$,
624
- // rowAmount: rowAmount$
625
- // }).pipe(
626
- // switchMap(async d => d),
627
- // map(data => {
628
- // // 由於event座標是基於底層的,但是container會有多欄,所以要重新計算
629
- // const eventData = {
630
- // offsetX: data.eventData.offsetX * data.columnAmount % data.layout.rootWidth,
631
- // offsetY: data.eventData.offsetY * data.rowAmount % data.layout.rootHeight
632
- // }
633
- // return eventData
634
- // })
635
- // )
1
+ import * as d3 from 'd3'
2
+ import {
3
+ Observable,
4
+ Subject,
5
+ of,
6
+ takeUntil,
7
+ filter,
8
+ map,
9
+ switchMap,
10
+ combineLatest,
11
+ merge,
12
+ shareReplay,
13
+ distinctUntilChanged
14
+ } from 'rxjs'
15
+ import type {
16
+ ChartParams,
17
+ HighlightTarget,
18
+ DataFormatterGrid,
19
+ ComputedDataGrid,
20
+ ComputedDatumGrid,
21
+ ContainerSize,
22
+ TransformData,
23
+ ContainerPositionScaled,
24
+ Layout } from '../../lib/core-types'
25
+ import { createAxisToLabelIndexScale } from '../../lib/core'
26
+ import { getClassName, getUniID } from '../utils/orbchartsUtils'
27
+ import { d3EventObservable } from '../utils/observables'
28
+
29
+ // 建立 grid 主要的 selection
30
+ export const gridSelectionsObservable = ({ selection, pluginName, clipPathID, seriesLabels$, gridContainerPosition$, gridAxesTransform$, gridGraphicTransform$ }: {
31
+ selection: d3.Selection<any, unknown, any, unknown>
32
+ pluginName: string
33
+ clipPathID: string
34
+ // computedData$: Observable<ComputedDataGrid>
35
+ seriesLabels$: Observable<string[]>
36
+ gridContainerPosition$: Observable<ContainerPositionScaled[]>
37
+ gridAxesTransform$: Observable<TransformData>
38
+ gridGraphicTransform$: Observable<TransformData>
39
+ }) => {
40
+ const seriesClassName = getClassName(pluginName, 'series')
41
+ const axesClassName = getClassName(pluginName, 'axes')
42
+ const graphicClassName = getClassName(pluginName, 'graphic')
43
+
44
+ // <g> series selection(container排放位置)
45
+ // <g> axes selection(旋轉圖軸方向)
46
+ // <defs> clipPath selection
47
+ // <g> graphic selection(圖形 scale 範圍的變形)
48
+ const seriesSelection$ = seriesLabels$.pipe(
49
+ map((seriesLabels, i) => {
50
+ return selection
51
+ .selectAll<SVGGElement, string>(`g.${seriesClassName}`)
52
+ .data(seriesLabels, d => d)
53
+ .join(
54
+ enter => {
55
+ return enter
56
+ .append('g')
57
+ .classed(seriesClassName, true)
58
+ .each((d, i, g) => {
59
+ const axesSelection = d3.select(g[i])
60
+ .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${axesClassName}`)
61
+ .data([i])
62
+ .join(
63
+ enter => {
64
+ return enter
65
+ .append('g')
66
+ .classed(axesClassName, true)
67
+ .attr('clip-path', `url(#${clipPathID})`)
68
+ .each((d, i, g) => {
69
+ const defsSelection = d3.select(g[i])
70
+ .selectAll<SVGDefsElement, any>('defs')
71
+ .data([i])
72
+ .join('defs')
73
+
74
+ const graphicGSelection = d3.select(g[i])
75
+ .selectAll<SVGGElement, any>('g')
76
+ .data([i])
77
+ .join('g')
78
+ .classed(graphicClassName, true)
79
+ })
80
+ },
81
+ update => update,
82
+ exit => exit.remove()
83
+ )
84
+ })
85
+ },
86
+ update => update,
87
+ exit => exit.remove()
88
+ )
89
+ }),
90
+ shareReplay(1)
91
+ )
92
+
93
+ // <g> series selection
94
+ combineLatest({
95
+ seriesSelection: seriesSelection$,
96
+ gridContainerPosition: gridContainerPosition$
97
+ }).pipe(
98
+ switchMap(async d => d)
99
+ ).subscribe(data => {
100
+ data.seriesSelection
101
+ .transition()
102
+ .attr('transform', (d, i) => {
103
+ const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
104
+ const translate = gridContainerPosition.translate
105
+ const scale = gridContainerPosition.scale
106
+ return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
107
+ })
108
+ })
109
+
110
+ // <g> axes selection
111
+ const axesSelection$ = combineLatest({
112
+ seriesSelection: seriesSelection$,
113
+ gridAxesTransform: gridAxesTransform$
114
+ }).pipe(
115
+ switchMap(async d => d),
116
+ map(data => {
117
+ return data.seriesSelection
118
+ .select<SVGGElement>(`g.${axesClassName}`)
119
+ .style('transform', data.gridAxesTransform.value)
120
+ }),
121
+ shareReplay(1)
122
+ )
123
+
124
+ // <defs> clipPath selection
125
+ const defsSelection$ = axesSelection$.pipe(
126
+ map(axesSelection => {
127
+ return axesSelection.select<SVGDefsElement>('defs')
128
+ }),
129
+ shareReplay(1)
130
+ )
131
+
132
+ // <g> graphic selection
133
+ const graphicGSelection$ = combineLatest({
134
+ axesSelection: axesSelection$,
135
+ gridGraphicTransform: gridGraphicTransform$
136
+ }).pipe(
137
+ switchMap(async d => d),
138
+ map(data => {
139
+ const graphicGSelection = data.axesSelection
140
+ .select<SVGGElement>(`g.${graphicClassName}`)
141
+ graphicGSelection
142
+ .transition()
143
+ .duration(50)
144
+ .style('transform', data.gridGraphicTransform.value)
145
+ return graphicGSelection
146
+ }),
147
+ shareReplay(1)
148
+ )
149
+
150
+ return {
151
+ seriesSelection$,
152
+ axesSelection$,
153
+ defsSelection$,
154
+ graphicGSelection$
155
+ }
156
+ }
157
+
158
+ // 建立 grid 主要的 selection - 只取的container
159
+ export const gridContainerSelectionsObservable = ({ selection, pluginName, computedData$, gridContainerPosition$, isSeriesSeprate$ }: {
160
+ selection: d3.Selection<any, unknown, any, unknown>
161
+ pluginName: string
162
+ computedData$: Observable<ComputedDataGrid>
163
+ gridContainerPosition$: Observable<ContainerPositionScaled[]>
164
+ isSeriesSeprate$: Observable<boolean>
165
+ }) => {
166
+ const containerClassName = getClassName(pluginName, 'container')
167
+
168
+ const containerSelection$ = combineLatest({
169
+ computedData: computedData$.pipe(
170
+ distinctUntilChanged((a, b) => {
171
+ // 只有當series的數量改變時,才重新計算
172
+ return a.length === b.length
173
+ }),
174
+ ),
175
+ isSeriesSeprate: isSeriesSeprate$
176
+ }).pipe(
177
+ switchMap(async (d) => d),
178
+ map(data => {
179
+ return data.isSeriesSeprate
180
+ // series分開的時候顯示各別axis
181
+ ? data.computedData
182
+ // series合併的時候只顯示第一個axis
183
+ : [data.computedData[0]]
184
+ }),
185
+ map((computedData, i) => {
186
+ return selection
187
+ .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${containerClassName}`)
188
+ .data(computedData, d => (d && d[0]) ? d[0].seriesIndex : i)
189
+ .join('g')
190
+ .classed(containerClassName, true)
191
+ }),
192
+ shareReplay(1)
193
+ )
194
+
195
+ combineLatest({
196
+ containerSelection: containerSelection$,
197
+ gridContainerPosition: gridContainerPosition$
198
+ }).pipe(
199
+ switchMap(async d => d)
200
+ ).subscribe(data => {
201
+ data.containerSelection
202
+ .attr('transform', (d, i) => {
203
+ const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
204
+ const translate = gridContainerPosition.translate
205
+ const scale = gridContainerPosition.scale
206
+ return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
207
+ })
208
+ // .attr('opacity', 0)
209
+ // .transition()
210
+ // .attr('opacity', 1)
211
+ })
212
+
213
+ return containerSelection$
214
+ }
215
+
216
+ // 由事件取得group data的function
217
+ export const gridGroupPositionFnObservable = ({ fullDataFormatter$, gridAxesSize$, computedData$, fullChartParams$, gridContainerPosition$, layout$ }: {
218
+ fullDataFormatter$: Observable<DataFormatterGrid>
219
+ gridAxesSize$: Observable<{
220
+ width: number;
221
+ height: number;
222
+ }>
223
+ computedData$: Observable<ComputedDataGrid>
224
+ // GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
225
+ fullChartParams$: Observable<ChartParams>
226
+ gridContainerPosition$: Observable<ContainerPositionScaled[]>
227
+ layout$: Observable<Layout>
228
+ }): Observable<(event: any) => { groupIndex: number; groupLabel: string }> => {
229
+ const destroy$ = new Subject()
230
+
231
+ // 顯示範圍內的group labels
232
+ // const scaleRangeGroupLabels$: Observable<string[]> = new Observable(subscriber => {
233
+ // combineLatest({
234
+ // dataFormatter: fullDataFormatter$,
235
+ // computedData: computedData$
236
+ // }).pipe(
237
+ // takeUntil(destroy$),
238
+ // switchMap(async (d) => d),
239
+ // ).subscribe(data => {
240
+ // const groupMin = 0
241
+ // const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
242
+ // const groupScaleDomainMin = data.dataFormatter.groupAxis.scaleDomain[0] === 'auto'
243
+ // ? groupMin - data.dataFormatter.groupAxis.scalePadding
244
+ // : data.dataFormatter.groupAxis.scaleDomain[0] as number - data.dataFormatter.groupAxis.scalePadding
245
+ // const groupScaleDomainMax = data.dataFormatter.groupAxis.scaleDomain[1] === 'auto'
246
+ // ? groupMax + data.dataFormatter.groupAxis.scalePadding
247
+ // : data.dataFormatter.groupAxis.scaleDomain[1] as number + data.dataFormatter.groupAxis.scalePadding
248
+
249
+ // // const groupingAmount = data.computedData[0]
250
+ // // ? data.computedData[0].length
251
+ // // : 0
252
+
253
+ // let _labels = data.dataFormatter.seriesDirection === 'row'
254
+ // ? (data.computedData[0] ?? []).map(d => d.groupLabel)
255
+ // : data.computedData.map(d => d[0].groupLabel)
256
+
257
+ // const _axisLabels =
258
+ // // new Array(groupingAmount).fill(0)
259
+ // // .map((d, i) => {
260
+ // // return _labels[i] != null
261
+ // // ? _labels[i]
262
+ // // : String(i) // 沒有label則用序列號填充
263
+ // // })
264
+ // _labels
265
+ // .filter((d, i) => {
266
+ // return i >= groupScaleDomainMin && i <= groupScaleDomainMax
267
+ // })
268
+ // subscriber.next(_axisLabels)
269
+ // })
270
+ // })
271
+ const groupScaleDomain$ = combineLatest({
272
+ fullDataFormatter: fullDataFormatter$,
273
+ gridAxesSize: gridAxesSize$,
274
+ computedData: computedData$
275
+ }).pipe(
276
+ switchMap(async (d) => d),
277
+ map(data => {
278
+ const groupMin = 0
279
+ const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
280
+ // const groupScaleDomainMin = data.fullDataFormatter.groupAxis.scaleDomain[0] === 'auto'
281
+ // ? groupMin - data.fullDataFormatter.groupAxis.scalePadding
282
+ // : data.fullDataFormatter.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.groupAxis.scalePadding
283
+ const groupScaleDomainMin = data.fullDataFormatter.groupAxis.scaleDomain[0] - data.fullDataFormatter.groupAxis.scalePadding
284
+ const groupScaleDomainMax = data.fullDataFormatter.groupAxis.scaleDomain[1] === 'max'
285
+ ? groupMax + data.fullDataFormatter.groupAxis.scalePadding
286
+ : data.fullDataFormatter.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.groupAxis.scalePadding
287
+
288
+ return [groupScaleDomainMin, groupScaleDomainMax]
289
+ }),
290
+ shareReplay(1)
291
+ )
292
+
293
+ const groupLabels$ = combineLatest({
294
+ fullDataFormatter: fullDataFormatter$,
295
+ computedData: computedData$
296
+ }).pipe(
297
+ switchMap(async d => d),
298
+ map(data => {
299
+ return data.fullDataFormatter.seriesDirection === 'row'
300
+ ? (data.computedData[0] ?? []).map(d => d.groupLabel)
301
+ : data.computedData.map(d => d[0].groupLabel)
302
+ })
303
+ )
304
+
305
+ // 顯示範圍內的group labels
306
+ const scaleRangeGroupLabels$ = combineLatest({
307
+ groupScaleDomain: groupScaleDomain$,
308
+ groupLabels: groupLabels$
309
+ }).pipe(
310
+ switchMap(async d => d),
311
+ map(data => {
312
+ return data.groupLabels
313
+ .filter((d, i) => {
314
+ return i >= data.groupScaleDomain[0] && i <= data.groupScaleDomain[1]
315
+ })
316
+ })
317
+ )
318
+
319
+ const columnAmount$ = gridContainerPosition$.pipe(
320
+ map(gridContainerPosition => {
321
+ const maxColumnIndex = gridContainerPosition.reduce((acc, current) => {
322
+ return current.columnIndex > acc ? current.columnIndex : acc
323
+ }, 0)
324
+ return maxColumnIndex + 1
325
+ }),
326
+ distinctUntilChanged()
327
+ )
328
+
329
+ const rowAmount$ = gridContainerPosition$.pipe(
330
+ map(gridContainerPosition => {
331
+ const maxRowIndex = gridContainerPosition.reduce((acc, current) => {
332
+ return current.rowIndex > acc ? current.rowIndex : acc
333
+ }, 0)
334
+ return maxRowIndex + 1
335
+ }),
336
+ distinctUntilChanged()
337
+ )
338
+
339
+ return new Observable<(event: any) => { groupIndex: number; groupLabel: string }>(subscriber => {
340
+ combineLatest({
341
+ dataFormatter: fullDataFormatter$,
342
+ axisSize: gridAxesSize$,
343
+ fullChartParams: fullChartParams$,
344
+ scaleRangeGroupLabels: scaleRangeGroupLabels$,
345
+ groupLabels: groupLabels$,
346
+ groupScaleDomain: groupScaleDomain$,
347
+ columnAmount: columnAmount$,
348
+ rowAmount: rowAmount$,
349
+ layout: layout$
350
+ }).pipe(
351
+ takeUntil(destroy$),
352
+ switchMap(async (d) => d),
353
+ ).subscribe(data => {
354
+
355
+ const reverse = data.dataFormatter.valueAxis.position === 'right'
356
+ || data.dataFormatter.valueAxis.position === 'bottom'
357
+ ? true : false
358
+
359
+ // 比例尺座標對應非連續資料索引
360
+ const xIndexScale = createAxisToLabelIndexScale({
361
+ axisLabels: data.scaleRangeGroupLabels,
362
+ axisWidth: data.axisSize.width,
363
+ padding: data.dataFormatter.groupAxis.scalePadding,
364
+ reverse
365
+ })
366
+
367
+ // 依比例尺位置計算座標
368
+ const axisValuePredicate = (event: any) => {
369
+ return data.dataFormatter.groupAxis.position === 'bottom'
370
+ || data.dataFormatter.groupAxis.position === 'top'
371
+ ? event.offsetX - data.fullChartParams.padding.left
372
+ : event.offsetY - data.fullChartParams.padding.top
373
+ }
374
+
375
+ // 比例尺座標取得groupData的function
376
+ const createEventGroupData: (event: MouseEvent) => { groupIndex: number; groupLabel: string } = (event: any) => {
377
+ // 由於event座標是基於底層的,但是container會有多欄,所以要重新計算
378
+ const eventData = {
379
+ offsetX: event.offsetX * data.columnAmount % data.layout.rootWidth,
380
+ offsetY: event.offsetY * data.rowAmount % data.layout.rootHeight
381
+ }
382
+ // console.log('data.columnAmount', data.columnAmount, 'data.rowAmount', data.rowAmount, 'data.layout.rootWidth', data.layout.rootWidth, 'data.layout.rootHeight', data.layout.rootHeight)
383
+ const axisValue = axisValuePredicate(eventData)
384
+ const xIndex = xIndexScale(axisValue)
385
+ const currentxIndexStart = Math.ceil(data.groupScaleDomain[0]) // 因為有padding所以會有小數點,所以要無條件進位
386
+ const groupIndex = xIndex + currentxIndexStart
387
+
388
+ return {
389
+ groupIndex,
390
+ groupLabel: data.groupLabels[groupIndex] ?? ''
391
+ }
392
+ }
393
+
394
+ subscriber.next(createEventGroupData)
395
+
396
+ return function unsubscribe () {
397
+ destroy$.next(undefined)
398
+ }
399
+ })
400
+ })
401
+ }
402
+
403
+ export const gridGroupPositionObservable = ({ rootSelection, fullDataFormatter$, containerSize$, gridAxesContainerSize$, computedData$, gridContainerPosition$, layout$ }: {
404
+ rootSelection: d3.Selection<any, unknown, any, unknown>
405
+ fullDataFormatter$: Observable<DataFormatterGrid>
406
+ // gridAxesSize$: Observable<ContainerSize>
407
+ containerSize$: Observable<ContainerSize>
408
+ gridAxesContainerSize$: Observable<ContainerSize>
409
+ computedData$: Observable<ComputedDataGrid>
410
+ gridContainerPosition$: Observable<ContainerPositionScaled[]>
411
+ layout$: Observable<Layout>
412
+ }) => {
413
+ const rootMousemove$ = d3EventObservable(rootSelection, 'mousemove')
414
+
415
+ const groupScaleDomain$ = combineLatest({
416
+ fullDataFormatter: fullDataFormatter$,
417
+ // gridAxesSize: gridAxesSize$,
418
+ computedData: computedData$
419
+ }).pipe(
420
+ switchMap(async (d) => d),
421
+ map(data => {
422
+ const groupMin = 0
423
+ const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
424
+ // const groupScaleDomainMin = data.fullDataFormatter.groupAxis.scaleDomain[0] === 'auto'
425
+ // ? groupMin - data.fullDataFormatter.groupAxis.scalePadding
426
+ // : data.fullDataFormatter.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.groupAxis.scalePadding
427
+ const groupScaleDomainMin = data.fullDataFormatter.groupAxis.scaleDomain[0] - data.fullDataFormatter.groupAxis.scalePadding
428
+ const groupScaleDomainMax = data.fullDataFormatter.groupAxis.scaleDomain[1] === 'max'
429
+ ? groupMax + data.fullDataFormatter.groupAxis.scalePadding
430
+ : data.fullDataFormatter.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.groupAxis.scalePadding
431
+
432
+ return [groupScaleDomainMin, groupScaleDomainMax]
433
+ }),
434
+ shareReplay(1)
435
+ )
436
+
437
+ const groupLabels$ = combineLatest({
438
+ fullDataFormatter: fullDataFormatter$,
439
+ computedData: computedData$
440
+ }).pipe(
441
+ switchMap(async d => d),
442
+ map(data => {
443
+ return data.fullDataFormatter.seriesDirection === 'row'
444
+ ? (data.computedData[0] ?? []).map(d => d.groupLabel)
445
+ : data.computedData.map(d => d[0].groupLabel)
446
+ })
447
+ )
448
+
449
+ const scaleRangeGroupLabels$ = combineLatest({
450
+ groupScaleDomain: groupScaleDomain$,
451
+ groupLabels: groupLabels$
452
+ }).pipe(
453
+ switchMap(async d => d),
454
+ map(data => {
455
+ return data.groupLabels
456
+ .filter((d, i) => {
457
+ return i >= data.groupScaleDomain[0] && i <= data.groupScaleDomain[1]
458
+ })
459
+ })
460
+ )
461
+
462
+ const reverse$ = fullDataFormatter$.pipe(
463
+ map(d => {
464
+ return d.valueAxis.position === 'right' || d.valueAxis.position === 'bottom'
465
+ ? true
466
+ : false
467
+ })
468
+ )
469
+
470
+ // 比例尺座標對應非連續資料索引
471
+ const xIndexScale$ = combineLatest({
472
+ reverse: reverse$,
473
+ // gridAxesSize: gridAxesSize$,
474
+ gridAxesContainerSize: gridAxesContainerSize$,
475
+ scaleRangeGroupLabels: scaleRangeGroupLabels$,
476
+ fullDataFormatter: fullDataFormatter$
477
+ }).pipe(
478
+ switchMap(async d => d),
479
+ map(data => {
480
+ return createAxisToLabelIndexScale({
481
+ axisLabels: data.scaleRangeGroupLabels,
482
+ axisWidth: data.gridAxesContainerSize.width,
483
+ padding: data.fullDataFormatter.groupAxis.scalePadding,
484
+ reverse: data.reverse
485
+ })
486
+ })
487
+ )
488
+
489
+ // const columnAmount$ = gridContainerPosition$.pipe(
490
+ // map(gridContainerPosition => {
491
+ // const maxColumnIndex = gridContainerPosition.reduce((acc, current) => {
492
+ // return current.columnIndex > acc ? current.columnIndex : acc
493
+ // }, 0)
494
+ // return maxColumnIndex + 1
495
+ // }),
496
+ // distinctUntilChanged()
497
+ // )
498
+
499
+ // const rowAmount$ = gridContainerPosition$.pipe(
500
+ // map(gridContainerPosition => {
501
+ // const maxRowIndex = gridContainerPosition.reduce((acc, current) => {
502
+ // return current.rowIndex > acc ? current.rowIndex : acc
503
+ // }, 0)
504
+ // return maxRowIndex + 1
505
+ // }),
506
+ // distinctUntilChanged()
507
+ // )
508
+
509
+ const axisValue$ = combineLatest({
510
+ fullDataFormatter: fullDataFormatter$,
511
+ rootMousemove: rootMousemove$,
512
+ // containerSize: containerSize$,
513
+ gridContainerPosition: gridContainerPosition$,
514
+ // columnAmount: columnAmount$,
515
+ // rowAmount: rowAmount$,
516
+ layout: layout$
517
+ }).pipe(
518
+ switchMap(async d => d),
519
+ map(data => {
520
+ // // 由於event座標是基於底層的,但是container會有多欄,所以要重新計算
521
+ // const eventData = {
522
+ // offsetX: data.rootMousemove.offsetX * data.columnAmount % data.layout.rootWidth,
523
+ // offsetY: data.rootMousemove.offsetY * data.rowAmount % data.layout.rootHeight
524
+ // }
525
+ // return data.fullDataFormatter.groupAxis.position === 'bottom'
526
+ // || data.fullDataFormatter.groupAxis.position === 'top'
527
+ // ? eventData.offsetX - data.layout.left
528
+ // : eventData.offsetY - data.layout.top
529
+
530
+ if (data.fullDataFormatter.groupAxis.position === 'bottom' || data.fullDataFormatter.groupAxis.position === 'top') {
531
+ let x = data.rootMousemove.offsetX
532
+ const rangeArr = data.gridContainerPosition
533
+ .map((d, i) => [d.translate[0], data.gridContainerPosition[i + 1]?.translate[0] ?? data.layout.rootWidth])
534
+ .filter(d => d[0] < d[1])
535
+ const range = rangeArr.find(d => x >= d[0] && x <= d[1])
536
+ if (range) {
537
+ x = x - range[0]
538
+ }
539
+ return x - data.layout.left
540
+ } else {
541
+ let y = data.rootMousemove.offsetY
542
+ const rangeArr = data.gridContainerPosition
543
+ .map((d, i) => [d.translate[1], data.gridContainerPosition[i + 1]?.translate[1] ?? data.layout.rootHeight])
544
+ .filter(d => d[0] < d[1])
545
+ const range = rangeArr.find(d => y >= d[0] && y <= d[1])
546
+ if (range) {
547
+ y = y - range[0]
548
+ }
549
+ return y - data.layout.top
550
+ }
551
+ })
552
+ )
553
+
554
+ const groupIndex$ = combineLatest({
555
+ xIndexScale: xIndexScale$,
556
+ axisValue: axisValue$,
557
+ groupScaleDomain: groupScaleDomain$
558
+ }).pipe(
559
+ switchMap(async d => d),
560
+ map(data => {
561
+ const xIndex = data.xIndexScale(data.axisValue)
562
+ const currentxIndexStart = Math.ceil(data.groupScaleDomain[0]) // 因為有padding所以會有小數點,所以要無條件進位
563
+ return xIndex + currentxIndexStart
564
+ })
565
+ )
566
+
567
+ const groupLabel$ = combineLatest({
568
+ groupIndex: groupIndex$,
569
+ groupLabels: groupLabels$
570
+ }).pipe(
571
+ switchMap(async d => d),
572
+ map(data => {
573
+ return data.groupLabels[data.groupIndex] ?? ''
574
+ })
575
+ )
576
+
577
+ return combineLatest({
578
+ groupIndex: groupIndex$,
579
+ groupLabel: groupLabel$
580
+ }).pipe(
581
+ switchMap(async d => d),
582
+ map(data => {
583
+ return {
584
+ groupIndex: data.groupIndex,
585
+ groupLabel: data.groupLabel
586
+ }
587
+ })
588
+ )
589
+ }
590
+
591
+ // const gridContainerEventData$ = ({ eventData$, gridContainerPosition$, layout$ }: {
592
+ // eventData$: Observable<any>
593
+ // gridContainerPosition$: Observable<ContainerPositionScaled[]>
594
+ // layout$: Observable<Layout>
595
+ // }): Observable<{
596
+ // offsetX: number;
597
+ // offsetY: number;
598
+ // }> => {
599
+ // const columnAmount$ = gridContainerPosition$.pipe(
600
+ // map(gridContainerPosition => {
601
+ // const maxColumnIndex = gridContainerPosition.reduce((acc, current) => {
602
+ // return current.columnIndex > acc ? current.columnIndex : acc
603
+ // }, 0)
604
+ // return maxColumnIndex + 1
605
+ // }),
606
+ // distinctUntilChanged()
607
+ // )
608
+
609
+ // const rowAmount$ = gridContainerPosition$.pipe(
610
+ // map(gridContainerPosition => {
611
+ // const maxRowIndex = gridContainerPosition.reduce((acc, current) => {
612
+ // return current.rowIndex > acc ? current.rowIndex : acc
613
+ // }, 0)
614
+ // return maxRowIndex + 1
615
+ // }),
616
+ // distinctUntilChanged()
617
+ // )
618
+
619
+ // return combineLatest({
620
+ // eventData: eventData$,
621
+ // gridContainerPosition: gridContainerPosition$,
622
+ // layout: layout$,
623
+ // columnAmount: columnAmount$,
624
+ // rowAmount: rowAmount$
625
+ // }).pipe(
626
+ // switchMap(async d => d),
627
+ // map(data => {
628
+ // // 由於event座標是基於底層的,但是container會有多欄,所以要重新計算
629
+ // const eventData = {
630
+ // offsetX: data.eventData.offsetX * data.columnAmount % data.layout.rootWidth,
631
+ // offsetY: data.eventData.offsetY * data.rowAmount % data.layout.rootHeight
632
+ // }
633
+ // return eventData
634
+ // })
635
+ // )
636
636
  // }