@orbcharts/core 3.0.1 → 3.0.3

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 (78) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-core.es.js +2345 -2229
  3. package/dist/orbcharts-core.umd.js +5 -4
  4. package/dist/src/{utils → multiValue}/multiValueObservables.d.ts +20 -10
  5. package/dist/src/utils/errorMessage.d.ts +5 -0
  6. package/dist/src/utils/index.d.ts +0 -1
  7. package/dist/src/utils/observables.d.ts +3 -2
  8. package/dist/src/utils/orbchartsUtils.d.ts +1 -0
  9. package/lib/core-types.ts +7 -7
  10. package/package.json +46 -42
  11. package/src/AbstractChart.ts +57 -57
  12. package/src/GridChart.ts +24 -24
  13. package/src/MultiGridChart.ts +24 -24
  14. package/src/MultiValueChart.ts +24 -24
  15. package/src/RelationshipChart.ts +24 -24
  16. package/src/SeriesChart.ts +24 -24
  17. package/src/TreeChart.ts +24 -24
  18. package/src/base/createBaseChart.ts +526 -506
  19. package/src/base/createBasePlugin.ts +154 -154
  20. package/src/base/validators/chartOptionsValidator.ts +23 -23
  21. package/src/base/validators/chartParamsValidator.ts +133 -133
  22. package/src/base/validators/elementValidator.ts +13 -13
  23. package/src/base/validators/pluginsValidator.ts +14 -14
  24. package/src/defaults.ts +283 -282
  25. package/src/defineGridPlugin.ts +3 -3
  26. package/src/defineMultiGridPlugin.ts +3 -3
  27. package/src/defineMultiValuePlugin.ts +3 -3
  28. package/src/defineNoneDataPlugin.ts +4 -4
  29. package/src/defineRelationshipPlugin.ts +3 -3
  30. package/src/defineSeriesPlugin.ts +3 -3
  31. package/src/defineTreePlugin.ts +3 -3
  32. package/src/grid/computedDataFn.ts +129 -129
  33. package/src/grid/contextObserverCallback.ts +201 -198
  34. package/src/grid/dataFormatterValidator.ts +125 -120
  35. package/src/grid/dataValidator.ts +12 -12
  36. package/src/{utils → grid}/gridObservables.ts +718 -705
  37. package/src/index.ts +20 -20
  38. package/src/multiGrid/computedDataFn.ts +123 -123
  39. package/src/multiGrid/contextObserverCallback.ts +75 -72
  40. package/src/multiGrid/dataFormatterValidator.ts +120 -115
  41. package/src/multiGrid/dataValidator.ts +12 -12
  42. package/src/{utils → multiGrid}/multiGridObservables.ts +401 -401
  43. package/src/multiValue/computedDataFn.ts +113 -113
  44. package/src/multiValue/contextObserverCallback.ts +328 -276
  45. package/src/multiValue/dataFormatterValidator.ts +94 -89
  46. package/src/multiValue/dataValidator.ts +12 -12
  47. package/src/{utils → multiValue}/multiValueObservables.ts +1219 -1044
  48. package/src/relationship/computedDataFn.ts +159 -159
  49. package/src/relationship/contextObserverCallback.ts +80 -80
  50. package/src/relationship/dataFormatterValidator.ts +13 -13
  51. package/src/relationship/dataValidator.ts +13 -13
  52. package/src/{utils → relationship}/relationshipObservables.ts +84 -84
  53. package/src/series/computedDataFn.ts +88 -88
  54. package/src/series/contextObserverCallback.ts +107 -107
  55. package/src/series/dataFormatterValidator.ts +46 -41
  56. package/src/series/dataValidator.ts +12 -12
  57. package/src/{utils → series}/seriesObservables.ts +175 -175
  58. package/src/tree/computedDataFn.ts +129 -129
  59. package/src/tree/contextObserverCallback.ts +58 -58
  60. package/src/tree/dataFormatterValidator.ts +13 -13
  61. package/src/tree/dataValidator.ts +13 -13
  62. package/src/{utils → tree}/treeObservables.ts +105 -105
  63. package/src/utils/commonUtils.ts +55 -55
  64. package/src/utils/d3Scale.ts +198 -198
  65. package/src/utils/errorMessage.ts +43 -42
  66. package/src/utils/index.ts +4 -10
  67. package/src/utils/observables.ts +293 -281
  68. package/src/utils/orbchartsUtils.ts +396 -377
  69. package/src/utils/validator.ts +126 -126
  70. package/tsconfig.base.json +13 -13
  71. package/tsconfig.json +2 -2
  72. package/vite-env.d.ts +6 -6
  73. package/vite.config.js +22 -22
  74. /package/dist/src/{utils → grid}/gridObservables.d.ts +0 -0
  75. /package/dist/src/{utils → multiGrid}/multiGridObservables.d.ts +0 -0
  76. /package/dist/src/{utils → relationship}/relationshipObservables.d.ts +0 -0
  77. /package/dist/src/{utils → series}/seriesObservables.d.ts +0 -0
  78. /package/dist/src/{utils → tree}/treeObservables.d.ts +0 -0
@@ -1,1044 +1,1219 @@
1
- import {
2
- combineLatest,
3
- distinctUntilChanged,
4
- iif,
5
- filter,
6
- map,
7
- merge,
8
- takeUntil,
9
- shareReplay,
10
- switchMap,
11
- Subject,
12
- Observable } from 'rxjs'
13
- import type {
14
- AxisPosition,
15
- ChartType,
16
- ChartParams,
17
- ComputedDataTypeMap,
18
- ComputedDatumTypeMap,
19
- ComputedDataMultiValue,
20
- ComputedDatumMultiValue,
21
- ComputedDatumWithSumMultiValue,
22
- ContainerSize,
23
- DataFormatterTypeMap,
24
- DataFormatterMultiValue,
25
- DataFormatterXYAxis,
26
- ComputedXYDatumMultiValue,
27
- ComputedXYDataMultiValue,
28
- ContainerPositionScaled,
29
- HighlightTarget,
30
- Layout,
31
- TransformData } from '../../lib/core-types'
32
- import { getMinMax, getMinMaxMultiValue } from './orbchartsUtils'
33
- import { createValueToAxisScale, createLabelToAxisScale, createAxisToLabelIndexScale } from './d3Scale'
34
- import { calcContainerPositionScaled } from './orbchartsUtils'
35
-
36
- export const xyMinMaxObservable = ({ computedData$, xyValueIndex$ }: {
37
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
38
- xyValueIndex$: Observable<[number, number]>
39
- }) => {
40
- return combineLatest({
41
- computedData: computedData$,
42
- xyValueIndex: xyValueIndex$,
43
- }).pipe(
44
- map(data => {
45
- const flatData = data.computedData.flat()
46
- const [minX, maxX] = getMinMax(flatData.map(d => d.value[data.xyValueIndex[0]]))
47
- const [minY, maxY] = getMinMax(flatData.map(d => d.value[data.xyValueIndex[1]]))
48
- return { minX, maxX, minY, maxY }
49
- })
50
- )
51
- }
52
-
53
- export const computedXYDataObservable = ({ computedData$, xyMinMax$, xyValueIndex$, fullDataFormatter$, layout$ }: {
54
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
55
- xyMinMax$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
56
- xyValueIndex$: Observable<[number, number]>
57
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
58
- layout$: Observable<Layout>
59
- }): Observable<ComputedXYDataMultiValue> => {
60
-
61
- // 未篩選範圍前的 scale
62
- function createOriginXScale (xyMinMax: { minX: number, maxX: number, minY: number, maxY: number }, layout: Layout) {
63
- let maxValue = xyMinMax.maxX
64
- let minValue = xyMinMax.minX
65
- if (minValue === maxValue && maxValue === 0) {
66
- // 避免最大及最小值相同造成無法計算scale
67
- maxValue = 1
68
- }
69
- const valueScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
70
- maxValue,
71
- minValue,
72
- axisWidth: layout.width,
73
- scaleDomain: ['auto', 'auto'], // 不使用dataFormatter設定 --> 以0為基準到最大或最小值為範圍( * 如果是使用[minValue, maxValue]的話,在兩者很接近的情況下有可能造成scale倍率過高而svg變型時失真的情況)
74
- scaleRange: [0, 1] // 不使用dataFormatter設定
75
- })
76
-
77
- return valueScale
78
- }
79
-
80
- // 未篩選範圍及visible前的 scale
81
- function createOriginYScale (xyMinMax: { minX: number, maxX: number, minY: number, maxY: number }, layout: Layout) {
82
- let maxValue = xyMinMax.maxY
83
- let minValue = xyMinMax.minY
84
- if (minValue === maxValue && maxValue === 0) {
85
- // 避免最大及最小值相同造成無法計算scale
86
- maxValue = 1
87
- }
88
- const valueScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
89
- maxValue,
90
- minValue,
91
- axisWidth: layout.height,
92
- scaleDomain: ['auto', 'auto'], // 不使用dataFormatter設定 --> 以0為基準到最大或最小值為範圍( * 如果是使用[minValue, maxValue]的話,在兩者很接近的情況下有可能造成scale倍率過高而svg變型時失真的情況)
93
- scaleRange: [0, 1], // 不使用dataFormatter設定
94
- reverse: true
95
- })
96
-
97
- return valueScale
98
- }
99
-
100
- return combineLatest({
101
- computedData: computedData$,
102
- xyMinMax: xyMinMax$,
103
- xyValueIndex: xyValueIndex$,
104
- fullDataFormatter: fullDataFormatter$,
105
- layout: layout$
106
- }).pipe(
107
- switchMap(async d => d),
108
- map(data => {
109
-
110
- const xScale = createOriginXScale(data.xyMinMax, data.layout)
111
- const yScale = createOriginYScale(data.xyMinMax, data.layout)
112
-
113
- return data.computedData
114
- .map((categoryData, categoryIndex) => {
115
- return categoryData.map((datum, datumIndex) => {
116
- return {
117
- ...datum,
118
- axisX: xScale(datum.value[data.xyValueIndex[0]] ?? 0),
119
- // axisY: data.layout.height - yScale(datum.value[1] ?? 0), // y軸的繪圖座標是從上到下,所以反轉
120
- axisY: yScale(datum.value[data.xyValueIndex[1]] ?? 0), // y軸的繪圖座標是從上到下,所以反轉
121
- }
122
- })
123
- })
124
- })
125
- )
126
- }
127
-
128
- // export const multiValueAxesTransformObservable = ({ fullDataFormatter$, layout$ }: {
129
- // fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
130
- // layout$: Observable<Layout>
131
- // }): Observable<TransformData> => {
132
- // const destroy$ = new Subject()
133
-
134
- // function calcAxesTransform ({ xAxis, yAxis, width, height }: {
135
- // xAxis: DataFormatterXYAxis,
136
- // yAxis: DataFormatterXYAxis,
137
- // width: number,
138
- // height: number
139
- // }): TransformData {
140
- // if (!xAxis || !yAxis) {
141
- // return {
142
- // translate: [0, 0],
143
- // scale: [1, 1],
144
- // rotate: 0,
145
- // rotateX: 0,
146
- // rotateY: 0,
147
- // value: ''
148
- // }
149
- // }
150
- // // const width = size.width - fullChartParams.layout.left - fullChartParams.layout.right
151
- // // const height = size.height - fullChartParams.layout.top - fullChartParams.layout.bottom
152
- // let translateX = 0
153
- // let translateY = height
154
- // let rotate = 0
155
- // let rotateX = 180
156
- // let rotateY = 0
157
-
158
- // return {
159
- // translate: [translateX, translateY],
160
- // scale: [1, 1],
161
- // rotate,
162
- // rotateX,
163
- // rotateY,
164
- // value: `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
165
- // }
166
- // }
167
-
168
- // return new Observable(subscriber => {
169
- // combineLatest({
170
- // fullDataFormatter: fullDataFormatter$,
171
- // layout: layout$
172
- // }).pipe(
173
- // takeUntil(destroy$),
174
- // switchMap(async (d) => d),
175
- // ).subscribe(data => {
176
- // const axesTransformData = calcAxesTransform({
177
- // xAxis: data.fullDataFormatter.xAxis,
178
- // yAxis: data.fullDataFormatter.yAxis,
179
- // width: data.layout.width,
180
- // height: data.layout.height
181
- // })
182
-
183
- // subscriber.next(axesTransformData)
184
- // })
185
-
186
- // return function unscbscribe () {
187
- // destroy$.next(undefined)
188
- // }
189
- // })
190
- // }
191
-
192
-
193
- // export const multiValueAxesReverseTransformObservable = ({ multiValueAxesTransform$ }: {
194
- // multiValueAxesTransform$: Observable<TransformData>
195
- // }): Observable<TransformData> => {
196
- // return multiValueAxesTransform$.pipe(
197
- // map(d => {
198
- // // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
199
- // const translate: [number, number] = [0, 0] // 無需逆轉
200
- // const scale: [number, number] = [1 / d.scale[0], 1 / d.scale[1]]
201
- // const rotate = d.rotate * -1
202
- // const rotateX = d.rotateX * -1
203
- // const rotateY = d.rotateY * -1
204
- // return {
205
- // translate,
206
- // scale,
207
- // rotate,
208
- // rotateX,
209
- // rotateY,
210
- // value: `translate(${translate[0]}px, ${translate[1]}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotate(${rotate}deg)`
211
- // }
212
- // }),
213
- // )
214
- // }
215
-
216
-
217
-
218
- // export const multiValueAxesSizeObservable = ({ fullDataFormatter$, layout$ }: {
219
- // fullDataFormatter$: Observable<DataFormatterMultiValue>
220
- // layout$: Observable<Layout>
221
- // }): Observable<{
222
- // width: number;
223
- // height: number;
224
- // }> => {
225
- // const destroy$ = new Subject()
226
-
227
- // function calcAxesSize ({ xAxisPosition, yAxisPosition, width, height }: {
228
- // xAxisPosition: AxisPosition
229
- // yAxisPosition: AxisPosition
230
- // width: number
231
- // height: number
232
- // }) {
233
- // if ((xAxisPosition === 'bottom' || xAxisPosition === 'top') && (yAxisPosition === 'left' || yAxisPosition === 'right')) {
234
- // return { width, height }
235
- // } else if ((xAxisPosition === 'left' || xAxisPosition === 'right') && (yAxisPosition === 'bottom' || yAxisPosition === 'top')) {
236
- // return {
237
- // width: height,
238
- // height: width
239
- // }
240
- // } else {
241
- // // default
242
- // return { width, height }
243
- // }
244
- // }
245
-
246
- // return new Observable(subscriber => {
247
- // combineLatest({
248
- // fullDataFormatter: fullDataFormatter$,
249
- // layout: layout$
250
- // }).pipe(
251
- // takeUntil(destroy$),
252
- // switchMap(async (d) => d),
253
- // ).subscribe(data => {
254
-
255
- // const axisSize = calcAxesSize({
256
- // xAxisPosition: 'bottom',
257
- // yAxisPosition: 'left',
258
- // width: data.layout.width,
259
- // height: data.layout.height,
260
- // })
261
-
262
- // subscriber.next(axisSize)
263
-
264
- // return function unsubscribe () {
265
- // destroy$.next(undefined)
266
- // }
267
- // })
268
- // })
269
- // }
270
-
271
- // export const highlightObservable = ({ computedData$, fullChartParams$, event$ }: {
272
- // computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
273
- // fullChartParams$: Observable<ChartParams>
274
- // event$: Subject<any>
275
- // }): Observable<string[]> => {
276
- // const datumList$ = computedData$.pipe(
277
- // map(d => d.flat())
278
- // )
279
- // return highlightObservable ({ datumList$, fullChartParams$, event$ })
280
- // }
281
-
282
- export const categoryLabelsObservable = ({ computedData$, fullDataFormatter$ }: {
283
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
284
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
285
- }) => {
286
- return computedData$.pipe(
287
- map(data => {
288
- return data
289
- .map(d => d[0] ? d[0].categoryLabel : '')
290
- // .filter(d => d != null && d != '')
291
- }),
292
- distinctUntilChanged((a, b) => {
293
- return JSON.stringify(a).length === JSON.stringify(b).length
294
- }),
295
- )
296
- }
297
-
298
- export const visibleComputedDataObservable = ({ computedData$ }: { computedData$: Observable<ComputedDataTypeMap<'multiValue'>> }) => {
299
- return computedData$.pipe(
300
- map(data => {
301
- return data
302
- .map(categoryData => {
303
- return categoryData.filter(d => d.visible == true)
304
- })
305
- .filter(categoryData => {
306
- return categoryData.length > 0
307
- })
308
- })
309
- )
310
- }
311
-
312
- export const visibleComputedSumDataObservable = ({ visibleComputedData$ }: { visibleComputedData$: Observable<ComputedDataMultiValue> }) => {
313
- return visibleComputedData$.pipe(
314
- map(data => {
315
- return data.map(categoryData => {
316
- return categoryData
317
- .map(d => {
318
- let newDatum = d as ComputedDatumWithSumMultiValue
319
- // 新增總計資料欄位
320
- newDatum.sum = newDatum.value.reduce((acc, curr) => acc + curr, 0)
321
- return newDatum
322
- })
323
- })
324
- })
325
- )
326
- }
327
-
328
- // Ranking資料 - value[index] 排序
329
- export const visibleComputedRankingByIndexDataObservable = ({ xyValueIndex$, isCategorySeprate$, visibleComputedData$ }: {
330
- xyValueIndex$: Observable<[number, number]>
331
- isCategorySeprate$: Observable<boolean>
332
- visibleComputedData$: Observable<ComputedDatumMultiValue[][]>
333
- }) => {
334
-
335
- return combineLatest({
336
- isCategorySeprate: isCategorySeprate$,
337
- xyValueIndex: xyValueIndex$,
338
- visibleComputedData: visibleComputedData$
339
- }).pipe(
340
- switchMap(async d => d),
341
- map(data => {
342
- const xValueIndex = data.xyValueIndex[0]
343
- // -- category 分開 --
344
- if (data.isCategorySeprate) {
345
- return data.visibleComputedData
346
- .map(categoryData => {
347
- return categoryData
348
- .sort((a, b) => {
349
- const bValue = b.value[xValueIndex] ?? - Infinity // - Infinity 為最小值
350
- const aValue = a.value[xValueIndex] ?? - Infinity
351
-
352
- return bValue - aValue
353
- })
354
- })
355
- // -- 用 value[index] 排序 --
356
- } else {
357
- return [
358
- data.visibleComputedData
359
- .flat()
360
- .sort((a, b) => {
361
- const bValue = b.value[xValueIndex] ?? - Infinity // - Infinity 為最小值
362
- const aValue = a.value[xValueIndex] ?? - Infinity
363
-
364
- return bValue - aValue
365
- })
366
- ]
367
- }
368
- })
369
- )
370
- }
371
-
372
- // Ranking資料 - 用所有 valueIndex 加總資料排序
373
- export const visibleComputedRankingBySumDataObservable = ({ isCategorySeprate$, visibleComputedSumData$ }: {
374
- isCategorySeprate$: Observable<boolean>
375
- // visibleComputedData$: Observable<ComputedDatumMultiValue[][]>
376
- visibleComputedSumData$: Observable<ComputedDatumWithSumMultiValue[][]>
377
- }) => {
378
-
379
- return combineLatest({
380
- isCategorySeprate: isCategorySeprate$,
381
- visibleComputedSumData: visibleComputedSumData$
382
- }).pipe(
383
- switchMap(async d => d),
384
- map(data => {
385
- // -- category 分開 --
386
- if (data.isCategorySeprate) {
387
- return data.visibleComputedSumData
388
- .map(categoryData => {
389
- return categoryData
390
- .sort((a, b) => b.sum - a.sum)
391
- })
392
- // -- 用 value[index] 排序 --
393
- } else {
394
- return [
395
- data.visibleComputedSumData
396
- .flat()
397
- .sort((a, b) => b.sum - a.sum)
398
- ]
399
- }
400
- })
401
- )
402
- }
403
-
404
- export const visibleComputedXYDataObservable = ({ computedXYData$ }: { computedXYData$: Observable<ComputedXYDataMultiValue> }) => {
405
- return computedXYData$.pipe(
406
- map(data => {
407
- return data
408
- .map(categoryData => {
409
- return categoryData.filter(d => d.visible == true)
410
- })
411
- .filter(categoryData => {
412
- return categoryData.length > 0
413
- })
414
- })
415
- )
416
- }
417
-
418
- // 所有container位置(對應category)
419
- export const containerPositionObservable = ({ computedData$, fullDataFormatter$, layout$ }: {
420
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
421
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
422
- layout$: Observable<Layout>
423
- }): Observable<ContainerPositionScaled[]> => {
424
-
425
- const containerPosition$ = combineLatest({
426
- computedData: computedData$,
427
- fullDataFormatter: fullDataFormatter$,
428
- layout: layout$,
429
- }).pipe(
430
- switchMap(async (d) => d),
431
- map(data => {
432
-
433
- if (data.fullDataFormatter.separateCategory) {
434
- // -- 依slotIndexes計算 --
435
- return calcContainerPositionScaled(data.layout, data.fullDataFormatter.container, data.computedData.length)
436
- // return data.computedData.map((seriesData, seriesIndex) => {
437
- // const columnIndex = seriesIndex % data.fullDataFormatter.container.columnAmount
438
- // const rowIndex = Math.floor(seriesIndex / data.fullDataFormatter.container.columnAmount)
439
- // const { translate, scale } = calcMultiValueContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
440
- // return {
441
- // slotIndex: seriesIndex,
442
- // rowIndex,
443
- // columnIndex,
444
- // translate,
445
- // scale,
446
- // }
447
- // })
448
- } else {
449
- // -- 無拆分 --
450
- const containerPositionArr = calcContainerPositionScaled(data.layout, data.fullDataFormatter.container, 1)
451
- return data.computedData.map((d, i) => containerPositionArr[0]) // 每個series相同位置
452
- // const columnIndex = 0
453
- // const rowIndex = 0
454
- // return data.computedData.map((seriesData, seriesIndex) => {
455
- // const { translate, scale } = calcMultiValueContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
456
- // return {
457
- // slotIndex: 0,
458
- // rowIndex,
459
- // columnIndex,
460
- // translate,
461
- // scale,
462
- // }
463
- // })
464
- }
465
- })
466
- )
467
-
468
- return containerPosition$
469
- }
470
-
471
- // export const containerSizeObservable = ({ layout$, containerPosition$ }: {
472
- // layout$: Observable<Layout>
473
- // containerPosition$: Observable<ContainerPositionScaled[]>
474
- // }) => {
475
- // const rowAmount$ = containerPosition$.pipe(
476
- // map(containerPosition => {
477
- // const maxRowIndex = containerPosition.reduce((acc, current) => {
478
- // return current.rowIndex > acc ? current.rowIndex : acc
479
- // }, 0)
480
- // return maxRowIndex + 1
481
- // }),
482
- // distinctUntilChanged(),
483
- // )
484
-
485
- // const columnAmount$ = containerPosition$.pipe(
486
- // map(containerPosition => {
487
- // const maxColumnIndex = containerPosition.reduce((acc, current) => {
488
- // return current.columnIndex > acc ? current.columnIndex : acc
489
- // }, 0)
490
- // return maxColumnIndex + 1
491
- // }),
492
- // distinctUntilChanged()
493
- // )
494
-
495
- // return combineLatest({
496
- // layout: layout$,
497
- // rowAmount: rowAmount$,
498
- // columnAmount: columnAmount$
499
- // }).pipe(
500
- // switchMap(async (d) => d),
501
- // map(data => {
502
- // const width = (data.layout.rootWidth / data.columnAmount) - (data.layout.left + data.layout.right)
503
- // const height = (data.layout.rootHeight / data.rowAmount) - (data.layout.top + data.layout.bottom)
504
- // return {
505
- // width,
506
- // height
507
- // }
508
- // }),
509
- // distinctUntilChanged((a, b) => a.width === b.width && a.height === b.height),
510
- // shareReplay(1)
511
- // )
512
- // }
513
-
514
- export const filteredXYMinMaxDataObservable = ({ visibleComputedXYData$, xyMinMax$, xyValueIndex$, fullDataFormatter$ }: {
515
- visibleComputedXYData$: Observable<ComputedXYDataMultiValue>
516
- xyMinMax$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
517
- xyValueIndex$: Observable<[number, number]>
518
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
519
- }) => {
520
- return combineLatest({
521
- visibleComputedXYData: visibleComputedXYData$,
522
- xyMinMax: xyMinMax$,
523
- xyValueIndex: xyValueIndex$,
524
- fullDataFormatter: fullDataFormatter$,
525
- }).pipe(
526
- map(data => {
527
- // 所有可見資料依 dataFormatter 的 scale 設定篩選出最大小值
528
- const { minX, maxX, minY, maxY } = (() => {
529
-
530
- let { minX, maxX, minY, maxY } = data.xyMinMax
531
-
532
- if (data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' && minX > 0) {
533
- minX = 0
534
- } else if (typeof data.fullDataFormatter.xAxis.scaleDomain[0] === 'number') {
535
- minX = data.fullDataFormatter.xAxis.scaleDomain[0] as number
536
- }
537
- if (data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' && maxX < 0) {
538
- maxX = 0
539
- } else if (typeof data.fullDataFormatter.xAxis.scaleDomain[1] === 'number') {
540
- maxX = data.fullDataFormatter.xAxis.scaleDomain[1] as number
541
- }
542
- if (data.fullDataFormatter.yAxis.scaleDomain[0] === 'auto' && minY > 0) {
543
- minY = 0
544
- } else if (typeof data.fullDataFormatter.yAxis.scaleDomain[0] === 'number') {
545
- minY = data.fullDataFormatter.yAxis.scaleDomain[0] as number
546
- }
547
- if (data.fullDataFormatter.yAxis.scaleDomain[1] === 'auto' && maxY < 0) {
548
- maxY = 0
549
- } else if (typeof data.fullDataFormatter.yAxis.scaleDomain[1] === 'number') {
550
- maxY = data.fullDataFormatter.yAxis.scaleDomain[1] as number
551
- }
552
-
553
- return { minX, maxX, minY, maxY }
554
- })()
555
- // console.log({ minX, maxX, minY, maxY })
556
- let datumList: ComputedXYDatumMultiValue[] = []
557
- let minXDatum: ComputedXYDatumMultiValue | null = null
558
- let maxXDatum: ComputedXYDatumMultiValue | null = null
559
- let minYDatum: ComputedXYDatumMultiValue | null = null
560
- let maxYDatum: ComputedXYDatumMultiValue | null = null
561
- // console.log('data.visibleComputedXYData', data.visibleComputedXYData)
562
- // minX, maxX, minY, maxY 範圍內的最大最小值資料
563
- // console.log({ minX, maxX, minY, maxY })
564
- for (let categoryData of data.visibleComputedXYData) {
565
- for (let datum of categoryData) {
566
- const xValue = datum.value[data.xyValueIndex[0]]
567
- const yValue = datum.value[data.xyValueIndex[1]]
568
- // 比較矩形範圍(所以 minX, maxX, minY, maxY 要同時比較)
569
- if (xValue >= minX && xValue <= maxX && yValue >= minY && yValue <= maxY) {
570
- datumList.push(datum)
571
- if (minXDatum == null || xValue < minXDatum.value[data.xyValueIndex[0]]) {
572
- minXDatum = datum
573
- }
574
- if (maxXDatum == null || xValue > maxXDatum.value[data.xyValueIndex[0]]) {
575
- maxXDatum = datum
576
- }
577
- if (minYDatum == null || yValue < minYDatum.value[data.xyValueIndex[1]]) {
578
- minYDatum = datum
579
- }
580
- if (maxYDatum == null || yValue > maxYDatum.value[data.xyValueIndex[1]]) {
581
- maxYDatum = datum
582
- }
583
- }
584
- }
585
- }
586
-
587
- return {
588
- datumList,
589
- minXDatum,
590
- maxXDatum,
591
- minYDatum,
592
- maxYDatum
593
- }
594
- })
595
- )
596
- }
597
-
598
- // export const visibleComputedRankingDataObservable = ({ visibleComputedData$ }: {
599
- // visibleComputedData$: Observable<ComputedDatumMultiValue[][]>
600
- // }) => {
601
- // return visibleComputedData$.pipe(
602
- // map(visibleComputedData => visibleComputedData
603
- // .flat()
604
- // .map(d => {
605
- // // 新增總計資料欄位
606
- // ;(d as any)._sum = d.value.reduce((acc, curr) => acc + curr, 0)
607
- // return d
608
- // })
609
- // .sort((a: any, b: any) => b._sum - a._sum)
610
- // )
611
- // )
612
-
613
- // // const labelAmountLimit$ = combineLatest({
614
- // // layout: layout$,
615
- // // textSizePx: textSizePx$,
616
- // // sortedLabels: sortedLabels$
617
- // // }).pipe(
618
- // // switchMap(async (d) => d),
619
- // // map(data => {
620
- // // const lineHeight = data.textSizePx * 2 // 2倍行高
621
- // // const labelAmountLimit = Math.floor(data.layout.height / lineHeight)
622
- // // return labelAmountLimit
623
- // // }),
624
- // // distinctUntilChanged()
625
- // // )
626
-
627
- // // return combineLatest({
628
- // // sortedLabels: sortedLabels$,
629
- // // labelAmountLimit: labelAmountLimit$
630
- // // }).pipe(
631
- // // map(data => {
632
- // // return data.sortedLabels.slice(0, data.labelAmountLimit)
633
- // // })
634
- // // )
635
-
636
- // }
637
-
638
- // export const rankingAmountLimitObservable = ({ layout$, textSizePx$ }: {
639
- // layout$: Observable<Layout>
640
- // textSizePx$: Observable<number>
641
- // }) => {
642
- // return combineLatest({
643
- // layout: layout$,
644
- // textSizePx: textSizePx$
645
- // }).pipe(
646
- // switchMap(async (d) => d),
647
- // map(data => {
648
- // const lineHeight = data.textSizePx * 2 // 2倍行高
649
- // const labelAmountLimit = Math.floor(data.layout.height / lineHeight)
650
- // return labelAmountLimit
651
- // }),
652
- // distinctUntilChanged()
653
- // )
654
- // }
655
-
656
- // export const rankingScaleObservable = ({ layout$, visibleComputedRankingData$, rankingAmountLimit$ }: {
657
- // layout$: Observable<Layout>
658
- // visibleComputedRankingData$: Observable<ComputedDatumMultiValue[]>
659
- // rankingAmountLimit$: Observable<number>
660
- // }) => {
661
- // return combineLatest({
662
- // layout: layout$,
663
- // rankingAmountLimit: rankingAmountLimit$,
664
- // visibleComputedRankingData: visibleComputedRankingData$,
665
- // }).pipe(
666
- // switchMap(async (d) => d),
667
- // map(data => {
668
- // let labelAmount = 0
669
- // let lineHeight = 0
670
- // let totalHeight = 0
671
- // if (data.visibleComputedRankingData.length > data.rankingAmountLimit) {
672
- // labelAmount = data.rankingAmountLimit
673
- // lineHeight = data.layout.height / labelAmount
674
- // totalHeight = lineHeight * labelAmount // 用全部的數量來算而不是要顯示的數量(要超出圖軸高度)
675
- // } else {
676
- // labelAmount = data.visibleComputedRankingData.length
677
- // lineHeight = data.layout.height / labelAmount
678
- // totalHeight = data.layout.height
679
- // }
680
-
681
- // return createLabelToAxisScale({
682
- // axisLabels: data.visibleComputedRankingData.map(d => d.label),
683
- // axisWidth: totalHeight,
684
- // padding: 0.5
685
- // })
686
- // })
687
- // )
688
- // }
689
-
690
- export const graphicTransformObservable = ({ xyMinMax$, xyValueIndex$, filteredXYMinMaxData$, fullDataFormatter$, layout$ }: {
691
- xyMinMax$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
692
- xyValueIndex$: Observable<[number, number]>
693
- filteredXYMinMaxData$: Observable<{
694
- minXDatum: ComputedXYDatumMultiValue
695
- maxXDatum: ComputedXYDatumMultiValue
696
- minYDatum: ComputedXYDatumMultiValue
697
- maxYDatum: ComputedXYDatumMultiValue
698
- }>
699
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
700
- layout$: Observable<Layout>
701
- }): Observable<TransformData> => {
702
- const destroy$ = new Subject()
703
-
704
- function calcDataAreaTransform ({ xyMinMax, xyValueIndex, filteredXYMinMaxData, xAxis, yAxis, width, height }: {
705
- xyMinMax: { minX: number, maxX: number, minY: number, maxY: number }
706
- xyValueIndex: [number, number]
707
- filteredXYMinMaxData: {
708
- minXDatum: ComputedXYDatumMultiValue
709
- maxXDatum: ComputedXYDatumMultiValue
710
- minYDatum: ComputedXYDatumMultiValue
711
- maxYDatum: ComputedXYDatumMultiValue
712
- }
713
- xAxis: DataFormatterXYAxis
714
- yAxis: DataFormatterXYAxis
715
- width: number
716
- height: number
717
- }): TransformData {
718
- // const flatData = data.flat()
719
-
720
- let translateX = 0
721
- let translateY = 0
722
- let scaleX = 0
723
- let scaleY = 0
724
-
725
- // // minX, maxX, filteredMinX, filteredMaxX
726
- // let filteredMinX = 0
727
- // let filteredMaxX = 0
728
- // let [minX, maxX] = getMinMax(flatData.map(d => d.value[0]))
729
- // if (minX === maxX) {
730
- // minX = maxX - 1 // 避免最大及最小值相同造成無法計算scale
731
- // }
732
- // if (xAxis.scaleDomain[0] === 'auto' && filteredMinX > 0) {
733
- // filteredMinX = 0
734
- // } else if (typeof xAxis.scaleDomain[0] === 'number') {
735
- // filteredMinX = xAxis.scaleDomain[0] as number
736
- // } else {
737
- // filteredMinX = minX
738
- // }
739
- // if (xAxis.scaleDomain[1] === 'auto' && filteredMaxX < 0) {
740
- // filteredMaxX = 0
741
- // } else if (typeof xAxis.scaleDomain[1] === 'number') {
742
- // filteredMaxX = xAxis.scaleDomain[1] as number
743
- // } else {
744
- // filteredMaxX = maxX
745
- // }
746
- // if (filteredMinX === filteredMaxX) {
747
- // filteredMinX = filteredMaxX - 1 // 避免最大及最小值相同造成無法計算scale
748
- // }
749
-
750
- // // minY, maxY, filteredMinY, filteredMaxY
751
- // let filteredMinY = 0
752
- // let filteredMaxY = 0
753
- // let [minY, maxY] = getMinMax(flatData.map(d => d.value[1]))
754
- // console.log('filteredXYMinMaxData', filteredXYMinMaxData)
755
- let { minX, maxX, minY, maxY } = xyMinMax
756
- // console.log({ minX, maxX, minY, maxY })
757
- let filteredMinX = filteredXYMinMaxData.minXDatum.value[xyValueIndex[0]] ?? 0
758
- let filteredMaxX = filteredXYMinMaxData.maxXDatum.value[xyValueIndex[0]] ?? 0
759
- let filteredMinY = filteredXYMinMaxData.minYDatum.value[xyValueIndex[1]] ?? 0
760
- let filteredMaxY = filteredXYMinMaxData.maxYDatum.value[xyValueIndex[1]] ?? 0
761
-
762
- // if (yAxis.scaleDomain[0] === 'auto' && filteredMinY > 0) {
763
- // filteredMinY = 0
764
- // } else if (typeof yAxis.scaleDomain[0] === 'number') {
765
- // filteredMinY = yAxis.scaleDomain[0] as number
766
- // } else {
767
- // filteredMinY = minY
768
- // }
769
- // if (yAxis.scaleDomain[1] === 'auto' && filteredMaxY < 0) {
770
- // filteredMaxY = 0
771
- // } else if (typeof yAxis.scaleDomain[1] === 'number') {
772
- // filteredMaxY = yAxis.scaleDomain[1] as number
773
- // } else {
774
- // filteredMaxY = maxY
775
- // }
776
-
777
- // console.log({ minX, maxX, minY, maxY, filteredMinX, filteredMaxX, filteredMinY, filteredMaxY })
778
- if (filteredMinX === filteredMaxX && filteredMaxX === 0) {
779
- // 避免最大及最小值相同造成無法計算scale
780
- filteredMaxX = 1
781
- }
782
- if (filteredMinY === filteredMaxY && filteredMaxY === 0) {
783
- // 避免最大及最小值相同造成無法計算scale
784
- filteredMaxY = 1
785
- }
786
- if (minX === maxX && maxX === 0) {
787
- // 避免最大及最小值相同造成無法計算scale
788
- maxX = 1
789
- }
790
- if (minY === maxY && maxY === 0) {
791
- // 避免最大及最小值相同造成無法計算scale
792
- maxY = 1
793
- }
794
- // -- xScale --
795
- const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
796
- maxValue: filteredMaxX,
797
- minValue: filteredMinX,
798
- axisWidth: width,
799
- scaleDomain: xAxis.scaleDomain,
800
- scaleRange: xAxis.scaleRange
801
- })
802
-
803
- // -- translateX, scaleX --
804
- const rangeMinX = xScale(minX > 0 ? 0 : minX) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
805
- const rangeMaxX = xScale(maxX < 0 ? 0 : maxX) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
806
- translateX = rangeMinX
807
- const gWidth = rangeMaxX - rangeMinX
808
- scaleX = gWidth / width
809
- // console.log({ gWidth, width, rangeMaxX, rangeMinX, scaleX, translateX })
810
- // -- yScale --
811
- const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
812
- maxValue: filteredMaxY,
813
- minValue: filteredMinY,
814
- axisWidth: height,
815
- scaleDomain: yAxis.scaleDomain,
816
- scaleRange: yAxis.scaleRange,
817
- reverse: true
818
- })
819
-
820
- // -- translateY, scaleY --
821
- const rangeMinY = yScale(minY > 0 ? 0 : minY) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
822
- const rangeMaxY = yScale(maxY < 0 ? 0 : maxY) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
823
- translateY = rangeMaxY // 最大值的 y 最小(最上方)
824
- const gHeight = rangeMinY - rangeMaxY // 最大的 y 減最小的 y
825
- scaleY = gHeight / height
826
-
827
- return {
828
- translate: [translateX, translateY],
829
- scale: [scaleX, scaleY],
830
- rotate: 0,
831
- rotateX: 0,
832
- rotateY: 0,
833
- value: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`
834
- }
835
- }
836
-
837
- return new Observable(subscriber => {
838
- combineLatest({
839
- xyMinMax: xyMinMax$,
840
- xyValueIndex: xyValueIndex$,
841
- filteredXYMinMaxData: filteredXYMinMaxData$,
842
- fullDataFormatter: fullDataFormatter$,
843
- layout: layout$
844
- }).pipe(
845
- takeUntil(destroy$),
846
- switchMap(async (d) => d),
847
- ).subscribe(data => {
848
- if (!data.filteredXYMinMaxData.minXDatum || !data.filteredXYMinMaxData.maxXDatum
849
- || data.filteredXYMinMaxData.minXDatum.value[data.xyValueIndex[0]] == null || data.filteredXYMinMaxData.maxXDatum.value[data.xyValueIndex[0]] == null
850
- || !data.filteredXYMinMaxData.minYDatum || !data.filteredXYMinMaxData.maxYDatum
851
- || data.filteredXYMinMaxData.minYDatum.value[data.xyValueIndex[1]] == null || data.filteredXYMinMaxData.maxYDatum.value[data.xyValueIndex[1]] == null
852
- ) {
853
- return
854
- }
855
- const dataAreaTransformData = calcDataAreaTransform({
856
- xyMinMax: data.xyMinMax,
857
- xyValueIndex: data.xyValueIndex,
858
- filteredXYMinMaxData: data.filteredXYMinMaxData,
859
- xAxis: data.fullDataFormatter.xAxis,
860
- yAxis: data.fullDataFormatter.yAxis,
861
- width: data.layout.width,
862
- height: data.layout.height
863
- })
864
-
865
- // console.log('dataAreaTransformData', dataAreaTransformData)
866
-
867
- subscriber.next(dataAreaTransformData)
868
- })
869
-
870
- return function unscbscribe () {
871
- destroy$.next(undefined)
872
- }
873
- })
874
- }
875
-
876
- export const graphicReverseScaleObservable = ({ containerPosition$, graphicTransform$ }: {
877
- containerPosition$: Observable<ContainerPositionScaled[]>
878
- // multiValueAxesTransform$: Observable<TransformData>
879
- graphicTransform$: Observable<TransformData>
880
- }): Observable<[number, number][]> => {
881
- return combineLatest({
882
- containerPosition: containerPosition$,
883
- // multiValueAxesTransform: multiValueAxesTransform$,
884
- graphicTransform: graphicTransform$,
885
- }).pipe(
886
- switchMap(async (d) => d),
887
- map(data => {
888
- // if (data.multiValueAxesTransform.rotate == 0 || data.multiValueAxesTransform.rotate == 180) {
889
- return data.containerPosition.map((series, seriesIndex) => {
890
- return [
891
- 1 / data.graphicTransform.scale[0] / data.containerPosition[seriesIndex].scale[0],
892
- 1 / data.graphicTransform.scale[1] / data.containerPosition[seriesIndex].scale[1],
893
- ]
894
- })
895
- // } else {
896
- // return data.containerPosition.map((series, seriesIndex) => {
897
- // // 由於有垂直的旋轉,所以外層 (container) x和y的scale要互換
898
- // return [
899
- // 1 / data.graphicTransform.scale[0] / data.containerPosition[seriesIndex].scale[1],
900
- // 1 / data.graphicTransform.scale[1] / data.containerPosition[seriesIndex].scale[0],
901
- // ]
902
- // })
903
- // }
904
- }),
905
- )
906
- }
907
-
908
- // X 軸圖軸 - 用 value[index]
909
- export const xScaleObservable = ({ visibleComputedSumData$, fullDataFormatter$, filteredXYMinMaxData$, containerSize$ }: {
910
- visibleComputedSumData$: Observable<ComputedDatumMultiValue[][]>
911
- fullDataFormatter$: Observable<DataFormatterMultiValue>
912
- filteredXYMinMaxData$: Observable<{
913
- minXDatum: ComputedXYDatumMultiValue
914
- maxXDatum: ComputedXYDatumMultiValue
915
- minYDatum: ComputedXYDatumMultiValue
916
- maxYDatum: ComputedXYDatumMultiValue
917
- }>
918
- // layout$: Observable<Layout>
919
- containerSize$: Observable<ContainerSize>
920
- }) => {
921
- return combineLatest({
922
- visibleComputedSumData: visibleComputedSumData$,
923
- fullDataFormatter: fullDataFormatter$,
924
- containerSize: containerSize$,
925
- // xyMinMax: xyMinMax$
926
- filteredXYMinMaxData: filteredXYMinMaxData$
927
- }).pipe(
928
- switchMap(async (d) => d),
929
- map(data => {
930
- const valueIndex = data.fullDataFormatter.xAxis.valueIndex
931
- if (!data.filteredXYMinMaxData.minXDatum || !data.filteredXYMinMaxData.maxXDatum
932
- // || data.filteredXYMinMaxData.minXDatum.value[valueIndex] == null || data.filteredXYMinMaxData.maxXDatum.value[valueIndex] == null
933
- ) {
934
- return
935
- }
936
- let maxValue: number | null = data.filteredXYMinMaxData.maxXDatum.value[valueIndex]
937
- let minValue: number | null = data.filteredXYMinMaxData.minXDatum.value[valueIndex]
938
- if (maxValue === minValue && maxValue === 0) {
939
- // 避免最大及最小值同等於 0 造成無法計算scale
940
- maxValue = 1
941
- }
942
-
943
- const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
944
- maxValue,
945
- minValue,
946
- axisWidth: data.containerSize.width,
947
- scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
948
- scaleRange: data.fullDataFormatter.xAxis.scaleRange,
949
- })
950
- return xScale
951
- })
952
- )
953
- }
954
-
955
- // X 軸圖軸 - 用所有 valueIndex 加總資料
956
- export const xSumScaleObservable = ({ fullDataFormatter$, filteredXYMinMaxData$, containerSize$ }: {
957
- // valueIndex$: Observable<number>
958
- fullDataFormatter$: Observable<DataFormatterMultiValue>
959
- filteredXYMinMaxData$: Observable<{
960
- minXDatum: ComputedXYDatumMultiValue
961
- maxXDatum: ComputedXYDatumMultiValue
962
- minYDatum: ComputedXYDatumMultiValue
963
- maxYDatum: ComputedXYDatumMultiValue
964
- }>
965
- // layout$: Observable<Layout>
966
- containerSize$: Observable<ContainerSize>
967
- }) => {
968
- return combineLatest({
969
- // valueIndex: valueIndex$,
970
- fullDataFormatter: fullDataFormatter$,
971
- containerSize: containerSize$,
972
- // xyMinMax: xyMinMax$
973
- filteredXYMinMaxData: filteredXYMinMaxData$
974
- }).pipe(
975
- switchMap(async (d) => d),
976
- map(data => {
977
- const valueIndex = data.fullDataFormatter.xAxis.valueIndex
978
- if (!data.filteredXYMinMaxData.minXDatum || !data.filteredXYMinMaxData.maxXDatum
979
- // || data.filteredXYMinMaxData.minXDatum.value[valueIndex] == null || data.filteredXYMinMaxData.maxXDatum.value[valueIndex] == null
980
- ) {
981
- return
982
- }
983
- let maxValue: number | null = data.filteredXYMinMaxData.maxXDatum.value[valueIndex]
984
- let minValue: number | null = data.filteredXYMinMaxData.minXDatum.value[valueIndex]
985
- if (maxValue === minValue && maxValue === 0) {
986
- // 避免最大及最小值同等於 0 造成無法計算scale
987
- maxValue = 1
988
- }
989
-
990
- const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
991
- maxValue,
992
- minValue,
993
- axisWidth: data.containerSize.width,
994
- scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
995
- scaleRange: data.fullDataFormatter.xAxis.scaleRange,
996
- })
997
- return xScale
998
- })
999
- )
1000
- }
1001
-
1002
- export const yScaleObservable = ({ fullDataFormatter$, filteredXYMinMaxData$, containerSize$ }: {
1003
- fullDataFormatter$: Observable<DataFormatterMultiValue>
1004
- filteredXYMinMaxData$: Observable<{
1005
- minXDatum: ComputedXYDatumMultiValue
1006
- maxXDatum: ComputedXYDatumMultiValue
1007
- minYDatum: ComputedXYDatumMultiValue
1008
- maxYDatum: ComputedXYDatumMultiValue
1009
- }>
1010
- containerSize$: Observable<ContainerSize>
1011
- }) => {
1012
- return combineLatest({
1013
- fullDataFormatter: fullDataFormatter$,
1014
- containerSize: containerSize$,
1015
- // xyMinMax: observer.xyMinMax$
1016
- filteredXYMinMaxData: filteredXYMinMaxData$
1017
- }).pipe(
1018
- switchMap(async (d) => d),
1019
- map(data => {
1020
- const valueIndex = data.fullDataFormatter.yAxis.valueIndex
1021
- if (!data.filteredXYMinMaxData.minYDatum || !data.filteredXYMinMaxData.maxYDatum
1022
- || data.filteredXYMinMaxData.minYDatum.value[valueIndex] == null || data.filteredXYMinMaxData.maxYDatum.value[valueIndex] == null
1023
- ) {
1024
- return
1025
- }
1026
- let maxValue = data.filteredXYMinMaxData.maxYDatum.value[valueIndex]
1027
- let minValue = data.filteredXYMinMaxData.minYDatum.value[valueIndex]
1028
- if (maxValue === minValue && maxValue === 0) {
1029
- // 避免最大及最小值同等於 0 造成無法計算scale
1030
- maxValue = 1
1031
- }
1032
-
1033
- const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
1034
- maxValue,
1035
- minValue,
1036
- axisWidth: data.containerSize.height,
1037
- scaleDomain: data.fullDataFormatter.yAxis.scaleDomain,
1038
- scaleRange: data.fullDataFormatter.yAxis.scaleRange,
1039
- reverse: true
1040
- })
1041
- return yScale
1042
- })
1043
- )
1044
- }
1
+ import {
2
+ combineLatest,
3
+ distinctUntilChanged,
4
+ iif,
5
+ filter,
6
+ map,
7
+ merge,
8
+ takeUntil,
9
+ shareReplay,
10
+ switchMap,
11
+ Subject,
12
+ Observable } from 'rxjs'
13
+ import type {
14
+ AxisPosition,
15
+ ChartType,
16
+ ChartParams,
17
+ ComputedDataTypeMap,
18
+ ComputedDatumTypeMap,
19
+ ComputedDataMultiValue,
20
+ ComputedDatumMultiValue,
21
+ ComputedDatumWithSumMultiValue,
22
+ ContainerSize,
23
+ DataFormatterTypeMap,
24
+ DataFormatterMultiValue,
25
+ DataFormatterXYAxis,
26
+ ComputedXYDatumMultiValue,
27
+ ComputedXYDataMultiValue,
28
+ ContainerPositionScaled,
29
+ HighlightTarget,
30
+ Layout,
31
+ TransformData } from '../../lib/core-types'
32
+ import { getMinMax, createDefaultValueLabel } from '../utils/orbchartsUtils'
33
+ import { createValueToAxisScale, createLabelToAxisScale, createAxisToLabelIndexScale } from '../utils/d3Scale'
34
+ import { calcContainerPositionScaled } from '../utils/orbchartsUtils'
35
+
36
+ export const valueLabelsObservable = ({ computedData$, fullDataFormatter$ }: {
37
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
38
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
39
+ }) => {
40
+ return combineLatest({
41
+ computedData: computedData$,
42
+ fullDataFormatter: fullDataFormatter$,
43
+ }).pipe(
44
+ map(data => {
45
+ return data.computedData[0] && data.computedData[0][0] && data.computedData[0][0].value.length
46
+ ? data.computedData[0][0].value.map((d, i) => data.fullDataFormatter.valueLabels[i] ?? createDefaultValueLabel('multiValue', i))
47
+ : []
48
+ }),
49
+ )
50
+ }
51
+
52
+ export const xyMinMaxObservable = ({ computedData$, xyValueIndex$ }: {
53
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
54
+ xyValueIndex$: Observable<[number, number]>
55
+ }) => {
56
+ return combineLatest({
57
+ computedData: computedData$,
58
+ xyValueIndex: xyValueIndex$,
59
+ }).pipe(
60
+ map(data => {
61
+ const flatData = data.computedData.flat()
62
+ const [minX, maxX] = getMinMax(flatData.map(d => d.value[data.xyValueIndex[0]]))
63
+ const [minY, maxY] = getMinMax(flatData.map(d => d.value[data.xyValueIndex[1]]))
64
+ return { minX, maxX, minY, maxY }
65
+ })
66
+ )
67
+ }
68
+
69
+ export const computedXYDataObservable = ({ computedData$, xyMinMax$, xyValueIndex$, fullDataFormatter$, layout$ }: {
70
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
71
+ xyMinMax$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
72
+ xyValueIndex$: Observable<[number, number]>
73
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
74
+ layout$: Observable<Layout>
75
+ }): Observable<ComputedXYDataMultiValue> => {
76
+
77
+ // 未篩選範圍前的 scale
78
+ function createOriginXScale (xyMinMax: { minX: number, maxX: number, minY: number, maxY: number }, layout: Layout) {
79
+ let maxValue = xyMinMax.maxX
80
+ let minValue = xyMinMax.minX
81
+ if (minValue === maxValue && maxValue === 0) {
82
+ // 避免最大及最小值相同造成無法計算scale
83
+ maxValue = 1
84
+ }
85
+ const valueScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
86
+ maxValue,
87
+ minValue,
88
+ axisWidth: layout.width,
89
+ scaleDomain: ['auto', 'auto'], // 不使用dataFormatter設定 --> 以0為基準到最大或最小值為範圍( * 如果是使用[minValue, maxValue]的話,在兩者很接近的情況下有可能造成scale倍率過高而svg變型時失真的情況)
90
+ scaleRange: [0, 1] // 不使用dataFormatter設定
91
+ })
92
+
93
+ return valueScale
94
+ }
95
+
96
+ // 未篩選範圍及visible前的 scale
97
+ function createOriginYScale (xyMinMax: { minX: number, maxX: number, minY: number, maxY: number }, layout: Layout) {
98
+ let maxValue = xyMinMax.maxY
99
+ let minValue = xyMinMax.minY
100
+ if (minValue === maxValue && maxValue === 0) {
101
+ // 避免最大及最小值相同造成無法計算scale
102
+ maxValue = 1
103
+ }
104
+ const valueScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
105
+ maxValue,
106
+ minValue,
107
+ axisWidth: layout.height,
108
+ scaleDomain: ['auto', 'auto'], // 不使用dataFormatter設定 --> 以0為基準到最大或最小值為範圍( * 如果是使用[minValue, maxValue]的話,在兩者很接近的情況下有可能造成scale倍率過高而svg變型時失真的情況)
109
+ scaleRange: [0, 1], // 不使用dataFormatter設定
110
+ reverse: true
111
+ })
112
+
113
+ return valueScale
114
+ }
115
+
116
+ return combineLatest({
117
+ computedData: computedData$,
118
+ xyMinMax: xyMinMax$,
119
+ xyValueIndex: xyValueIndex$,
120
+ fullDataFormatter: fullDataFormatter$,
121
+ layout: layout$
122
+ }).pipe(
123
+ switchMap(async d => d),
124
+ map(data => {
125
+
126
+ const xScale = createOriginXScale(data.xyMinMax, data.layout)
127
+ const yScale = createOriginYScale(data.xyMinMax, data.layout)
128
+
129
+ return data.computedData
130
+ .map((categoryData, categoryIndex) => {
131
+ return categoryData.map((datum, datumIndex) => {
132
+ return {
133
+ ...datum,
134
+ axisX: xScale(datum.value[data.xyValueIndex[0]] ?? 0),
135
+ // axisY: data.layout.height - yScale(datum.value[1] ?? 0), // y軸的繪圖座標是從上到下,所以反轉
136
+ axisY: yScale(datum.value[data.xyValueIndex[1]] ?? 0), // y軸的繪圖座標是從上到下,所以反轉
137
+ }
138
+ })
139
+ })
140
+ })
141
+ )
142
+ }
143
+
144
+ // export const multiValueAxesTransformObservable = ({ fullDataFormatter$, layout$ }: {
145
+ // fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
146
+ // layout$: Observable<Layout>
147
+ // }): Observable<TransformData> => {
148
+ // const destroy$ = new Subject()
149
+
150
+ // function calcAxesTransform ({ xAxis, yAxis, width, height }: {
151
+ // xAxis: DataFormatterXYAxis,
152
+ // yAxis: DataFormatterXYAxis,
153
+ // width: number,
154
+ // height: number
155
+ // }): TransformData {
156
+ // if (!xAxis || !yAxis) {
157
+ // return {
158
+ // translate: [0, 0],
159
+ // scale: [1, 1],
160
+ // rotate: 0,
161
+ // rotateX: 0,
162
+ // rotateY: 0,
163
+ // value: ''
164
+ // }
165
+ // }
166
+ // // const width = size.width - fullChartParams.layout.left - fullChartParams.layout.right
167
+ // // const height = size.height - fullChartParams.layout.top - fullChartParams.layout.bottom
168
+ // let translateX = 0
169
+ // let translateY = height
170
+ // let rotate = 0
171
+ // let rotateX = 180
172
+ // let rotateY = 0
173
+
174
+ // return {
175
+ // translate: [translateX, translateY],
176
+ // scale: [1, 1],
177
+ // rotate,
178
+ // rotateX,
179
+ // rotateY,
180
+ // value: `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
181
+ // }
182
+ // }
183
+
184
+ // return new Observable(subscriber => {
185
+ // combineLatest({
186
+ // fullDataFormatter: fullDataFormatter$,
187
+ // layout: layout$
188
+ // }).pipe(
189
+ // takeUntil(destroy$),
190
+ // switchMap(async (d) => d),
191
+ // ).subscribe(data => {
192
+ // const axesTransformData = calcAxesTransform({
193
+ // xAxis: data.fullDataFormatter.xAxis,
194
+ // yAxis: data.fullDataFormatter.yAxis,
195
+ // width: data.layout.width,
196
+ // height: data.layout.height
197
+ // })
198
+
199
+ // subscriber.next(axesTransformData)
200
+ // })
201
+
202
+ // return function unscbscribe () {
203
+ // destroy$.next(undefined)
204
+ // }
205
+ // })
206
+ // }
207
+
208
+
209
+ // export const multiValueAxesReverseTransformObservable = ({ multiValueAxesTransform$ }: {
210
+ // multiValueAxesTransform$: Observable<TransformData>
211
+ // }): Observable<TransformData> => {
212
+ // return multiValueAxesTransform$.pipe(
213
+ // map(d => {
214
+ // // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
215
+ // const translate: [number, number] = [0, 0] // 無需逆轉
216
+ // const scale: [number, number] = [1 / d.scale[0], 1 / d.scale[1]]
217
+ // const rotate = d.rotate * -1
218
+ // const rotateX = d.rotateX * -1
219
+ // const rotateY = d.rotateY * -1
220
+ // return {
221
+ // translate,
222
+ // scale,
223
+ // rotate,
224
+ // rotateX,
225
+ // rotateY,
226
+ // value: `translate(${translate[0]}px, ${translate[1]}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotate(${rotate}deg)`
227
+ // }
228
+ // }),
229
+ // )
230
+ // }
231
+
232
+
233
+
234
+ // export const multiValueAxesSizeObservable = ({ fullDataFormatter$, layout$ }: {
235
+ // fullDataFormatter$: Observable<DataFormatterMultiValue>
236
+ // layout$: Observable<Layout>
237
+ // }): Observable<{
238
+ // width: number;
239
+ // height: number;
240
+ // }> => {
241
+ // const destroy$ = new Subject()
242
+
243
+ // function calcAxesSize ({ xAxisPosition, yAxisPosition, width, height }: {
244
+ // xAxisPosition: AxisPosition
245
+ // yAxisPosition: AxisPosition
246
+ // width: number
247
+ // height: number
248
+ // }) {
249
+ // if ((xAxisPosition === 'bottom' || xAxisPosition === 'top') && (yAxisPosition === 'left' || yAxisPosition === 'right')) {
250
+ // return { width, height }
251
+ // } else if ((xAxisPosition === 'left' || xAxisPosition === 'right') && (yAxisPosition === 'bottom' || yAxisPosition === 'top')) {
252
+ // return {
253
+ // width: height,
254
+ // height: width
255
+ // }
256
+ // } else {
257
+ // // default
258
+ // return { width, height }
259
+ // }
260
+ // }
261
+
262
+ // return new Observable(subscriber => {
263
+ // combineLatest({
264
+ // fullDataFormatter: fullDataFormatter$,
265
+ // layout: layout$
266
+ // }).pipe(
267
+ // takeUntil(destroy$),
268
+ // switchMap(async (d) => d),
269
+ // ).subscribe(data => {
270
+
271
+ // const axisSize = calcAxesSize({
272
+ // xAxisPosition: 'bottom',
273
+ // yAxisPosition: 'left',
274
+ // width: data.layout.width,
275
+ // height: data.layout.height,
276
+ // })
277
+
278
+ // subscriber.next(axisSize)
279
+
280
+ // return function unsubscribe () {
281
+ // destroy$.next(undefined)
282
+ // }
283
+ // })
284
+ // })
285
+ // }
286
+
287
+ // export const highlightObservable = ({ computedData$, fullChartParams$, event$ }: {
288
+ // computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
289
+ // fullChartParams$: Observable<ChartParams>
290
+ // event$: Subject<any>
291
+ // }): Observable<string[]> => {
292
+ // const datumList$ = computedData$.pipe(
293
+ // map(d => d.flat())
294
+ // )
295
+ // return highlightObservable ({ datumList$, fullChartParams$, event$ })
296
+ // }
297
+
298
+ export const categoryLabelsObservable = ({ computedData$, fullDataFormatter$ }: {
299
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
300
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
301
+ }) => {
302
+ return computedData$.pipe(
303
+ map(data => {
304
+ return data
305
+ .map(d => d[0] ? d[0].categoryLabel : '')
306
+ // .filter(d => d != null && d != '')
307
+ }),
308
+ distinctUntilChanged((a, b) => {
309
+ return JSON.stringify(a).length === JSON.stringify(b).length
310
+ }),
311
+ )
312
+ }
313
+
314
+ export const visibleComputedDataObservable = ({ computedData$ }: { computedData$: Observable<ComputedDataTypeMap<'multiValue'>> }) => {
315
+ return computedData$.pipe(
316
+ map(data => {
317
+ return data
318
+ .map(categoryData => {
319
+ return categoryData.filter(d => d.visible == true)
320
+ })
321
+ .filter(categoryData => {
322
+ return categoryData.length > 0
323
+ })
324
+ })
325
+ )
326
+ }
327
+
328
+ export const ordinalScaleDomainObservable = ({ visibleComputedData$, fullDataFormatter$ }: {
329
+ visibleComputedData$: Observable<ComputedDataMultiValue>
330
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
331
+ }) => {
332
+ return combineLatest({
333
+ visibleComputedData: visibleComputedData$,
334
+ fullDataFormatter: fullDataFormatter$,
335
+ }).pipe(
336
+ map(data => {
337
+ let maxValue: number = data.visibleComputedData[0] && data.visibleComputedData[0][0] && data.visibleComputedData[0][0].value.length
338
+ ? data.visibleComputedData[0][0].value.length - 1
339
+ : 0
340
+ const scaleDomain: [number, number] = [
341
+ data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[0] === 'min'
342
+ ? 0
343
+ : data.fullDataFormatter.xAxis.scaleDomain[0] as number,
344
+ data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[1] === 'max'
345
+ ? maxValue
346
+ : data.fullDataFormatter.xAxis.scaleDomain[1] as number
347
+ ]
348
+ return scaleDomain
349
+ }),
350
+ distinctUntilChanged((a, b) => a[0] === b[0] && a[1] === b[1])
351
+ )
352
+ }
353
+
354
+ export const visibleComputedSumDataObservable = ({ visibleComputedData$, ordinalScaleDomain$ }: {
355
+ visibleComputedData$: Observable<ComputedDataMultiValue>
356
+ // fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
357
+ ordinalScaleDomain$: Observable<[number, number]>
358
+ }) => {
359
+
360
+ return combineLatest({
361
+ visibleComputedData: visibleComputedData$,
362
+ ordinalScaleDomain: ordinalScaleDomain$,
363
+ }).pipe(
364
+ map(data => {
365
+ return data.visibleComputedData.map(categoryData => {
366
+ return categoryData
367
+ .map((d, i) => {
368
+ let newDatum = d as ComputedDatumWithSumMultiValue
369
+ // 新增總計資料欄位
370
+ newDatum.sum = newDatum.value
371
+ // 只加總範圍內的
372
+ .filter((d, i) => i >= data.ordinalScaleDomain[0] && i <= data.ordinalScaleDomain[1])
373
+ .reduce((acc, curr) => acc + curr, 0)
374
+ return newDatum
375
+ })
376
+ })
377
+ })
378
+ )
379
+ }
380
+
381
+ // Ranking資料 - 用 value[index] 排序
382
+ export const visibleComputedRankingByIndexDataObservable = ({ xyValueIndex$, isCategorySeprate$, visibleComputedData$ }: {
383
+ xyValueIndex$: Observable<[number, number]>
384
+ isCategorySeprate$: Observable<boolean>
385
+ visibleComputedData$: Observable<ComputedDatumMultiValue[][]>
386
+ }) => {
387
+
388
+ return combineLatest({
389
+ isCategorySeprate: isCategorySeprate$,
390
+ xyValueIndex: xyValueIndex$,
391
+ visibleComputedData: visibleComputedData$
392
+ }).pipe(
393
+ switchMap(async d => d),
394
+ map(data => {
395
+ const xValueIndex = data.xyValueIndex[0]
396
+ // -- category 分開 --
397
+ if (data.isCategorySeprate) {
398
+ return data.visibleComputedData
399
+ .map(categoryData => {
400
+ return categoryData
401
+ .sort((a, b) => {
402
+ const bValue = b.value[xValueIndex] ?? - Infinity // - Infinity 為最小值
403
+ const aValue = a.value[xValueIndex] ?? - Infinity
404
+
405
+ return bValue - aValue
406
+ })
407
+ })
408
+ // -- 用 value[index] 排序 --
409
+ } else {
410
+ return [
411
+ data.visibleComputedData
412
+ .flat()
413
+ .sort((a, b) => {
414
+ const bValue = b.value[xValueIndex] ?? - Infinity // - Infinity 為最小值
415
+ const aValue = a.value[xValueIndex] ?? - Infinity
416
+
417
+ return bValue - aValue
418
+ })
419
+ ]
420
+ }
421
+ })
422
+ )
423
+ }
424
+
425
+ // Ranking資料 - 用所有 valueIndex 加總資料排序
426
+ export const visibleComputedRankingBySumDataObservable = ({ isCategorySeprate$, visibleComputedSumData$ }: {
427
+ isCategorySeprate$: Observable<boolean>
428
+ // visibleComputedData$: Observable<ComputedDatumMultiValue[][]>
429
+ visibleComputedSumData$: Observable<ComputedDatumWithSumMultiValue[][]>
430
+ }) => {
431
+
432
+ return combineLatest({
433
+ isCategorySeprate: isCategorySeprate$,
434
+ visibleComputedSumData: visibleComputedSumData$
435
+ }).pipe(
436
+ switchMap(async d => d),
437
+ map(data => {
438
+ // -- category 分開 --
439
+ if (data.isCategorySeprate) {
440
+ return data.visibleComputedSumData
441
+ .map(categoryData => {
442
+ return categoryData
443
+ .sort((a, b) => b.sum - a.sum)
444
+ })
445
+ // -- 用 value[index] 排序 --
446
+ } else {
447
+ return [
448
+ data.visibleComputedSumData
449
+ .flat()
450
+ .sort((a, b) => b.sum - a.sum)
451
+ ]
452
+ }
453
+ })
454
+ )
455
+ }
456
+
457
+ export const visibleComputedXYDataObservable = ({ computedXYData$ }: { computedXYData$: Observable<ComputedXYDataMultiValue> }) => {
458
+ return computedXYData$.pipe(
459
+ map(data => {
460
+ return data
461
+ .map(categoryData => {
462
+ return categoryData.filter(d => d.visible == true)
463
+ })
464
+ .filter(categoryData => {
465
+ return categoryData.length > 0
466
+ })
467
+ })
468
+ )
469
+ }
470
+
471
+ // 所有container位置(對應category)
472
+ export const containerPositionObservable = ({ computedData$, fullDataFormatter$, layout$ }: {
473
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
474
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
475
+ layout$: Observable<Layout>
476
+ }): Observable<ContainerPositionScaled[]> => {
477
+
478
+ const containerPosition$ = combineLatest({
479
+ computedData: computedData$,
480
+ fullDataFormatter: fullDataFormatter$,
481
+ layout: layout$,
482
+ }).pipe(
483
+ switchMap(async (d) => d),
484
+ map(data => {
485
+ // 無資料時回傳預設container位置
486
+ if (data.computedData.length === 0) {
487
+ const defaultPositionArr: ContainerPositionScaled[] = [
488
+ {
489
+ "slotIndex": 0,
490
+ "rowIndex": 0,
491
+ "columnIndex": 0,
492
+ "translate": [0, 0],
493
+ "scale": [1, 1]
494
+ }
495
+ ]
496
+ return defaultPositionArr
497
+ }
498
+ if (data.fullDataFormatter.separateCategory) {
499
+ // -- 依slotIndexes計算 --
500
+ return calcContainerPositionScaled(data.layout, data.fullDataFormatter.container, data.computedData.length)
501
+ // return data.computedData.map((seriesData, seriesIndex) => {
502
+ // const columnIndex = seriesIndex % data.fullDataFormatter.container.columnAmount
503
+ // const rowIndex = Math.floor(seriesIndex / data.fullDataFormatter.container.columnAmount)
504
+ // const { translate, scale } = calcMultiValueContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
505
+ // return {
506
+ // slotIndex: seriesIndex,
507
+ // rowIndex,
508
+ // columnIndex,
509
+ // translate,
510
+ // scale,
511
+ // }
512
+ // })
513
+ } else {
514
+ // -- 無拆分 --
515
+ const containerPositionArr = calcContainerPositionScaled(data.layout, data.fullDataFormatter.container, 1)
516
+ return data.computedData.map((d, i) => containerPositionArr[0]) // 每個series相同位置
517
+ // const columnIndex = 0
518
+ // const rowIndex = 0
519
+ // return data.computedData.map((seriesData, seriesIndex) => {
520
+ // const { translate, scale } = calcMultiValueContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
521
+ // return {
522
+ // slotIndex: 0,
523
+ // rowIndex,
524
+ // columnIndex,
525
+ // translate,
526
+ // scale,
527
+ // }
528
+ // })
529
+ }
530
+ })
531
+ )
532
+
533
+ return containerPosition$
534
+ }
535
+
536
+ // export const containerSizeObservable = ({ layout$, containerPosition$ }: {
537
+ // layout$: Observable<Layout>
538
+ // containerPosition$: Observable<ContainerPositionScaled[]>
539
+ // }) => {
540
+ // const rowAmount$ = containerPosition$.pipe(
541
+ // map(containerPosition => {
542
+ // const maxRowIndex = containerPosition.reduce((acc, current) => {
543
+ // return current.rowIndex > acc ? current.rowIndex : acc
544
+ // }, 0)
545
+ // return maxRowIndex + 1
546
+ // }),
547
+ // distinctUntilChanged(),
548
+ // )
549
+
550
+ // const columnAmount$ = containerPosition$.pipe(
551
+ // map(containerPosition => {
552
+ // const maxColumnIndex = containerPosition.reduce((acc, current) => {
553
+ // return current.columnIndex > acc ? current.columnIndex : acc
554
+ // }, 0)
555
+ // return maxColumnIndex + 1
556
+ // }),
557
+ // distinctUntilChanged()
558
+ // )
559
+
560
+ // return combineLatest({
561
+ // layout: layout$,
562
+ // rowAmount: rowAmount$,
563
+ // columnAmount: columnAmount$
564
+ // }).pipe(
565
+ // switchMap(async (d) => d),
566
+ // map(data => {
567
+ // const width = (data.layout.rootWidth / data.columnAmount) - (data.layout.left + data.layout.right)
568
+ // const height = (data.layout.rootHeight / data.rowAmount) - (data.layout.top + data.layout.bottom)
569
+ // return {
570
+ // width,
571
+ // height
572
+ // }
573
+ // }),
574
+ // distinctUntilChanged((a, b) => a.width === b.width && a.height === b.height),
575
+ // shareReplay(1)
576
+ // )
577
+ // }
578
+
579
+ export const filteredXYMinMaxDataObservable = ({ visibleComputedXYData$, xyMinMax$, xyValueIndex$, fullDataFormatter$ }: {
580
+ visibleComputedXYData$: Observable<ComputedXYDataMultiValue>
581
+ xyMinMax$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
582
+ xyValueIndex$: Observable<[number, number]>
583
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
584
+ }) => {
585
+ return combineLatest({
586
+ visibleComputedXYData: visibleComputedXYData$,
587
+ xyMinMax: xyMinMax$,
588
+ xyValueIndex: xyValueIndex$,
589
+ fullDataFormatter: fullDataFormatter$,
590
+ }).pipe(
591
+ map(data => {
592
+ // 所有可見資料依 dataFormatter 的 scale 設定篩選出最大小值
593
+ const { minX, maxX, minY, maxY } = (() => {
594
+
595
+ let { minX, maxX, minY, maxY } = data.xyMinMax
596
+
597
+ if (data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' && minX > 0) {
598
+ minX = 0
599
+ } else if (typeof data.fullDataFormatter.xAxis.scaleDomain[0] === 'number') {
600
+ minX = data.fullDataFormatter.xAxis.scaleDomain[0] as number
601
+ }
602
+ if (data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' && maxX < 0) {
603
+ maxX = 0
604
+ } else if (typeof data.fullDataFormatter.xAxis.scaleDomain[1] === 'number') {
605
+ maxX = data.fullDataFormatter.xAxis.scaleDomain[1] as number
606
+ }
607
+ if (data.fullDataFormatter.yAxis.scaleDomain[0] === 'auto' && minY > 0) {
608
+ minY = 0
609
+ } else if (typeof data.fullDataFormatter.yAxis.scaleDomain[0] === 'number') {
610
+ minY = data.fullDataFormatter.yAxis.scaleDomain[0] as number
611
+ }
612
+ if (data.fullDataFormatter.yAxis.scaleDomain[1] === 'auto' && maxY < 0) {
613
+ maxY = 0
614
+ } else if (typeof data.fullDataFormatter.yAxis.scaleDomain[1] === 'number') {
615
+ maxY = data.fullDataFormatter.yAxis.scaleDomain[1] as number
616
+ }
617
+
618
+ return { minX, maxX, minY, maxY }
619
+ })()
620
+ // console.log({ minX, maxX, minY, maxY })
621
+ let datumList: ComputedXYDatumMultiValue[] = []
622
+ let minXDatum: ComputedXYDatumMultiValue | null = null
623
+ let maxXDatum: ComputedXYDatumMultiValue | null = null
624
+ let minYDatum: ComputedXYDatumMultiValue | null = null
625
+ let maxYDatum: ComputedXYDatumMultiValue | null = null
626
+ // console.log('data.visibleComputedXYData', data.visibleComputedXYData)
627
+ // minX, maxX, minY, maxY 範圍內的最大最小值資料
628
+ // console.log({ minX, maxX, minY, maxY })
629
+ for (let categoryData of data.visibleComputedXYData) {
630
+ for (let datum of categoryData) {
631
+ const xValue = datum.value[data.xyValueIndex[0]]
632
+ const yValue = datum.value[data.xyValueIndex[1]]
633
+ // 比較矩形範圍(所以 minX, maxX, minY, maxY 要同時比較)
634
+ if (xValue >= minX && xValue <= maxX && yValue >= minY && yValue <= maxY) {
635
+ datumList.push(datum)
636
+ if (minXDatum == null || xValue < minXDatum.value[data.xyValueIndex[0]]) {
637
+ minXDatum = datum
638
+ }
639
+ if (maxXDatum == null || xValue > maxXDatum.value[data.xyValueIndex[0]]) {
640
+ maxXDatum = datum
641
+ }
642
+ if (minYDatum == null || yValue < minYDatum.value[data.xyValueIndex[1]]) {
643
+ minYDatum = datum
644
+ }
645
+ if (maxYDatum == null || yValue > maxYDatum.value[data.xyValueIndex[1]]) {
646
+ maxYDatum = datum
647
+ }
648
+ }
649
+ }
650
+ }
651
+
652
+ return {
653
+ datumList,
654
+ minXDatum,
655
+ maxXDatum,
656
+ minYDatum,
657
+ maxYDatum
658
+ }
659
+ })
660
+ )
661
+ }
662
+
663
+ // export const visibleComputedRankingDataObservable = ({ visibleComputedData$ }: {
664
+ // visibleComputedData$: Observable<ComputedDatumMultiValue[][]>
665
+ // }) => {
666
+ // return visibleComputedData$.pipe(
667
+ // map(visibleComputedData => visibleComputedData
668
+ // .flat()
669
+ // .map(d => {
670
+ // // 新增總計資料欄位
671
+ // ;(d as any)._sum = d.value.reduce((acc, curr) => acc + curr, 0)
672
+ // return d
673
+ // })
674
+ // .sort((a: any, b: any) => b._sum - a._sum)
675
+ // )
676
+ // )
677
+
678
+ // // const labelAmountLimit$ = combineLatest({
679
+ // // layout: layout$,
680
+ // // textSizePx: textSizePx$,
681
+ // // sortedLabels: sortedLabels$
682
+ // // }).pipe(
683
+ // // switchMap(async (d) => d),
684
+ // // map(data => {
685
+ // // const lineHeight = data.textSizePx * 2 // 2倍行高
686
+ // // const labelAmountLimit = Math.floor(data.layout.height / lineHeight)
687
+ // // return labelAmountLimit
688
+ // // }),
689
+ // // distinctUntilChanged()
690
+ // // )
691
+
692
+ // // return combineLatest({
693
+ // // sortedLabels: sortedLabels$,
694
+ // // labelAmountLimit: labelAmountLimit$
695
+ // // }).pipe(
696
+ // // map(data => {
697
+ // // return data.sortedLabels.slice(0, data.labelAmountLimit)
698
+ // // })
699
+ // // )
700
+
701
+ // }
702
+
703
+ // export const rankingAmountLimitObservable = ({ layout$, textSizePx$ }: {
704
+ // layout$: Observable<Layout>
705
+ // textSizePx$: Observable<number>
706
+ // }) => {
707
+ // return combineLatest({
708
+ // layout: layout$,
709
+ // textSizePx: textSizePx$
710
+ // }).pipe(
711
+ // switchMap(async (d) => d),
712
+ // map(data => {
713
+ // const lineHeight = data.textSizePx * 2 // 2倍行高
714
+ // const labelAmountLimit = Math.floor(data.layout.height / lineHeight)
715
+ // return labelAmountLimit
716
+ // }),
717
+ // distinctUntilChanged()
718
+ // )
719
+ // }
720
+
721
+ // export const rankingScaleObservable = ({ layout$, visibleComputedRankingData$, rankingAmountLimit$ }: {
722
+ // layout$: Observable<Layout>
723
+ // visibleComputedRankingData$: Observable<ComputedDatumMultiValue[]>
724
+ // rankingAmountLimit$: Observable<number>
725
+ // }) => {
726
+ // return combineLatest({
727
+ // layout: layout$,
728
+ // rankingAmountLimit: rankingAmountLimit$,
729
+ // visibleComputedRankingData: visibleComputedRankingData$,
730
+ // }).pipe(
731
+ // switchMap(async (d) => d),
732
+ // map(data => {
733
+ // let labelAmount = 0
734
+ // let lineHeight = 0
735
+ // let totalHeight = 0
736
+ // if (data.visibleComputedRankingData.length > data.rankingAmountLimit) {
737
+ // labelAmount = data.rankingAmountLimit
738
+ // lineHeight = data.layout.height / labelAmount
739
+ // totalHeight = lineHeight * labelAmount // 用全部的數量來算而不是要顯示的數量(要超出圖軸高度)
740
+ // } else {
741
+ // labelAmount = data.visibleComputedRankingData.length
742
+ // lineHeight = data.layout.height / labelAmount
743
+ // totalHeight = data.layout.height
744
+ // }
745
+
746
+ // return createLabelToAxisScale({
747
+ // axisLabels: data.visibleComputedRankingData.map(d => d.label),
748
+ // axisWidth: totalHeight,
749
+ // padding: 0.5
750
+ // })
751
+ // })
752
+ // )
753
+ // }
754
+
755
+ export const graphicTransformObservable = ({ xyMinMax$, xyValueIndex$, filteredXYMinMaxData$, fullDataFormatter$, layout$ }: {
756
+ xyMinMax$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
757
+ xyValueIndex$: Observable<[number, number]>
758
+ filteredXYMinMaxData$: Observable<{
759
+ minXDatum: ComputedXYDatumMultiValue
760
+ maxXDatum: ComputedXYDatumMultiValue
761
+ minYDatum: ComputedXYDatumMultiValue
762
+ maxYDatum: ComputedXYDatumMultiValue
763
+ }>
764
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
765
+ layout$: Observable<Layout>
766
+ }): Observable<TransformData> => {
767
+ const destroy$ = new Subject()
768
+
769
+ function calcDataAreaTransform ({ xyMinMax, xyValueIndex, filteredXYMinMaxData, xAxis, yAxis, width, height }: {
770
+ xyMinMax: { minX: number, maxX: number, minY: number, maxY: number }
771
+ xyValueIndex: [number, number]
772
+ filteredXYMinMaxData: {
773
+ minXDatum: ComputedXYDatumMultiValue
774
+ maxXDatum: ComputedXYDatumMultiValue
775
+ minYDatum: ComputedXYDatumMultiValue
776
+ maxYDatum: ComputedXYDatumMultiValue
777
+ }
778
+ xAxis: DataFormatterXYAxis
779
+ yAxis: DataFormatterXYAxis
780
+ width: number
781
+ height: number
782
+ }): TransformData {
783
+ // const flatData = data.flat()
784
+
785
+ let translateX = 0
786
+ let translateY = 0
787
+ let scaleX = 0
788
+ let scaleY = 0
789
+
790
+ // // minX, maxX, filteredMinX, filteredMaxX
791
+ // let filteredMinX = 0
792
+ // let filteredMaxX = 0
793
+ // let [minX, maxX] = getMinMax(flatData.map(d => d.value[0]))
794
+ // if (minX === maxX) {
795
+ // minX = maxX - 1 // 避免最大及最小值相同造成無法計算scale
796
+ // }
797
+ // if (xAxis.scaleDomain[0] === 'auto' && filteredMinX > 0) {
798
+ // filteredMinX = 0
799
+ // } else if (typeof xAxis.scaleDomain[0] === 'number') {
800
+ // filteredMinX = xAxis.scaleDomain[0] as number
801
+ // } else {
802
+ // filteredMinX = minX
803
+ // }
804
+ // if (xAxis.scaleDomain[1] === 'auto' && filteredMaxX < 0) {
805
+ // filteredMaxX = 0
806
+ // } else if (typeof xAxis.scaleDomain[1] === 'number') {
807
+ // filteredMaxX = xAxis.scaleDomain[1] as number
808
+ // } else {
809
+ // filteredMaxX = maxX
810
+ // }
811
+ // if (filteredMinX === filteredMaxX) {
812
+ // filteredMinX = filteredMaxX - 1 // 避免最大及最小值相同造成無法計算scale
813
+ // }
814
+
815
+ // // minY, maxY, filteredMinY, filteredMaxY
816
+ // let filteredMinY = 0
817
+ // let filteredMaxY = 0
818
+ // let [minY, maxY] = getMinMax(flatData.map(d => d.value[1]))
819
+ // console.log('filteredXYMinMaxData', filteredXYMinMaxData)
820
+ let { minX, maxX, minY, maxY } = xyMinMax
821
+ // console.log({ minX, maxX, minY, maxY })
822
+ let filteredMinX = filteredXYMinMaxData.minXDatum.value[xyValueIndex[0]] ?? 0
823
+ let filteredMaxX = filteredXYMinMaxData.maxXDatum.value[xyValueIndex[0]] ?? 0
824
+ let filteredMinY = filteredXYMinMaxData.minYDatum.value[xyValueIndex[1]] ?? 0
825
+ let filteredMaxY = filteredXYMinMaxData.maxYDatum.value[xyValueIndex[1]] ?? 0
826
+
827
+ // if (yAxis.scaleDomain[0] === 'auto' && filteredMinY > 0) {
828
+ // filteredMinY = 0
829
+ // } else if (typeof yAxis.scaleDomain[0] === 'number') {
830
+ // filteredMinY = yAxis.scaleDomain[0] as number
831
+ // } else {
832
+ // filteredMinY = minY
833
+ // }
834
+ // if (yAxis.scaleDomain[1] === 'auto' && filteredMaxY < 0) {
835
+ // filteredMaxY = 0
836
+ // } else if (typeof yAxis.scaleDomain[1] === 'number') {
837
+ // filteredMaxY = yAxis.scaleDomain[1] as number
838
+ // } else {
839
+ // filteredMaxY = maxY
840
+ // }
841
+
842
+ // console.log({ minX, maxX, minY, maxY, filteredMinX, filteredMaxX, filteredMinY, filteredMaxY })
843
+ if (filteredMinX === filteredMaxX && filteredMaxX === 0) {
844
+ // 避免最大及最小值相同造成無法計算scale
845
+ filteredMaxX = 1
846
+ }
847
+ if (filteredMinY === filteredMaxY && filteredMaxY === 0) {
848
+ // 避免最大及最小值相同造成無法計算scale
849
+ filteredMaxY = 1
850
+ }
851
+ if (minX === maxX && maxX === 0) {
852
+ // 避免最大及最小值相同造成無法計算scale
853
+ maxX = 1
854
+ }
855
+ if (minY === maxY && maxY === 0) {
856
+ // 避免最大及最小值相同造成無法計算scale
857
+ maxY = 1
858
+ }
859
+ // -- xScale --
860
+ const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
861
+ maxValue: filteredMaxX,
862
+ minValue: filteredMinX,
863
+ axisWidth: width,
864
+ scaleDomain: xAxis.scaleDomain,
865
+ scaleRange: xAxis.scaleRange
866
+ })
867
+
868
+ // -- translateX, scaleX --
869
+ const rangeMinX = xScale(minX > 0 ? 0 : minX) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
870
+ const rangeMaxX = xScale(maxX < 0 ? 0 : maxX) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
871
+ translateX = rangeMinX
872
+ const gWidth = rangeMaxX - rangeMinX
873
+ scaleX = gWidth / width
874
+ // console.log({ gWidth, width, rangeMaxX, rangeMinX, scaleX, translateX })
875
+ // -- yScale --
876
+ const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
877
+ maxValue: filteredMaxY,
878
+ minValue: filteredMinY,
879
+ axisWidth: height,
880
+ scaleDomain: yAxis.scaleDomain,
881
+ scaleRange: yAxis.scaleRange,
882
+ reverse: true
883
+ })
884
+
885
+ // -- translateY, scaleY --
886
+ const rangeMinY = yScale(minY > 0 ? 0 : minY) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
887
+ const rangeMaxY = yScale(maxY < 0 ? 0 : maxY) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
888
+ translateY = rangeMaxY // 最大值的 y 最小(最上方)
889
+ const gHeight = rangeMinY - rangeMaxY // 最大的 y 減最小的 y
890
+ scaleY = gHeight / height
891
+
892
+ return {
893
+ translate: [translateX, translateY],
894
+ scale: [scaleX, scaleY],
895
+ rotate: 0,
896
+ rotateX: 0,
897
+ rotateY: 0,
898
+ value: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`
899
+ }
900
+ }
901
+
902
+ return new Observable(subscriber => {
903
+ combineLatest({
904
+ xyMinMax: xyMinMax$,
905
+ xyValueIndex: xyValueIndex$,
906
+ filteredXYMinMaxData: filteredXYMinMaxData$,
907
+ fullDataFormatter: fullDataFormatter$,
908
+ layout: layout$
909
+ }).pipe(
910
+ takeUntil(destroy$),
911
+ switchMap(async (d) => d),
912
+ ).subscribe(data => {
913
+ if (!data.filteredXYMinMaxData.minXDatum || !data.filteredXYMinMaxData.maxXDatum
914
+ || data.filteredXYMinMaxData.minXDatum.value[data.xyValueIndex[0]] == null || data.filteredXYMinMaxData.maxXDatum.value[data.xyValueIndex[0]] == null
915
+ || !data.filteredXYMinMaxData.minYDatum || !data.filteredXYMinMaxData.maxYDatum
916
+ || data.filteredXYMinMaxData.minYDatum.value[data.xyValueIndex[1]] == null || data.filteredXYMinMaxData.maxYDatum.value[data.xyValueIndex[1]] == null
917
+ ) {
918
+ return
919
+ }
920
+ const dataAreaTransformData = calcDataAreaTransform({
921
+ xyMinMax: data.xyMinMax,
922
+ xyValueIndex: data.xyValueIndex,
923
+ filteredXYMinMaxData: data.filteredXYMinMaxData,
924
+ xAxis: data.fullDataFormatter.xAxis,
925
+ yAxis: data.fullDataFormatter.yAxis,
926
+ width: data.layout.width,
927
+ height: data.layout.height
928
+ })
929
+
930
+ // console.log('dataAreaTransformData', dataAreaTransformData)
931
+
932
+ subscriber.next(dataAreaTransformData)
933
+ })
934
+
935
+ return function unscbscribe () {
936
+ destroy$.next(undefined)
937
+ }
938
+ })
939
+ }
940
+
941
+ export const graphicReverseScaleObservable = ({ containerPosition$, graphicTransform$ }: {
942
+ containerPosition$: Observable<ContainerPositionScaled[]>
943
+ // multiValueAxesTransform$: Observable<TransformData>
944
+ graphicTransform$: Observable<TransformData>
945
+ }): Observable<[number, number][]> => {
946
+ return combineLatest({
947
+ containerPosition: containerPosition$,
948
+ // multiValueAxesTransform: multiValueAxesTransform$,
949
+ graphicTransform: graphicTransform$,
950
+ }).pipe(
951
+ switchMap(async (d) => d),
952
+ map(data => {
953
+ // if (data.multiValueAxesTransform.rotate == 0 || data.multiValueAxesTransform.rotate == 180) {
954
+ return data.containerPosition.map((series, seriesIndex) => {
955
+ return [
956
+ 1 / data.graphicTransform.scale[0] / data.containerPosition[seriesIndex].scale[0],
957
+ 1 / data.graphicTransform.scale[1] / data.containerPosition[seriesIndex].scale[1],
958
+ ]
959
+ })
960
+ // } else {
961
+ // return data.containerPosition.map((series, seriesIndex) => {
962
+ // // 由於有垂直的旋轉,所以外層 (container) x和y的scale要互換
963
+ // return [
964
+ // 1 / data.graphicTransform.scale[0] / data.containerPosition[seriesIndex].scale[1],
965
+ // 1 / data.graphicTransform.scale[1] / data.containerPosition[seriesIndex].scale[0],
966
+ // ]
967
+ // })
968
+ // }
969
+ }),
970
+ )
971
+ }
972
+
973
+ // X 軸圖軸 - 用 value[index]
974
+ export const xScaleObservable = ({ visibleComputedSumData$, fullDataFormatter$, filteredXYMinMaxData$, containerSize$ }: {
975
+ visibleComputedSumData$: Observable<ComputedDatumMultiValue[][]>
976
+ fullDataFormatter$: Observable<DataFormatterMultiValue>
977
+ filteredXYMinMaxData$: Observable<{
978
+ minXDatum: ComputedXYDatumMultiValue
979
+ maxXDatum: ComputedXYDatumMultiValue
980
+ minYDatum: ComputedXYDatumMultiValue
981
+ maxYDatum: ComputedXYDatumMultiValue
982
+ }>
983
+ // layout$: Observable<Layout>
984
+ containerSize$: Observable<ContainerSize>
985
+ }) => {
986
+ return combineLatest({
987
+ visibleComputedSumData: visibleComputedSumData$,
988
+ fullDataFormatter: fullDataFormatter$,
989
+ containerSize: containerSize$,
990
+ // xyMinMax: xyMinMax$
991
+ filteredXYMinMaxData: filteredXYMinMaxData$
992
+ }).pipe(
993
+ switchMap(async (d) => d),
994
+ map(data => {
995
+ const valueIndex = data.fullDataFormatter.xAxis.valueIndex
996
+ if (!data.filteredXYMinMaxData.minXDatum || !data.filteredXYMinMaxData.maxXDatum
997
+ // || data.filteredXYMinMaxData.minXDatum.value[valueIndex] == null || data.filteredXYMinMaxData.maxXDatum.value[valueIndex] == null
998
+ ) {
999
+ return
1000
+ }
1001
+ let maxValue: number | null = data.filteredXYMinMaxData.maxXDatum.value[valueIndex]
1002
+ let minValue: number | null = data.filteredXYMinMaxData.minXDatum.value[valueIndex]
1003
+ if (maxValue === minValue && maxValue === 0) {
1004
+ // 避免最大及最小值同等於 0 造成無法計算scale
1005
+ maxValue = 1
1006
+ }
1007
+
1008
+ const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
1009
+ maxValue,
1010
+ minValue,
1011
+ axisWidth: data.containerSize.width,
1012
+ scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
1013
+ scaleRange: data.fullDataFormatter.xAxis.scaleRange,
1014
+ })
1015
+ return xScale
1016
+ })
1017
+ )
1018
+ }
1019
+
1020
+ // X 軸圖軸 - 用所有 valueIndex 加總資料
1021
+ // export const xSumScaleObservable = ({ fullDataFormatter$, filteredXYMinMaxData$, containerSize$ }: {
1022
+ // // valueIndex$: Observable<number>
1023
+ // fullDataFormatter$: Observable<DataFormatterMultiValue>
1024
+ // filteredXYMinMaxData$: Observable<{
1025
+ // minXDatum: ComputedXYDatumMultiValue
1026
+ // maxXDatum: ComputedXYDatumMultiValue
1027
+ // minYDatum: ComputedXYDatumMultiValue
1028
+ // maxYDatum: ComputedXYDatumMultiValue
1029
+ // }>
1030
+ // // layout$: Observable<Layout>
1031
+ // containerSize$: Observable<ContainerSize>
1032
+ // }) => {
1033
+ // return combineLatest({
1034
+ // // valueIndex: valueIndex$,
1035
+ // fullDataFormatter: fullDataFormatter$,
1036
+ // containerSize: containerSize$,
1037
+ // // xyMinMax: xyMinMax$
1038
+ // filteredXYMinMaxData: filteredXYMinMaxData$
1039
+ // }).pipe(
1040
+ // switchMap(async (d) => d),
1041
+ // map(data => {
1042
+ // const valueIndex = data.fullDataFormatter.xAxis.valueIndex
1043
+ // if (!data.filteredXYMinMaxData.minXDatum || !data.filteredXYMinMaxData.maxXDatum
1044
+ // // || data.filteredXYMinMaxData.minXDatum.value[valueIndex] == null || data.filteredXYMinMaxData.maxXDatum.value[valueIndex] == null
1045
+ // ) {
1046
+ // return
1047
+ // }
1048
+ // let maxValue: number | null = data.filteredXYMinMaxData.maxXDatum.value[valueIndex]
1049
+ // let minValue: number | null = data.filteredXYMinMaxData.minXDatum.value[valueIndex]
1050
+ // if (maxValue === minValue && maxValue === 0) {
1051
+ // // 避免最大及最小值同等於 0 造成無法計算scale
1052
+ // maxValue = 1
1053
+ // }
1054
+
1055
+ // const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
1056
+ // maxValue,
1057
+ // minValue,
1058
+ // axisWidth: data.containerSize.width,
1059
+ // scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
1060
+ // scaleRange: data.fullDataFormatter.xAxis.scaleRange,
1061
+ // })
1062
+ // return xScale
1063
+ // })
1064
+ // )
1065
+ // }
1066
+
1067
+ export const yScaleObservable = ({ fullDataFormatter$, filteredXYMinMaxData$, containerSize$ }: {
1068
+ fullDataFormatter$: Observable<DataFormatterMultiValue>
1069
+ filteredXYMinMaxData$: Observable<{
1070
+ minXDatum: ComputedXYDatumMultiValue
1071
+ maxXDatum: ComputedXYDatumMultiValue
1072
+ minYDatum: ComputedXYDatumMultiValue
1073
+ maxYDatum: ComputedXYDatumMultiValue
1074
+ }>
1075
+ containerSize$: Observable<ContainerSize>
1076
+ }) => {
1077
+ return combineLatest({
1078
+ fullDataFormatter: fullDataFormatter$,
1079
+ containerSize: containerSize$,
1080
+ // xyMinMax: observer.xyMinMax$
1081
+ filteredXYMinMaxData: filteredXYMinMaxData$
1082
+ }).pipe(
1083
+ switchMap(async (d) => d),
1084
+ map(data => {
1085
+ const valueIndex = data.fullDataFormatter.yAxis.valueIndex
1086
+ if (!data.filteredXYMinMaxData.minYDatum || !data.filteredXYMinMaxData.maxYDatum
1087
+ || data.filteredXYMinMaxData.minYDatum.value[valueIndex] == null || data.filteredXYMinMaxData.maxYDatum.value[valueIndex] == null
1088
+ ) {
1089
+ return
1090
+ }
1091
+ let maxValue = data.filteredXYMinMaxData.maxYDatum.value[valueIndex]
1092
+ let minValue = data.filteredXYMinMaxData.minYDatum.value[valueIndex]
1093
+ if (maxValue === minValue && maxValue === 0) {
1094
+ // 避免最大及最小值同等於 0 造成無法計算scale
1095
+ maxValue = 1
1096
+ }
1097
+
1098
+ const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
1099
+ maxValue,
1100
+ minValue,
1101
+ axisWidth: data.containerSize.height,
1102
+ scaleDomain: data.fullDataFormatter.yAxis.scaleDomain,
1103
+ scaleRange: data.fullDataFormatter.yAxis.scaleRange,
1104
+ reverse: true
1105
+ })
1106
+ return yScale
1107
+ })
1108
+ )
1109
+ }
1110
+
1111
+ export const ordinalPaddingObservable = ({ ordinalScaleDomain$, computedData$, containerSize$ }: {
1112
+ // fullDataFormatter$: Observable<DataFormatterMultiValue>
1113
+ ordinalScaleDomain$: Observable<[number, number]>
1114
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
1115
+ containerSize$: Observable<ContainerSize>
1116
+ }) => {
1117
+ return combineLatest({
1118
+ ordinalScaleDomain: ordinalScaleDomain$,
1119
+ containerSize: containerSize$,
1120
+ computedData: computedData$
1121
+ }).pipe(
1122
+ switchMap(async (d) => d),
1123
+ map(data => {
1124
+ let maxValue: number = data.computedData[0] && data.computedData[0][0] && data.computedData[0][0].value.length
1125
+ ? data.computedData[0][0].value.length - 1
1126
+ : 0
1127
+ let minValue: number = 0
1128
+
1129
+ let axisWidth = data.containerSize.width
1130
+ // const scaleDomain: [number, number] = [
1131
+ // data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[0] === 'min'
1132
+ // ? 0
1133
+ // : data.fullDataFormatter.xAxis.scaleDomain[0] as number,
1134
+ // data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[1] === 'max'
1135
+ // ? maxValue
1136
+ // : data.fullDataFormatter.xAxis.scaleDomain[1] as number
1137
+ // ]
1138
+ const distance = data.ordinalScaleDomain[1] - data.ordinalScaleDomain[0]
1139
+ // console.log('distance', distance)
1140
+ if (distance >= 1) {
1141
+ return axisWidth / (distance + 1) / 2
1142
+ } else {
1143
+ return 0
1144
+ }
1145
+ })
1146
+ )
1147
+ }
1148
+
1149
+ // 定性的 X 軸圖軸 - 用 value 的 index 計算
1150
+ export const ordinalScaleObservable = ({ ordinalScaleDomain$, computedData$, containerSize$, ordinalPadding$ }: {
1151
+ // fullDataFormatter$: Observable<DataFormatterMultiValue>
1152
+ ordinalScaleDomain$: Observable<[number, number]>
1153
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
1154
+ containerSize$: Observable<ContainerSize>
1155
+ ordinalPadding$: Observable<number>
1156
+ }) => {
1157
+ return combineLatest({
1158
+ // fullDataFormatter: fullDataFormatter$,
1159
+ ordinalScaleDomain: ordinalScaleDomain$,
1160
+ computedData: computedData$,
1161
+ containerSize: containerSize$,
1162
+ ordinalPadding: ordinalPadding$,
1163
+ }).pipe(
1164
+ switchMap(async (d) => d),
1165
+ map(data => {
1166
+ let maxValue: number = data.computedData[0] && data.computedData[0][0] && data.computedData[0][0].value.length
1167
+ ? data.computedData[0][0].value.length - 1
1168
+ : 0
1169
+ let minValue: number = 0
1170
+ let axisWidth = data.containerSize.width - (data.ordinalPadding * 2)
1171
+ // const scaleDomain: [number, number] = [
1172
+ // data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[0] === 'min'
1173
+ // ? 0
1174
+ // : data.fullDataFormatter.xAxis.scaleDomain[0] as number,
1175
+ // data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[1] === 'max'
1176
+ // ? maxValue
1177
+ // : data.fullDataFormatter.xAxis.scaleDomain[1] as number
1178
+ // ]
1179
+ // const distance = scaleDomain[1] - scaleDomain[0]
1180
+ // // console.log('distance', distance)
1181
+ // if (distance >= 1) {
1182
+ // axisWidth = axisWidth - (axisWidth / (distance + 1))
1183
+ // }
1184
+
1185
+ const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
1186
+ maxValue,
1187
+ minValue,
1188
+ axisWidth,
1189
+ scaleDomain: data.ordinalScaleDomain,
1190
+ scaleRange: [0, 1],
1191
+ })
1192
+
1193
+ // const xScale = createLabelToAxisScale({
1194
+ // axisLabels: new Array(maxValue + 1).fill('').map((d, i) => String(i)),
1195
+ // axisWidth: data.containerSize.width,
1196
+ // padding: 0.5
1197
+ // })
1198
+ return xScale
1199
+ })
1200
+ )
1201
+ }
1202
+
1203
+ // export const valueLabelsObservable = ({ fullDataFormatter$, computedData$ }: {
1204
+ // fullDataFormatter$: Observable<DataFormatterMultiValue>
1205
+ // computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
1206
+ // }) => {
1207
+ // return combineLatest({
1208
+ // fullDataFormatter: fullDataFormatter$,
1209
+ // computedData: computedData$
1210
+ // }).pipe(
1211
+ // switchMap(async (d) => d),
1212
+ // map(data => {
1213
+ // const valueLabels = data.computedData[0] && data.computedData[0][0] && data.computedData[0][0].value.length
1214
+ // ? data.computedData[0][0].value.map((d, i) => data.fullDataFormatter.valueLabels[i] ?? String(i)) // 預設為 0, 1, 2, 3...
1215
+ // : []
1216
+ // return valueLabels
1217
+ // })
1218
+ // )
1219
+ // }