@orbcharts/plugins-basic 3.0.0-beta.13 → 3.0.0-beta.14

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 (100) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic/src/utils/d3Utils.d.ts +1 -0
  3. package/dist/orbcharts-plugins-basic.es.js +5558 -5443
  4. package/dist/orbcharts-plugins-basic.umd.js +159 -78
  5. package/lib/core-types.ts +7 -7
  6. package/lib/core.ts +6 -6
  7. package/lib/plugins-basic-types.ts +6 -6
  8. package/package.json +44 -44
  9. package/src/base/BaseBars.ts +765 -765
  10. package/src/base/BaseBarsTriangle.ts +676 -676
  11. package/src/base/BaseDots.ts +464 -464
  12. package/src/base/BaseGroupAxis.ts +679 -679
  13. package/src/base/BaseLegend.ts +684 -684
  14. package/src/base/BaseLineAreas.ts +629 -629
  15. package/src/base/BaseLines.ts +706 -706
  16. package/src/base/BaseStackedBar.ts +782 -782
  17. package/src/base/BaseTooltip.ts +385 -385
  18. package/src/base/BaseValueAxis.ts +583 -583
  19. package/src/base/types.ts +2 -2
  20. package/src/const.ts +30 -30
  21. package/src/grid/defaults.ts +250 -246
  22. package/src/grid/gridObservables.ts +554 -554
  23. package/src/grid/index.ts +16 -16
  24. package/src/grid/plugins/Bars.ts +69 -69
  25. package/src/grid/plugins/BarsPN.ts +66 -66
  26. package/src/grid/plugins/BarsTriangle.ts +73 -73
  27. package/src/grid/plugins/Dots.ts +68 -68
  28. package/src/grid/plugins/GridLegend.ts +107 -107
  29. package/src/grid/plugins/GridTooltip.ts +66 -66
  30. package/src/grid/plugins/GridZoom.ts +218 -218
  31. package/src/grid/plugins/GroupAux.ts +1103 -1103
  32. package/src/grid/plugins/GroupAxis.ts +97 -97
  33. package/src/grid/plugins/LineAreas.ts +65 -65
  34. package/src/grid/plugins/Lines.ts +59 -59
  35. package/src/grid/plugins/StackedBar.ts +64 -64
  36. package/src/grid/plugins/StackedValueAxis.ts +96 -96
  37. package/src/grid/plugins/ValueAxis.ts +94 -94
  38. package/src/index.ts +6 -6
  39. package/src/multiGrid/defaults.ts +228 -224
  40. package/src/multiGrid/index.ts +14 -14
  41. package/src/multiGrid/multiGridObservables.ts +49 -49
  42. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  43. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  44. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  45. package/src/multiGrid/plugins/MultiGridLegend.ts +159 -159
  46. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  47. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  48. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  49. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  50. package/src/multiGrid/plugins/MultiStackedBar.ts +106 -106
  51. package/src/multiGrid/plugins/MultiStackedValueAxis.ts +134 -134
  52. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  53. package/src/multiGrid/plugins/OverlappingStackedValueAxes.ts +299 -299
  54. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  55. package/src/multiValue/defaults.ts +166 -166
  56. package/src/multiValue/index.ts +8 -8
  57. package/src/multiValue/multiValueObservables.ts +297 -297
  58. package/src/multiValue/plugins/MultiValueLegend.ts +107 -107
  59. package/src/multiValue/plugins/MultiValueTooltip.ts +66 -66
  60. package/src/multiValue/plugins/Scatter.ts +426 -426
  61. package/src/multiValue/plugins/ScatterBubbles.ts +554 -554
  62. package/src/multiValue/plugins/XYAux.ts +681 -681
  63. package/src/multiValue/plugins/XYAxes.ts +684 -684
  64. package/src/multiValue/plugins/XYZoom.ts +299 -299
  65. package/src/noneData/defaults.ts +102 -102
  66. package/src/noneData/index.ts +3 -3
  67. package/src/noneData/plugins/Container.ts +27 -27
  68. package/src/noneData/plugins/Tooltip.ts +373 -373
  69. package/src/relationship/defaults.ts +218 -196
  70. package/src/relationship/index.ts +5 -5
  71. package/src/relationship/plugins/ForceDirected.ts +1168 -1168
  72. package/src/relationship/plugins/ForceDirectedBubbles.ts +1403 -1395
  73. package/src/relationship/plugins/RelationshipLegend.ts +100 -100
  74. package/src/relationship/plugins/RelationshipTooltip.ts +66 -66
  75. package/src/relationship/relationshipObservables.ts +49 -49
  76. package/src/series/defaults.ts +230 -207
  77. package/src/series/index.ts +9 -9
  78. package/src/series/plugins/Bubbles.ts +620 -606
  79. package/src/series/plugins/Pie.ts +623 -623
  80. package/src/series/plugins/PieEventTexts.ts +284 -284
  81. package/src/series/plugins/PieLabels.ts +640 -640
  82. package/src/series/plugins/Rose.ts +516 -516
  83. package/src/series/plugins/RoseLabels.ts +600 -600
  84. package/src/series/plugins/SeriesLegend.ts +107 -107
  85. package/src/series/plugins/SeriesTooltip.ts +66 -66
  86. package/src/series/seriesObservables.ts +145 -145
  87. package/src/series/seriesUtils.ts +51 -51
  88. package/src/tree/defaults.ts +100 -78
  89. package/src/tree/index.ts +4 -4
  90. package/src/tree/plugins/TreeLegend.ts +100 -100
  91. package/src/tree/plugins/TreeMap.ts +341 -333
  92. package/src/tree/plugins/TreeTooltip.ts +66 -66
  93. package/src/utils/commonUtils.ts +21 -21
  94. package/src/utils/d3Graphics.ts +174 -174
  95. package/src/utils/d3Utils.ts +92 -74
  96. package/src/utils/observables.ts +14 -14
  97. package/src/utils/orbchartsUtils.ts +129 -115
  98. package/tsconfig.base.json +13 -13
  99. package/tsconfig.json +2 -2
  100. package/vite.config.js +22 -22
@@ -1,297 +1,297 @@
1
- import * as d3 from 'd3'
2
- import {
3
- Observable,
4
- Subject,
5
- debounceTime,
6
- of,
7
- takeUntil,
8
- filter,
9
- map,
10
- switchMap,
11
- combineLatest,
12
- merge,
13
- shareReplay,
14
- distinctUntilChanged
15
- } from 'rxjs'
16
- import type {
17
- ChartParams,
18
- HighlightTarget,
19
- DataFormatterMultiValue,
20
- ComputedDataMultiValue,
21
- ComputedDatumMultiValue,
22
- ComputedLayoutDatumMultiValue,
23
- TransformData,
24
- ContainerPositionScaled,
25
- Layout,
26
- } from '../../lib/core-types'
27
- import { createAxisToLabelIndexScale, createAxisToValueScale } from '../../lib/core'
28
- import { getClassName, getUniID } from '../utils/orbchartsUtils'
29
- import { d3EventObservable } from '../utils/observables'
30
-
31
- // 建立 multiValue 主要的 selection
32
- export const multiValueSelectionsObservable = ({ selection, pluginName, clipPathID, categoryLabels$, multiValueContainerPosition$, multiValueGraphicTransform$ }: {
33
- selection: d3.Selection<any, unknown, any, unknown>
34
- pluginName: string
35
- clipPathID: string
36
- // computedData$: Observable<ComputedDataMultiValue>
37
- categoryLabels$: Observable<string[]>
38
- multiValueContainerPosition$: Observable<ContainerPositionScaled[]>
39
- // multiValueAxesTransform$: Observable<TransformData>
40
- multiValueGraphicTransform$: Observable<TransformData>
41
- }) => {
42
- const categoryClassName = getClassName(pluginName, 'category')
43
- const axesClassName = getClassName(pluginName, 'axes')
44
- const graphicClassName = getClassName(pluginName, 'graphic')
45
-
46
- // <g> category selection(container排放位置)
47
- // <g> axes selection(圖軸)
48
- // <defs> clipPath selection
49
- // <g> graphic selection(圖形 scale 範圍的變形)
50
- const categorySelection$ = categoryLabels$.pipe(
51
- map((categoryLabels, i) => {
52
- return selection
53
- .selectAll<SVGGElement, string>(`g.${categoryClassName}`)
54
- .data(categoryLabels, d => d)
55
- .join(
56
- enter => {
57
- return enter
58
- .append('g')
59
- .classed(categoryClassName, true)
60
- .each((d, i, g) => {
61
- const axesSelection = d3.select(g[i])
62
- .selectAll<SVGGElement, ComputedDatumMultiValue[]>(`g.${axesClassName}`)
63
- .data([i])
64
- .join(
65
- enter => {
66
- return enter
67
- .append('g')
68
- .classed(axesClassName, true)
69
- .attr('clip-path', `url(#${clipPathID})`)
70
- .each((d, i, g) => {
71
- const defsSelection = d3.select(g[i])
72
- .selectAll<SVGDefsElement, any>('defs')
73
- .data([i])
74
- .join('defs')
75
-
76
- const graphicGSelection = d3.select(g[i])
77
- .selectAll<SVGGElement, any>('g')
78
- .data([i])
79
- .join('g')
80
- .classed(graphicClassName, true)
81
- })
82
- },
83
- update => update,
84
- exit => exit.remove()
85
- )
86
- })
87
- },
88
- update => update,
89
- exit => exit.remove()
90
- )
91
- }),
92
- shareReplay(1)
93
- )
94
-
95
- // <g> category selection
96
- combineLatest({
97
- categorySelection: categorySelection$,
98
- multiValueContainerPosition: multiValueContainerPosition$
99
- }).pipe(
100
- switchMap(async d => d)
101
- ).subscribe(data => {
102
- data.categorySelection
103
- .transition()
104
- .attr('transform', (d, i) => {
105
- const multiValueContainerPosition = data.multiValueContainerPosition[i] ?? data.multiValueContainerPosition[0]
106
- const translate = multiValueContainerPosition.translate
107
- const scale = multiValueContainerPosition.scale
108
- return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
109
- })
110
- })
111
-
112
- // <g> axes selection
113
- const axesSelection$ = categorySelection$.pipe(
114
- map(categorySelection => {
115
- return categorySelection
116
- .select<SVGGElement>(`g.${axesClassName}`)
117
- }),
118
- shareReplay(1)
119
- )
120
-
121
- // <defs> clipPath selection
122
- const defsSelection$ = axesSelection$.pipe(
123
- map(axesSelection => {
124
- return axesSelection.select<SVGDefsElement>('defs')
125
- }),
126
- shareReplay(1)
127
- )
128
-
129
- // <g> graphic selection
130
- const graphicGSelection$ = combineLatest({
131
- axesSelection: axesSelection$,
132
- multiValueGraphicTransform: multiValueGraphicTransform$
133
- }).pipe(
134
- switchMap(async d => d),
135
- map(data => {
136
- const graphicGSelection = data.axesSelection
137
- .select<SVGGElement>(`g.${graphicClassName}`)
138
- graphicGSelection
139
- .transition()
140
- .duration(50)
141
- .style('transform', data.multiValueGraphicTransform.value)
142
- return graphicGSelection
143
- }),
144
- shareReplay(1)
145
- )
146
-
147
- return {
148
- categorySelection$,
149
- axesSelection$,
150
- defsSelection$,
151
- graphicGSelection$
152
- }
153
- }
154
-
155
-
156
- export const multiValueXYPositionObservable = ({ rootSelection, fullDataFormatter$, filteredMinMaxXYData$, multiValueContainerPosition$, layout$ }: {
157
- rootSelection: d3.Selection<any, unknown, any, unknown>
158
- fullDataFormatter$: Observable<DataFormatterMultiValue>
159
- // computedData$: Observable<ComputedDataMultiValue>
160
- // minMaxXY$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
161
- filteredMinMaxXYData$: Observable<{
162
- minXDatum: ComputedLayoutDatumMultiValue
163
- maxXDatum: ComputedLayoutDatumMultiValue
164
- minYDatum: ComputedLayoutDatumMultiValue
165
- maxYDatum: ComputedLayoutDatumMultiValue
166
- }>
167
- multiValueContainerPosition$: Observable<ContainerPositionScaled[]>
168
- layout$: Observable<Layout>
169
- }) => {
170
- const rootMousemove$ = d3EventObservable(rootSelection, 'mousemove').pipe(
171
- debounceTime(2) // 避免過度頻繁觸發,實測時沒加電腦容易卡頓
172
- )
173
-
174
- const columnAmount$ = multiValueContainerPosition$.pipe(
175
- map(multiValueContainerPosition => {
176
- const maxColumnIndex = multiValueContainerPosition.reduce((acc, current) => {
177
- return current.columnIndex > acc ? current.columnIndex : acc
178
- }, 0)
179
- return maxColumnIndex + 1
180
- }),
181
- distinctUntilChanged(),
182
- shareReplay(1)
183
- )
184
-
185
- const rowAmount$ = multiValueContainerPosition$.pipe(
186
- map(multiValueContainerPosition => {
187
- const maxRowIndex = multiValueContainerPosition.reduce((acc, current) => {
188
- return current.rowIndex > acc ? current.rowIndex : acc
189
- }, 0)
190
- return maxRowIndex + 1
191
- }),
192
- distinctUntilChanged(),
193
- shareReplay(1)
194
- )
195
-
196
- // const xyScale$ = combineLatest({
197
- // layout: layout$,
198
- // filteredMinMaxXYData: filteredMinMaxXYData$,
199
- // fullDataFormatter: fullDataFormatter$,
200
- // columnAmount: columnAmount$,
201
- // rowAmount: rowAmount$
202
- // }).pipe(
203
- // switchMap(async d => d),
204
- // map(data => {
205
- // const xScale = createAxisToValueScale({
206
- // maxValue: data.filteredMinMaxXYData.maxXDatum.value[0],
207
- // minValue: data.filteredMinMaxXYData.minXDatum.value[0],
208
- // axisWidth: data.layout.width,
209
- // scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
210
- // scaleRange: data.fullDataFormatter.xAxis.scaleRange,
211
- // })
212
- // const yScale = createAxisToValueScale({
213
- // maxValue: data.filteredMinMaxXYData.maxYDatum.value[1],
214
- // minValue: data.filteredMinMaxXYData.minYDatum.value[1],
215
- // axisWidth: data.layout.height,
216
- // scaleDomain: data.fullDataFormatter.yAxis.scaleDomain,
217
- // scaleRange: data.fullDataFormatter.yAxis.scaleRange,
218
- // reverse: true
219
- // })
220
- // return { xScale, yScale }
221
- // })
222
- // )
223
-
224
- const xyScale$: Observable<{
225
- xScale: d3.ScaleLinear<number, number, never>;
226
- yScale: d3.ScaleLinear<number, number, never>;
227
- }> = new Observable(subscriber => {
228
- combineLatest({
229
- layout: layout$,
230
- filteredMinMaxXYData: filteredMinMaxXYData$,
231
- fullDataFormatter: fullDataFormatter$,
232
- columnAmount: columnAmount$,
233
- rowAmount: rowAmount$
234
- }).pipe(
235
- switchMap(async d => d),
236
- ).subscribe(data => {
237
- if (!data.filteredMinMaxXYData.minXDatum || !data.filteredMinMaxXYData.maxXDatum
238
- || data.filteredMinMaxXYData.minXDatum.value[0] == null || data.filteredMinMaxXYData.maxXDatum.value[0] == null
239
- || !data.filteredMinMaxXYData.minYDatum || !data.filteredMinMaxXYData.maxYDatum
240
- || data.filteredMinMaxXYData.minYDatum.value[1] == null || data.filteredMinMaxXYData.maxYDatum.value[1] == null
241
- ) {
242
- return
243
- }
244
- const xScale = createAxisToValueScale({
245
- maxValue: data.filteredMinMaxXYData.maxXDatum.value[0],
246
- minValue: data.filteredMinMaxXYData.minXDatum.value[0],
247
- axisWidth: data.layout.width,
248
- scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
249
- scaleRange: data.fullDataFormatter.xAxis.scaleRange,
250
- })
251
- const yScale = createAxisToValueScale({
252
- maxValue: data.filteredMinMaxXYData.maxYDatum.value[1],
253
- minValue: data.filteredMinMaxXYData.minYDatum.value[1],
254
- axisWidth: data.layout.height,
255
- scaleDomain: data.fullDataFormatter.yAxis.scaleDomain,
256
- scaleRange: data.fullDataFormatter.yAxis.scaleRange,
257
- reverse: true
258
- })
259
- subscriber.next({ xScale, yScale })
260
- })
261
- })
262
-
263
- const axisValue$ = combineLatest({
264
- fullDataFormatter: fullDataFormatter$,
265
- rootMousemove: rootMousemove$,
266
- columnAmount: columnAmount$,
267
- rowAmount: rowAmount$,
268
- layout: layout$,
269
- multiValueContainerPosition: multiValueContainerPosition$
270
- }).pipe(
271
- switchMap(async d => d),
272
- map(data => {
273
- // 由於event座標是基於底層的,但是container會有多欄,所以要重新計算
274
- return {
275
- x: ((data.rootMousemove.offsetX - data.layout.left) / data.multiValueContainerPosition[0].scale[0])
276
- % (data.layout.rootWidth / data.columnAmount / data.multiValueContainerPosition[0].scale[0]),
277
- y: ((data.rootMousemove.offsetY - data.layout.top) / data.multiValueContainerPosition[0].scale[1])
278
- % (data.layout.rootHeight / data.rowAmount / data.multiValueContainerPosition[0].scale[1])
279
- }
280
- })
281
- )
282
-
283
- return combineLatest({
284
- xyScale: xyScale$,
285
- axisValue: axisValue$
286
- }).pipe(
287
- switchMap(async d => d),
288
- map(data => {
289
- return {
290
- x: data.axisValue.x,
291
- y: data.axisValue.y,
292
- xValue: data.xyScale.xScale(data.axisValue.x),
293
- yValue: data.xyScale.yScale(data.axisValue.y)
294
- }
295
- })
296
- )
297
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ Observable,
4
+ Subject,
5
+ debounceTime,
6
+ of,
7
+ takeUntil,
8
+ filter,
9
+ map,
10
+ switchMap,
11
+ combineLatest,
12
+ merge,
13
+ shareReplay,
14
+ distinctUntilChanged
15
+ } from 'rxjs'
16
+ import type {
17
+ ChartParams,
18
+ HighlightTarget,
19
+ DataFormatterMultiValue,
20
+ ComputedDataMultiValue,
21
+ ComputedDatumMultiValue,
22
+ ComputedLayoutDatumMultiValue,
23
+ TransformData,
24
+ ContainerPositionScaled,
25
+ Layout,
26
+ } from '../../lib/core-types'
27
+ import { createAxisToLabelIndexScale, createAxisToValueScale } from '../../lib/core'
28
+ import { getClassName, getUniID } from '../utils/orbchartsUtils'
29
+ import { d3EventObservable } from '../utils/observables'
30
+
31
+ // 建立 multiValue 主要的 selection
32
+ export const multiValueSelectionsObservable = ({ selection, pluginName, clipPathID, categoryLabels$, multiValueContainerPosition$, multiValueGraphicTransform$ }: {
33
+ selection: d3.Selection<any, unknown, any, unknown>
34
+ pluginName: string
35
+ clipPathID: string
36
+ // computedData$: Observable<ComputedDataMultiValue>
37
+ categoryLabels$: Observable<string[]>
38
+ multiValueContainerPosition$: Observable<ContainerPositionScaled[]>
39
+ // multiValueAxesTransform$: Observable<TransformData>
40
+ multiValueGraphicTransform$: Observable<TransformData>
41
+ }) => {
42
+ const categoryClassName = getClassName(pluginName, 'category')
43
+ const axesClassName = getClassName(pluginName, 'axes')
44
+ const graphicClassName = getClassName(pluginName, 'graphic')
45
+
46
+ // <g> category selection(container排放位置)
47
+ // <g> axes selection(圖軸)
48
+ // <defs> clipPath selection
49
+ // <g> graphic selection(圖形 scale 範圍的變形)
50
+ const categorySelection$ = categoryLabels$.pipe(
51
+ map((categoryLabels, i) => {
52
+ return selection
53
+ .selectAll<SVGGElement, string>(`g.${categoryClassName}`)
54
+ .data(categoryLabels, d => d)
55
+ .join(
56
+ enter => {
57
+ return enter
58
+ .append('g')
59
+ .classed(categoryClassName, true)
60
+ .each((d, i, g) => {
61
+ const axesSelection = d3.select(g[i])
62
+ .selectAll<SVGGElement, ComputedDatumMultiValue[]>(`g.${axesClassName}`)
63
+ .data([i])
64
+ .join(
65
+ enter => {
66
+ return enter
67
+ .append('g')
68
+ .classed(axesClassName, true)
69
+ .attr('clip-path', `url(#${clipPathID})`)
70
+ .each((d, i, g) => {
71
+ const defsSelection = d3.select(g[i])
72
+ .selectAll<SVGDefsElement, any>('defs')
73
+ .data([i])
74
+ .join('defs')
75
+
76
+ const graphicGSelection = d3.select(g[i])
77
+ .selectAll<SVGGElement, any>('g')
78
+ .data([i])
79
+ .join('g')
80
+ .classed(graphicClassName, true)
81
+ })
82
+ },
83
+ update => update,
84
+ exit => exit.remove()
85
+ )
86
+ })
87
+ },
88
+ update => update,
89
+ exit => exit.remove()
90
+ )
91
+ }),
92
+ shareReplay(1)
93
+ )
94
+
95
+ // <g> category selection
96
+ combineLatest({
97
+ categorySelection: categorySelection$,
98
+ multiValueContainerPosition: multiValueContainerPosition$
99
+ }).pipe(
100
+ switchMap(async d => d)
101
+ ).subscribe(data => {
102
+ data.categorySelection
103
+ .transition()
104
+ .attr('transform', (d, i) => {
105
+ const multiValueContainerPosition = data.multiValueContainerPosition[i] ?? data.multiValueContainerPosition[0]
106
+ const translate = multiValueContainerPosition.translate
107
+ const scale = multiValueContainerPosition.scale
108
+ return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
109
+ })
110
+ })
111
+
112
+ // <g> axes selection
113
+ const axesSelection$ = categorySelection$.pipe(
114
+ map(categorySelection => {
115
+ return categorySelection
116
+ .select<SVGGElement>(`g.${axesClassName}`)
117
+ }),
118
+ shareReplay(1)
119
+ )
120
+
121
+ // <defs> clipPath selection
122
+ const defsSelection$ = axesSelection$.pipe(
123
+ map(axesSelection => {
124
+ return axesSelection.select<SVGDefsElement>('defs')
125
+ }),
126
+ shareReplay(1)
127
+ )
128
+
129
+ // <g> graphic selection
130
+ const graphicGSelection$ = combineLatest({
131
+ axesSelection: axesSelection$,
132
+ multiValueGraphicTransform: multiValueGraphicTransform$
133
+ }).pipe(
134
+ switchMap(async d => d),
135
+ map(data => {
136
+ const graphicGSelection = data.axesSelection
137
+ .select<SVGGElement>(`g.${graphicClassName}`)
138
+ graphicGSelection
139
+ .transition()
140
+ .duration(50)
141
+ .style('transform', data.multiValueGraphicTransform.value)
142
+ return graphicGSelection
143
+ }),
144
+ shareReplay(1)
145
+ )
146
+
147
+ return {
148
+ categorySelection$,
149
+ axesSelection$,
150
+ defsSelection$,
151
+ graphicGSelection$
152
+ }
153
+ }
154
+
155
+
156
+ export const multiValueXYPositionObservable = ({ rootSelection, fullDataFormatter$, filteredMinMaxXYData$, multiValueContainerPosition$, layout$ }: {
157
+ rootSelection: d3.Selection<any, unknown, any, unknown>
158
+ fullDataFormatter$: Observable<DataFormatterMultiValue>
159
+ // computedData$: Observable<ComputedDataMultiValue>
160
+ // minMaxXY$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
161
+ filteredMinMaxXYData$: Observable<{
162
+ minXDatum: ComputedLayoutDatumMultiValue
163
+ maxXDatum: ComputedLayoutDatumMultiValue
164
+ minYDatum: ComputedLayoutDatumMultiValue
165
+ maxYDatum: ComputedLayoutDatumMultiValue
166
+ }>
167
+ multiValueContainerPosition$: Observable<ContainerPositionScaled[]>
168
+ layout$: Observable<Layout>
169
+ }) => {
170
+ const rootMousemove$ = d3EventObservable(rootSelection, 'mousemove').pipe(
171
+ debounceTime(2) // 避免過度頻繁觸發,實測時沒加電腦容易卡頓
172
+ )
173
+
174
+ const columnAmount$ = multiValueContainerPosition$.pipe(
175
+ map(multiValueContainerPosition => {
176
+ const maxColumnIndex = multiValueContainerPosition.reduce((acc, current) => {
177
+ return current.columnIndex > acc ? current.columnIndex : acc
178
+ }, 0)
179
+ return maxColumnIndex + 1
180
+ }),
181
+ distinctUntilChanged(),
182
+ shareReplay(1)
183
+ )
184
+
185
+ const rowAmount$ = multiValueContainerPosition$.pipe(
186
+ map(multiValueContainerPosition => {
187
+ const maxRowIndex = multiValueContainerPosition.reduce((acc, current) => {
188
+ return current.rowIndex > acc ? current.rowIndex : acc
189
+ }, 0)
190
+ return maxRowIndex + 1
191
+ }),
192
+ distinctUntilChanged(),
193
+ shareReplay(1)
194
+ )
195
+
196
+ // const xyScale$ = combineLatest({
197
+ // layout: layout$,
198
+ // filteredMinMaxXYData: filteredMinMaxXYData$,
199
+ // fullDataFormatter: fullDataFormatter$,
200
+ // columnAmount: columnAmount$,
201
+ // rowAmount: rowAmount$
202
+ // }).pipe(
203
+ // switchMap(async d => d),
204
+ // map(data => {
205
+ // const xScale = createAxisToValueScale({
206
+ // maxValue: data.filteredMinMaxXYData.maxXDatum.value[0],
207
+ // minValue: data.filteredMinMaxXYData.minXDatum.value[0],
208
+ // axisWidth: data.layout.width,
209
+ // scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
210
+ // scaleRange: data.fullDataFormatter.xAxis.scaleRange,
211
+ // })
212
+ // const yScale = createAxisToValueScale({
213
+ // maxValue: data.filteredMinMaxXYData.maxYDatum.value[1],
214
+ // minValue: data.filteredMinMaxXYData.minYDatum.value[1],
215
+ // axisWidth: data.layout.height,
216
+ // scaleDomain: data.fullDataFormatter.yAxis.scaleDomain,
217
+ // scaleRange: data.fullDataFormatter.yAxis.scaleRange,
218
+ // reverse: true
219
+ // })
220
+ // return { xScale, yScale }
221
+ // })
222
+ // )
223
+
224
+ const xyScale$: Observable<{
225
+ xScale: d3.ScaleLinear<number, number, never>;
226
+ yScale: d3.ScaleLinear<number, number, never>;
227
+ }> = new Observable(subscriber => {
228
+ combineLatest({
229
+ layout: layout$,
230
+ filteredMinMaxXYData: filteredMinMaxXYData$,
231
+ fullDataFormatter: fullDataFormatter$,
232
+ columnAmount: columnAmount$,
233
+ rowAmount: rowAmount$
234
+ }).pipe(
235
+ switchMap(async d => d),
236
+ ).subscribe(data => {
237
+ if (!data.filteredMinMaxXYData.minXDatum || !data.filteredMinMaxXYData.maxXDatum
238
+ || data.filteredMinMaxXYData.minXDatum.value[0] == null || data.filteredMinMaxXYData.maxXDatum.value[0] == null
239
+ || !data.filteredMinMaxXYData.minYDatum || !data.filteredMinMaxXYData.maxYDatum
240
+ || data.filteredMinMaxXYData.minYDatum.value[1] == null || data.filteredMinMaxXYData.maxYDatum.value[1] == null
241
+ ) {
242
+ return
243
+ }
244
+ const xScale = createAxisToValueScale({
245
+ maxValue: data.filteredMinMaxXYData.maxXDatum.value[0],
246
+ minValue: data.filteredMinMaxXYData.minXDatum.value[0],
247
+ axisWidth: data.layout.width,
248
+ scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
249
+ scaleRange: data.fullDataFormatter.xAxis.scaleRange,
250
+ })
251
+ const yScale = createAxisToValueScale({
252
+ maxValue: data.filteredMinMaxXYData.maxYDatum.value[1],
253
+ minValue: data.filteredMinMaxXYData.minYDatum.value[1],
254
+ axisWidth: data.layout.height,
255
+ scaleDomain: data.fullDataFormatter.yAxis.scaleDomain,
256
+ scaleRange: data.fullDataFormatter.yAxis.scaleRange,
257
+ reverse: true
258
+ })
259
+ subscriber.next({ xScale, yScale })
260
+ })
261
+ })
262
+
263
+ const axisValue$ = combineLatest({
264
+ fullDataFormatter: fullDataFormatter$,
265
+ rootMousemove: rootMousemove$,
266
+ columnAmount: columnAmount$,
267
+ rowAmount: rowAmount$,
268
+ layout: layout$,
269
+ multiValueContainerPosition: multiValueContainerPosition$
270
+ }).pipe(
271
+ switchMap(async d => d),
272
+ map(data => {
273
+ // 由於event座標是基於底層的,但是container會有多欄,所以要重新計算
274
+ return {
275
+ x: ((data.rootMousemove.offsetX - data.layout.left) / data.multiValueContainerPosition[0].scale[0])
276
+ % (data.layout.rootWidth / data.columnAmount / data.multiValueContainerPosition[0].scale[0]),
277
+ y: ((data.rootMousemove.offsetY - data.layout.top) / data.multiValueContainerPosition[0].scale[1])
278
+ % (data.layout.rootHeight / data.rowAmount / data.multiValueContainerPosition[0].scale[1])
279
+ }
280
+ })
281
+ )
282
+
283
+ return combineLatest({
284
+ xyScale: xyScale$,
285
+ axisValue: axisValue$
286
+ }).pipe(
287
+ switchMap(async d => d),
288
+ map(data => {
289
+ return {
290
+ x: data.axisValue.x,
291
+ y: data.axisValue.y,
292
+ xValue: data.xyScale.xScale(data.axisValue.x),
293
+ yValue: data.xyScale.yScale(data.axisValue.y)
294
+ }
295
+ })
296
+ )
297
+ }