@orbcharts/plugins-basic 3.0.8 → 3.0.10

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 (120) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +6182 -6004
  3. package/dist/orbcharts-plugins-basic.umd.js +44 -44
  4. package/dist/src/series/defaults.d.ts +2 -1
  5. package/dist/src/series/index.d.ts +1 -0
  6. package/dist/src/series/plugins/Indicator.d.ts +3 -0
  7. package/lib/core-types.ts +7 -7
  8. package/lib/core.ts +6 -6
  9. package/lib/gridObservables.ts +6 -6
  10. package/lib/plugins-basic-types.ts +6 -6
  11. package/package.json +48 -48
  12. package/src/base/BaseBars.ts +765 -765
  13. package/src/base/BaseBarsTriangle.ts +676 -676
  14. package/src/base/BaseDots.ts +464 -464
  15. package/src/base/BaseGroupAxis.ts +691 -691
  16. package/src/base/BaseLegend.ts +684 -684
  17. package/src/base/BaseLineAreas.ts +629 -629
  18. package/src/base/BaseLines.ts +706 -706
  19. package/src/base/BaseOrdinalBubbles.ts +729 -729
  20. package/src/base/BaseRacingBars.ts +582 -582
  21. package/src/base/BaseRacingLabels.ts +404 -404
  22. package/src/base/BaseRacingValueLabels.ts +403 -403
  23. package/src/base/BaseStackedBars.ts +793 -793
  24. package/src/base/BaseTooltip.ts +408 -408
  25. package/src/base/BaseValueAxis.ts +600 -600
  26. package/src/base/BaseXAxis.ts +427 -427
  27. package/src/base/BaseXZoom.ts +241 -241
  28. package/src/base/BaseYAxis.ts +389 -389
  29. package/src/base/types.ts +2 -2
  30. package/src/const.ts +30 -30
  31. package/src/grid/defaults.ts +213 -213
  32. package/src/grid/gridObservables.ts +635 -635
  33. package/src/grid/index.ts +16 -16
  34. package/src/grid/plugins/Bars.ts +69 -69
  35. package/src/grid/plugins/BarsPN.ts +66 -66
  36. package/src/grid/plugins/BarsTriangle.ts +73 -73
  37. package/src/grid/plugins/Dots.ts +68 -68
  38. package/src/grid/plugins/GridLegend.ts +107 -107
  39. package/src/grid/plugins/GridTooltip.ts +66 -66
  40. package/src/grid/plugins/GroupAux.ts +1095 -1095
  41. package/src/grid/plugins/GroupAxis.ts +73 -73
  42. package/src/grid/plugins/GroupZoom.ts +218 -218
  43. package/src/grid/plugins/LineAreas.ts +65 -65
  44. package/src/grid/plugins/Lines.ts +59 -59
  45. package/src/grid/plugins/StackedBars.ts +66 -66
  46. package/src/grid/plugins/StackedValueAxis.ts +97 -97
  47. package/src/grid/plugins/ValueAxis.ts +94 -94
  48. package/src/index.ts +6 -6
  49. package/src/multiGrid/defaults.ts +244 -244
  50. package/src/multiGrid/index.ts +14 -14
  51. package/src/multiGrid/multiGridObservables.ts +50 -50
  52. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  53. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  54. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  55. package/src/multiGrid/plugins/MultiGridLegend.ts +169 -169
  56. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  57. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  58. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  59. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  60. package/src/multiGrid/plugins/MultiStackedBars.ts +109 -109
  61. package/src/multiGrid/plugins/MultiStackedValueAxis.ts +135 -135
  62. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  63. package/src/multiGrid/plugins/OverlappingStackedValueAxes.ts +300 -300
  64. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  65. package/src/multiValue/defaults.ts +523 -523
  66. package/src/multiValue/index.ts +16 -16
  67. package/src/multiValue/multiValueObservables.ts +781 -781
  68. package/src/multiValue/plugins/MultiValueLegend.ts +107 -107
  69. package/src/multiValue/plugins/MultiValueTooltip.ts +66 -66
  70. package/src/multiValue/plugins/OrdinalAux.ts +660 -660
  71. package/src/multiValue/plugins/OrdinalAxis.ts +524 -524
  72. package/src/multiValue/plugins/OrdinalBubbles.ts +226 -226
  73. package/src/multiValue/plugins/OrdinalZoom.ts +57 -57
  74. package/src/multiValue/plugins/RacingBars.ts +375 -375
  75. package/src/multiValue/plugins/RacingCounterTexts.ts +300 -300
  76. package/src/multiValue/plugins/RacingValueAxis.ts +114 -114
  77. package/src/multiValue/plugins/Scatter.ts +486 -486
  78. package/src/multiValue/plugins/ScatterBubbles.ts +635 -635
  79. package/src/multiValue/plugins/XAxis.ts +107 -107
  80. package/src/multiValue/plugins/XYAux.ts +683 -683
  81. package/src/multiValue/plugins/XYAxes.ts +194 -194
  82. package/src/multiValue/plugins/XYAxes_legacy.ts +683 -683
  83. package/src/multiValue/plugins/XZoom.ts +40 -40
  84. package/src/noneData/defaults.ts +102 -102
  85. package/src/noneData/index.ts +3 -3
  86. package/src/noneData/plugins/Container.ts +27 -27
  87. package/src/noneData/plugins/Tooltip.ts +373 -373
  88. package/src/relationship/defaults.ts +221 -221
  89. package/src/relationship/index.ts +5 -5
  90. package/src/relationship/plugins/ForceDirected.ts +1056 -1056
  91. package/src/relationship/plugins/ForceDirectedBubbles.ts +1294 -1294
  92. package/src/relationship/plugins/RelationshipLegend.ts +100 -100
  93. package/src/relationship/plugins/RelationshipTooltip.ts +66 -66
  94. package/src/relationship/relationshipObservables.ts +49 -49
  95. package/src/series/defaults.ts +236 -224
  96. package/src/series/index.ts +10 -9
  97. package/src/series/plugins/Bubbles.ts +784 -784
  98. package/src/series/plugins/Indicator.ts +298 -0
  99. package/src/series/plugins/Pie.ts +622 -622
  100. package/src/series/plugins/PieEventTexts.ts +283 -283
  101. package/src/series/plugins/PieLabels.ts +639 -639
  102. package/src/series/plugins/Rose.ts +515 -515
  103. package/src/series/plugins/RoseLabels.ts +599 -599
  104. package/src/series/plugins/SeriesLegend.ts +107 -107
  105. package/src/series/plugins/SeriesTooltip.ts +66 -66
  106. package/src/series/seriesObservables.ts +168 -168
  107. package/src/series/seriesUtils.ts +51 -51
  108. package/src/tree/defaults.ts +102 -102
  109. package/src/tree/index.ts +4 -4
  110. package/src/tree/plugins/TreeLegend.ts +100 -100
  111. package/src/tree/plugins/TreeMap.ts +341 -341
  112. package/src/tree/plugins/TreeTooltip.ts +66 -66
  113. package/src/utils/commonUtils.ts +31 -31
  114. package/src/utils/d3Graphics.ts +176 -176
  115. package/src/utils/d3Utils.ts +92 -92
  116. package/src/utils/observables.ts +14 -14
  117. package/src/utils/orbchartsUtils.ts +129 -129
  118. package/tsconfig.base.json +13 -13
  119. package/tsconfig.json +2 -2
  120. 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
  // }