@orbcharts/core 3.0.0-beta.8 → 3.0.0

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 (76) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-core.es.js +2734 -2353
  3. package/dist/orbcharts-core.umd.js +4 -4
  4. package/dist/src/defaults.d.ts +2 -1
  5. package/dist/src/utils/gridObservables.d.ts +8 -4
  6. package/dist/src/utils/index.d.ts +0 -3
  7. package/dist/src/utils/multiGridObservables.d.ts +3 -2
  8. package/dist/src/utils/multiValueObservables.d.ts +76 -29
  9. package/dist/src/utils/observables.d.ts +8 -1
  10. package/dist/src/utils/orbchartsUtils.d.ts +9 -9
  11. package/dist/src/utils/seriesObservables.d.ts +1 -1
  12. package/lib/core-types.ts +7 -7
  13. package/package.json +42 -42
  14. package/src/AbstractChart.ts +57 -57
  15. package/src/GridChart.ts +24 -24
  16. package/src/MultiGridChart.ts +24 -24
  17. package/src/MultiValueChart.ts +24 -24
  18. package/src/RelationshipChart.ts +24 -24
  19. package/src/SeriesChart.ts +24 -24
  20. package/src/TreeChart.ts +24 -24
  21. package/src/base/createBaseChart.ts +506 -505
  22. package/src/base/createBasePlugin.ts +154 -153
  23. package/src/base/validators/chartOptionsValidator.ts +23 -23
  24. package/src/base/validators/chartParamsValidator.ts +133 -133
  25. package/src/base/validators/elementValidator.ts +13 -13
  26. package/src/base/validators/pluginsValidator.ts +14 -14
  27. package/src/defaults.ts +282 -238
  28. package/src/defineGridPlugin.ts +3 -3
  29. package/src/defineMultiGridPlugin.ts +3 -3
  30. package/src/defineMultiValuePlugin.ts +3 -3
  31. package/src/defineNoneDataPlugin.ts +4 -4
  32. package/src/defineRelationshipPlugin.ts +3 -3
  33. package/src/defineSeriesPlugin.ts +3 -3
  34. package/src/defineTreePlugin.ts +3 -3
  35. package/src/grid/computedDataFn.ts +129 -129
  36. package/src/grid/contextObserverCallback.ts +198 -176
  37. package/src/grid/dataFormatterValidator.ts +120 -101
  38. package/src/grid/dataValidator.ts +12 -12
  39. package/src/index.ts +20 -20
  40. package/src/multiGrid/computedDataFn.ts +123 -123
  41. package/src/multiGrid/contextObserverCallback.ts +72 -41
  42. package/src/multiGrid/dataFormatterValidator.ts +115 -115
  43. package/src/multiGrid/dataValidator.ts +12 -12
  44. package/src/multiValue/computedDataFn.ts +113 -110
  45. package/src/multiValue/contextObserverCallback.ts +276 -160
  46. package/src/multiValue/dataFormatterValidator.ts +89 -9
  47. package/src/multiValue/dataValidator.ts +12 -9
  48. package/src/relationship/computedDataFn.ts +159 -144
  49. package/src/relationship/contextObserverCallback.ts +80 -80
  50. package/src/relationship/dataFormatterValidator.ts +13 -9
  51. package/src/relationship/dataValidator.ts +13 -9
  52. package/src/series/computedDataFn.ts +88 -88
  53. package/src/series/contextObserverCallback.ts +107 -100
  54. package/src/series/dataFormatterValidator.ts +41 -41
  55. package/src/series/dataValidator.ts +12 -12
  56. package/src/tree/computedDataFn.ts +129 -129
  57. package/src/tree/contextObserverCallback.ts +58 -58
  58. package/src/tree/dataFormatterValidator.ts +13 -13
  59. package/src/tree/dataValidator.ts +13 -13
  60. package/src/utils/commonUtils.ts +55 -55
  61. package/src/utils/d3Scale.ts +198 -198
  62. package/src/utils/errorMessage.ts +42 -42
  63. package/src/utils/gridObservables.ts +705 -683
  64. package/src/utils/index.ts +10 -10
  65. package/src/utils/multiGridObservables.ts +401 -392
  66. package/src/utils/multiValueObservables.ts +1044 -662
  67. package/src/utils/observables.ts +281 -219
  68. package/src/utils/orbchartsUtils.ts +377 -377
  69. package/src/utils/relationshipObservables.ts +84 -84
  70. package/src/utils/seriesObservables.ts +175 -175
  71. package/src/utils/treeObservables.ts +105 -105
  72. package/src/utils/validator.ts +126 -126
  73. package/tsconfig.base.json +13 -13
  74. package/tsconfig.json +2 -2
  75. package/vite-env.d.ts +6 -6
  76. package/vite.config.js +22 -22
@@ -1,662 +1,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
- DataFormatterTypeMap,
22
- DataFormatterMultiValue,
23
- DataFormatterAxis,
24
- ComputedLayoutDatumMultiValue,
25
- ComputedLayoutDataMultiValue,
26
- ContainerPositionScaled,
27
- HighlightTarget,
28
- Layout,
29
- TransformData } from '../../lib/core-types'
30
- import { getMinMax, getMinMaxMultiValue } from './orbchartsUtils'
31
- import { createValueToAxisScale, createLabelToAxisScale, createAxisToLabelIndexScale } from './d3Scale'
32
- import { calcGridContainerLayout } from './orbchartsUtils'
33
-
34
- export const minMaxXYObservable = ({ computedData$ }: { computedData$: Observable<ComputedDataTypeMap<'multiValue'>> }) => {
35
- return computedData$.pipe(
36
- map(data => {
37
- const flatData = data.flat()
38
- const [minX, maxX] = getMinMax(flatData.map(d => d.value[0]))
39
- const [minY, maxY] = getMinMax(flatData.map(d => d.value[1]))
40
- return { minX, maxX, minY, maxY }
41
- })
42
- )
43
- }
44
-
45
- export const multiValueComputedLayoutDataObservable = ({ computedData$, minMaxXY$, fullDataFormatter$, layout$ }: {
46
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
47
- minMaxXY$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
48
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
49
- layout$: Observable<Layout>
50
- }): Observable<ComputedLayoutDataMultiValue> => {
51
-
52
- // 未篩選範圍前的 scale
53
- function createOriginXScale (minMaxXY: { minX: number, maxX: number, minY: number, maxY: number }, layout: Layout) {
54
- let maxValue = minMaxXY.maxX
55
- let minValue = minMaxXY.minX
56
- if (minValue === maxValue && maxValue === 0) {
57
- // 避免最大及最小值相同造成無法計算scale
58
- maxValue = 1
59
- }
60
- const valueScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
61
- maxValue,
62
- minValue,
63
- axisWidth: layout.width,
64
- scaleDomain: ['auto', 'auto'], // 不使用dataFormatter設定 --> 以0為基準到最大或最小值為範圍( * 如果是使用[minValue, maxValue]的話,在兩者很接近的情況下有可能造成scale倍率過高而svg變型時失真的情況)
65
- scaleRange: [0, 1] // 不使用dataFormatter設定
66
- })
67
-
68
- return valueScale
69
- }
70
-
71
- // 未篩選範圍及visible前的 scale
72
- function createOriginYScale (minMaxXY: { minX: number, maxX: number, minY: number, maxY: number }, layout: Layout) {
73
- let maxValue = minMaxXY.maxY
74
- let minValue = minMaxXY.minY
75
- if (minValue === maxValue && maxValue === 0) {
76
- // 避免最大及最小值相同造成無法計算scale
77
- maxValue = 1
78
- }
79
- const valueScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
80
- maxValue,
81
- minValue,
82
- axisWidth: layout.height,
83
- scaleDomain: ['auto', 'auto'], // 不使用dataFormatter設定 --> 以0為基準到最大或最小值為範圍( * 如果是使用[minValue, maxValue]的話,在兩者很接近的情況下有可能造成scale倍率過高而svg變型時失真的情況)
84
- scaleRange: [0, 1], // 不使用dataFormatter設定
85
- reverse: true
86
- })
87
-
88
- return valueScale
89
- }
90
-
91
- return combineLatest({
92
- computedData: computedData$,
93
- minMaxXY: minMaxXY$,
94
- fullDataFormatter: fullDataFormatter$,
95
- layout: layout$
96
- }).pipe(
97
- switchMap(async d => d),
98
- map(data => {
99
-
100
- const xScale = createOriginXScale(data.minMaxXY, data.layout)
101
- const yScale = createOriginYScale(data.minMaxXY, data.layout)
102
-
103
- return data.computedData
104
- .map((categoryData, categoryIndex) => {
105
- return categoryData.map((datum, datumIndex) => {
106
- return {
107
- ...datum,
108
- axisX: xScale(datum.value[0] ?? 0),
109
- // axisY: data.layout.height - yScale(datum.value[1] ?? 0), // y軸的繪圖座標是從上到下,所以反轉
110
- axisY: yScale(datum.value[1] ?? 0), // y軸的繪圖座標是從上到下,所以反轉
111
- }
112
- })
113
- })
114
- })
115
- )
116
- }
117
-
118
- // export const multiValueAxesTransformObservable = ({ fullDataFormatter$, layout$ }: {
119
- // fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
120
- // layout$: Observable<Layout>
121
- // }): Observable<TransformData> => {
122
- // const destroy$ = new Subject()
123
-
124
- // function calcAxesTransform ({ xAxis, yAxis, width, height }: {
125
- // xAxis: DataFormatterAxis,
126
- // yAxis: DataFormatterAxis,
127
- // width: number,
128
- // height: number
129
- // }): TransformData {
130
- // if (!xAxis || !yAxis) {
131
- // return {
132
- // translate: [0, 0],
133
- // scale: [1, 1],
134
- // rotate: 0,
135
- // rotateX: 0,
136
- // rotateY: 0,
137
- // value: ''
138
- // }
139
- // }
140
- // // const width = size.width - fullChartParams.layout.left - fullChartParams.layout.right
141
- // // const height = size.height - fullChartParams.layout.top - fullChartParams.layout.bottom
142
- // let translateX = 0
143
- // let translateY = height
144
- // let rotate = 0
145
- // let rotateX = 180
146
- // let rotateY = 0
147
-
148
- // return {
149
- // translate: [translateX, translateY],
150
- // scale: [1, 1],
151
- // rotate,
152
- // rotateX,
153
- // rotateY,
154
- // value: `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
155
- // }
156
- // }
157
-
158
- // return new Observable(subscriber => {
159
- // combineLatest({
160
- // fullDataFormatter: fullDataFormatter$,
161
- // layout: layout$
162
- // }).pipe(
163
- // takeUntil(destroy$),
164
- // switchMap(async (d) => d),
165
- // ).subscribe(data => {
166
- // const axesTransformData = calcAxesTransform({
167
- // xAxis: data.fullDataFormatter.xAxis,
168
- // yAxis: data.fullDataFormatter.yAxis,
169
- // width: data.layout.width,
170
- // height: data.layout.height
171
- // })
172
-
173
- // subscriber.next(axesTransformData)
174
- // })
175
-
176
- // return function unscbscribe () {
177
- // destroy$.next(undefined)
178
- // }
179
- // })
180
- // }
181
-
182
-
183
- // export const multiValueAxesReverseTransformObservable = ({ multiValueAxesTransform$ }: {
184
- // multiValueAxesTransform$: Observable<TransformData>
185
- // }): Observable<TransformData> => {
186
- // return multiValueAxesTransform$.pipe(
187
- // map(d => {
188
- // // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
189
- // const translate: [number, number] = [0, 0] // 無需逆轉
190
- // const scale: [number, number] = [1 / d.scale[0], 1 / d.scale[1]]
191
- // const rotate = d.rotate * -1
192
- // const rotateX = d.rotateX * -1
193
- // const rotateY = d.rotateY * -1
194
- // return {
195
- // translate,
196
- // scale,
197
- // rotate,
198
- // rotateX,
199
- // rotateY,
200
- // value: `translate(${translate[0]}px, ${translate[1]}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotate(${rotate}deg)`
201
- // }
202
- // }),
203
- // )
204
- // }
205
-
206
-
207
-
208
- // export const multiValueAxesSizeObservable = ({ fullDataFormatter$, layout$ }: {
209
- // fullDataFormatter$: Observable<DataFormatterMultiValue>
210
- // layout$: Observable<Layout>
211
- // }): Observable<{
212
- // width: number;
213
- // height: number;
214
- // }> => {
215
- // const destroy$ = new Subject()
216
-
217
- // function calcAxesSize ({ xAxisPosition, yAxisPosition, width, height }: {
218
- // xAxisPosition: AxisPosition
219
- // yAxisPosition: AxisPosition
220
- // width: number
221
- // height: number
222
- // }) {
223
- // if ((xAxisPosition === 'bottom' || xAxisPosition === 'top') && (yAxisPosition === 'left' || yAxisPosition === 'right')) {
224
- // return { width, height }
225
- // } else if ((xAxisPosition === 'left' || xAxisPosition === 'right') && (yAxisPosition === 'bottom' || yAxisPosition === 'top')) {
226
- // return {
227
- // width: height,
228
- // height: width
229
- // }
230
- // } else {
231
- // // default
232
- // return { width, height }
233
- // }
234
- // }
235
-
236
- // return new Observable(subscriber => {
237
- // combineLatest({
238
- // fullDataFormatter: fullDataFormatter$,
239
- // layout: layout$
240
- // }).pipe(
241
- // takeUntil(destroy$),
242
- // switchMap(async (d) => d),
243
- // ).subscribe(data => {
244
-
245
- // const axisSize = calcAxesSize({
246
- // xAxisPosition: 'bottom',
247
- // yAxisPosition: 'left',
248
- // width: data.layout.width,
249
- // height: data.layout.height,
250
- // })
251
-
252
- // subscriber.next(axisSize)
253
-
254
- // return function unsubscribe () {
255
- // destroy$.next(undefined)
256
- // }
257
- // })
258
- // })
259
- // }
260
-
261
- // export const multiValueHighlightObservable = ({ computedData$, fullChartParams$, event$ }: {
262
- // computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
263
- // fullChartParams$: Observable<ChartParams>
264
- // event$: Subject<any>
265
- // }): Observable<string[]> => {
266
- // const datumList$ = computedData$.pipe(
267
- // map(d => d.flat())
268
- // )
269
- // return highlightObservable ({ datumList$, fullChartParams$, event$ })
270
- // }
271
-
272
- export const multiValueCategoryLabelsObservable = ({ computedData$, fullDataFormatter$ }: {
273
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
274
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
275
- }) => {
276
- return computedData$.pipe(
277
- map(data => {
278
- return data
279
- .map(d => d[0] ? d[0].categoryLabel : '')
280
- // .filter(d => d != null && d != '')
281
- }),
282
- distinctUntilChanged((a, b) => {
283
- return JSON.stringify(a).length === JSON.stringify(b).length
284
- }),
285
- )
286
- }
287
-
288
- export const multiValueVisibleComputedDataObservable = ({ computedData$ }: { computedData$: Observable<ComputedDataTypeMap<'multiValue'>> }) => {
289
- return computedData$.pipe(
290
- map(data => {
291
- return data
292
- .map(categoryData => {
293
- return categoryData.filter(d => d.visible == true)
294
- })
295
- .filter(categoryData => {
296
- return categoryData.length > 0
297
- })
298
- })
299
- )
300
- }
301
-
302
- export const multiValueVisibleComputedLayoutDataObservable = ({ computedLayoutData$ }: { computedLayoutData$: Observable<ComputedLayoutDataMultiValue> }) => {
303
- return computedLayoutData$.pipe(
304
- map(data => {
305
- return data
306
- .map(categoryData => {
307
- return categoryData.filter(d => d.visible == true)
308
- })
309
- .filter(categoryData => {
310
- return categoryData.length > 0
311
- })
312
- })
313
- )
314
- }
315
-
316
- // 所有container位置(對應series)
317
- export const multiValueContainerPositionObservable = ({ computedData$, fullDataFormatter$, layout$ }: {
318
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
319
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
320
- layout$: Observable<Layout>
321
- }): Observable<ContainerPositionScaled[]> => {
322
-
323
- const multiValueContainerPosition$ = combineLatest({
324
- computedData: computedData$,
325
- fullDataFormatter: fullDataFormatter$,
326
- layout: layout$,
327
- }).pipe(
328
- switchMap(async (d) => d),
329
- map(data => {
330
-
331
- if (data.fullDataFormatter.separateCategory) {
332
- // -- 依slotIndexes計算 --
333
- return calcGridContainerLayout(data.layout, data.fullDataFormatter.container, data.computedData.length)
334
- // return data.computedData.map((seriesData, seriesIndex) => {
335
- // const columnIndex = seriesIndex % data.fullDataFormatter.container.columnAmount
336
- // const rowIndex = Math.floor(seriesIndex / data.fullDataFormatter.container.columnAmount)
337
- // const { translate, scale } = calcMultiValueContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
338
- // return {
339
- // slotIndex: seriesIndex,
340
- // rowIndex,
341
- // columnIndex,
342
- // translate,
343
- // scale,
344
- // }
345
- // })
346
- } else {
347
- // -- 無拆分 --
348
- const multiValueContainerPositionArr = calcGridContainerLayout(data.layout, data.fullDataFormatter.container, 1)
349
- return data.computedData.map((d, i) => multiValueContainerPositionArr[0]) // 每個series相同位置
350
- // const columnIndex = 0
351
- // const rowIndex = 0
352
- // return data.computedData.map((seriesData, seriesIndex) => {
353
- // const { translate, scale } = calcMultiValueContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
354
- // return {
355
- // slotIndex: 0,
356
- // rowIndex,
357
- // columnIndex,
358
- // translate,
359
- // scale,
360
- // }
361
- // })
362
- }
363
- })
364
- )
365
-
366
- return multiValueContainerPosition$
367
- }
368
-
369
-
370
- export const filteredMinMaxXYDataObservable = ({ visibleComputedLayoutData$, minMaxXY$, fullDataFormatter$ }: {
371
- visibleComputedLayoutData$: Observable<ComputedLayoutDataMultiValue>
372
- minMaxXY$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
373
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
374
- }) => {
375
- return combineLatest({
376
- visibleComputedLayoutData: visibleComputedLayoutData$,
377
- minMaxXY: minMaxXY$,
378
- fullDataFormatter: fullDataFormatter$,
379
- }).pipe(
380
- map(data => {
381
- // 所有可見資料依 dataFormatter 的 scale 設定篩選出最大小值
382
- const { minX, maxX, minY, maxY } = (() => {
383
-
384
- let { minX, maxX, minY, maxY } = data.minMaxXY
385
-
386
- if (data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' && minX > 0) {
387
- minX = 0
388
- } else if (typeof data.fullDataFormatter.xAxis.scaleDomain[0] === 'number') {
389
- minX = data.fullDataFormatter.xAxis.scaleDomain[0] as number
390
- }
391
- if (data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' && maxX < 0) {
392
- maxX = 0
393
- } else if (typeof data.fullDataFormatter.xAxis.scaleDomain[1] === 'number') {
394
- maxX = data.fullDataFormatter.xAxis.scaleDomain[1] as number
395
- }
396
- if (data.fullDataFormatter.yAxis.scaleDomain[0] === 'auto' && minY > 0) {
397
- minY = 0
398
- } else if (typeof data.fullDataFormatter.yAxis.scaleDomain[0] === 'number') {
399
- minY = data.fullDataFormatter.yAxis.scaleDomain[0] as number
400
- }
401
- if (data.fullDataFormatter.yAxis.scaleDomain[1] === 'auto' && maxY < 0) {
402
- maxY = 0
403
- } else if (typeof data.fullDataFormatter.yAxis.scaleDomain[1] === 'number') {
404
- maxY = data.fullDataFormatter.yAxis.scaleDomain[1] as number
405
- }
406
-
407
- return { minX, maxX, minY, maxY }
408
- })()
409
- // console.log({ minX, maxX, minY, maxY })
410
- let datumList: ComputedLayoutDatumMultiValue[] = []
411
- let minXDatum: ComputedLayoutDatumMultiValue | null = null
412
- let maxXDatum: ComputedLayoutDatumMultiValue | null = null
413
- let minYDatum: ComputedLayoutDatumMultiValue | null = null
414
- let maxYDatum: ComputedLayoutDatumMultiValue | null = null
415
- // console.log('data.visibleComputedLayoutData', data.visibleComputedLayoutData)
416
- // minX, maxX, minY, maxY 範圍內的最大最小值資料
417
- // console.log({ minX, maxX, minY, maxY })
418
- for (let categoryData of data.visibleComputedLayoutData) {
419
- for (let datum of categoryData) {
420
- // 比較矩形範圍(所以 minX, maxX, minY, maxY 要同時比較)
421
- if (datum.value[0] >= minX && datum.value[0] <= maxX && datum.value[1] >= minY && datum.value[1] <= maxY) {
422
- datumList.push(datum)
423
- if (minXDatum == null || datum.value[0] < minXDatum.value[0]) {
424
- minXDatum = datum
425
- }
426
- if (maxXDatum == null || datum.value[0] > maxXDatum.value[0]) {
427
- maxXDatum = datum
428
- }
429
- if (minYDatum == null || datum.value[1] < minYDatum.value[1]) {
430
- minYDatum = datum
431
- }
432
- if (maxYDatum == null || datum.value[1] > maxYDatum.value[1]) {
433
- maxYDatum = datum
434
- }
435
- }
436
- }
437
- }
438
-
439
- return {
440
- datumList,
441
- minXDatum,
442
- maxXDatum,
443
- minYDatum,
444
- maxYDatum
445
- }
446
- })
447
- )
448
- }
449
-
450
- export const multiValueGraphicTransformObservable = ({ minMaxXY$, filteredMinMaxXYData$, fullDataFormatter$, layout$ }: {
451
- minMaxXY$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
452
- filteredMinMaxXYData$: Observable<{
453
- minXDatum: ComputedLayoutDatumMultiValue
454
- maxXDatum: ComputedLayoutDatumMultiValue
455
- minYDatum: ComputedLayoutDatumMultiValue
456
- maxYDatum: ComputedLayoutDatumMultiValue
457
- }>
458
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
459
- layout$: Observable<Layout>
460
- }): Observable<TransformData> => {
461
- const destroy$ = new Subject()
462
-
463
- function calcDataAreaTransform ({ minMaxXY, filteredMinMaxXYData, xAxis, yAxis, width, height }: {
464
- minMaxXY: { minX: number, maxX: number, minY: number, maxY: number }
465
- filteredMinMaxXYData: {
466
- minXDatum: ComputedLayoutDatumMultiValue
467
- maxXDatum: ComputedLayoutDatumMultiValue
468
- minYDatum: ComputedLayoutDatumMultiValue
469
- maxYDatum: ComputedLayoutDatumMultiValue
470
- }
471
- xAxis: DataFormatterAxis
472
- yAxis: DataFormatterAxis
473
- width: number
474
- height: number
475
- }): TransformData {
476
- // const flatData = data.flat()
477
-
478
- let translateX = 0
479
- let translateY = 0
480
- let scaleX = 0
481
- let scaleY = 0
482
-
483
- // // minX, maxX, filteredMinX, filteredMaxX
484
- // let filteredMinX = 0
485
- // let filteredMaxX = 0
486
- // let [minX, maxX] = getMinMax(flatData.map(d => d.value[0]))
487
- // if (minX === maxX) {
488
- // minX = maxX - 1 // 避免最大及最小值相同造成無法計算scale
489
- // }
490
- // if (xAxis.scaleDomain[0] === 'auto' && filteredMinX > 0) {
491
- // filteredMinX = 0
492
- // } else if (typeof xAxis.scaleDomain[0] === 'number') {
493
- // filteredMinX = xAxis.scaleDomain[0] as number
494
- // } else {
495
- // filteredMinX = minX
496
- // }
497
- // if (xAxis.scaleDomain[1] === 'auto' && filteredMaxX < 0) {
498
- // filteredMaxX = 0
499
- // } else if (typeof xAxis.scaleDomain[1] === 'number') {
500
- // filteredMaxX = xAxis.scaleDomain[1] as number
501
- // } else {
502
- // filteredMaxX = maxX
503
- // }
504
- // if (filteredMinX === filteredMaxX) {
505
- // filteredMinX = filteredMaxX - 1 // 避免最大及最小值相同造成無法計算scale
506
- // }
507
-
508
- // // minY, maxY, filteredMinY, filteredMaxY
509
- // let filteredMinY = 0
510
- // let filteredMaxY = 0
511
- // let [minY, maxY] = getMinMax(flatData.map(d => d.value[1]))
512
- // console.log('filteredMinMaxXYData', filteredMinMaxXYData)
513
- let { minX, maxX, minY, maxY } = minMaxXY
514
- // console.log({ minX, maxX, minY, maxY })
515
- let filteredMinX = filteredMinMaxXYData.minXDatum.value[0] ?? 0
516
- let filteredMaxX = filteredMinMaxXYData.maxXDatum.value[0] ?? 0
517
- let filteredMinY = filteredMinMaxXYData.minYDatum.value[1] ?? 0
518
- let filteredMaxY = filteredMinMaxXYData.maxYDatum.value[1] ?? 0
519
-
520
- // if (yAxis.scaleDomain[0] === 'auto' && filteredMinY > 0) {
521
- // filteredMinY = 0
522
- // } else if (typeof yAxis.scaleDomain[0] === 'number') {
523
- // filteredMinY = yAxis.scaleDomain[0] as number
524
- // } else {
525
- // filteredMinY = minY
526
- // }
527
- // if (yAxis.scaleDomain[1] === 'auto' && filteredMaxY < 0) {
528
- // filteredMaxY = 0
529
- // } else if (typeof yAxis.scaleDomain[1] === 'number') {
530
- // filteredMaxY = yAxis.scaleDomain[1] as number
531
- // } else {
532
- // filteredMaxY = maxY
533
- // }
534
-
535
- // console.log({ minX, maxX, minY, maxY, filteredMinX, filteredMaxX, filteredMinY, filteredMaxY })
536
- if (filteredMinX === filteredMaxX && filteredMaxX === 0) {
537
- // 避免最大及最小值相同造成無法計算scale
538
- filteredMaxX = 1
539
- }
540
- if (filteredMinY === filteredMaxY && filteredMaxY === 0) {
541
- // 避免最大及最小值相同造成無法計算scale
542
- filteredMaxY = 1
543
- }
544
- if (minX === maxX && maxX === 0) {
545
- // 避免最大及最小值相同造成無法計算scale
546
- maxX = 1
547
- }
548
- if (minY === maxY && maxY === 0) {
549
- // 避免最大及最小值相同造成無法計算scale
550
- maxY = 1
551
- }
552
- // -- xScale --
553
- const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
554
- maxValue: filteredMaxX,
555
- minValue: filteredMinX,
556
- axisWidth: width,
557
- scaleDomain: xAxis.scaleDomain,
558
- scaleRange: xAxis.scaleRange
559
- })
560
-
561
- // -- translateX, scaleX --
562
- const rangeMinX = xScale(minX > 0 ? 0 : minX) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
563
- const rangeMaxX = xScale(maxX < 0 ? 0 : maxX) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
564
- translateX = rangeMinX
565
- const gWidth = rangeMaxX - rangeMinX
566
- scaleX = gWidth / width
567
- // console.log({ gWidth, width, rangeMaxX, rangeMinX, scaleX, translateX })
568
- // -- yScale --
569
- const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
570
- maxValue: filteredMaxY,
571
- minValue: filteredMinY,
572
- axisWidth: height,
573
- scaleDomain: yAxis.scaleDomain,
574
- scaleRange: yAxis.scaleRange,
575
- reverse: true
576
- })
577
-
578
- // -- translateY, scaleY --
579
- const rangeMinY = yScale(minY > 0 ? 0 : minY) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
580
- const rangeMaxY = yScale(maxY < 0 ? 0 : maxY) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
581
- translateY = rangeMaxY // 最大值的 y 最小(最上方)
582
- const gHeight = rangeMinY - rangeMaxY // 最大的 y 減最小的 y
583
- scaleY = gHeight / height
584
-
585
- return {
586
- translate: [translateX, translateY],
587
- scale: [scaleX, scaleY],
588
- rotate: 0,
589
- rotateX: 0,
590
- rotateY: 0,
591
- value: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`
592
- }
593
- }
594
-
595
- return new Observable(subscriber => {
596
- combineLatest({
597
- minMaxXY: minMaxXY$,
598
- filteredMinMaxXYData: filteredMinMaxXYData$,
599
- fullDataFormatter: fullDataFormatter$,
600
- layout: layout$
601
- }).pipe(
602
- takeUntil(destroy$),
603
- switchMap(async (d) => d),
604
- ).subscribe(data => {
605
- if (!data.filteredMinMaxXYData.minXDatum || !data.filteredMinMaxXYData.maxXDatum
606
- || data.filteredMinMaxXYData.minXDatum.value[0] == null || data.filteredMinMaxXYData.maxXDatum.value[0] == null
607
- || !data.filteredMinMaxXYData.minYDatum || !data.filteredMinMaxXYData.maxYDatum
608
- || data.filteredMinMaxXYData.minYDatum.value[1] == null || data.filteredMinMaxXYData.maxYDatum.value[1] == null
609
- ) {
610
- return
611
- }
612
- const dataAreaTransformData = calcDataAreaTransform({
613
- minMaxXY: data.minMaxXY,
614
- filteredMinMaxXYData: data.filteredMinMaxXYData,
615
- xAxis: data.fullDataFormatter.xAxis,
616
- yAxis: data.fullDataFormatter.yAxis,
617
- width: data.layout.width,
618
- height: data.layout.height
619
- })
620
-
621
- // console.log('dataAreaTransformData', dataAreaTransformData)
622
-
623
- subscriber.next(dataAreaTransformData)
624
- })
625
-
626
- return function unscbscribe () {
627
- destroy$.next(undefined)
628
- }
629
- })
630
- }
631
-
632
- export const multiValueGraphicReverseScaleObservable = ({ multiValueContainerPosition$, multiValueGraphicTransform$ }: {
633
- multiValueContainerPosition$: Observable<ContainerPositionScaled[]>
634
- // multiValueAxesTransform$: Observable<TransformData>
635
- multiValueGraphicTransform$: Observable<TransformData>
636
- }): Observable<[number, number][]> => {
637
- return combineLatest({
638
- multiValueContainerPosition: multiValueContainerPosition$,
639
- // multiValueAxesTransform: multiValueAxesTransform$,
640
- multiValueGraphicTransform: multiValueGraphicTransform$,
641
- }).pipe(
642
- switchMap(async (d) => d),
643
- map(data => {
644
- // if (data.multiValueAxesTransform.rotate == 0 || data.multiValueAxesTransform.rotate == 180) {
645
- return data.multiValueContainerPosition.map((series, seriesIndex) => {
646
- return [
647
- 1 / data.multiValueGraphicTransform.scale[0] / data.multiValueContainerPosition[seriesIndex].scale[0],
648
- 1 / data.multiValueGraphicTransform.scale[1] / data.multiValueContainerPosition[seriesIndex].scale[1],
649
- ]
650
- })
651
- // } else {
652
- // return data.multiValueContainerPosition.map((series, seriesIndex) => {
653
- // // 由於有垂直的旋轉,所以外層 (container) x和y的scale要互換
654
- // return [
655
- // 1 / data.multiValueGraphicTransform.scale[0] / data.multiValueContainerPosition[seriesIndex].scale[1],
656
- // 1 / data.multiValueGraphicTransform.scale[1] / data.multiValueContainerPosition[seriesIndex].scale[0],
657
- // ]
658
- // })
659
- // }
660
- }),
661
- )
662
- }
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
+ }