@orbcharts/core 3.0.5 → 3.0.6

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 (68) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-core.es.js +2217 -2164
  3. package/dist/orbcharts-core.umd.js +5 -5
  4. package/lib/core-types.ts +7 -7
  5. package/package.json +46 -46
  6. package/src/AbstractChart.ts +57 -57
  7. package/src/GridChart.ts +24 -24
  8. package/src/MultiGridChart.ts +24 -24
  9. package/src/MultiValueChart.ts +24 -24
  10. package/src/RelationshipChart.ts +24 -24
  11. package/src/SeriesChart.ts +24 -24
  12. package/src/TreeChart.ts +24 -24
  13. package/src/base/createBaseChart.ts +524 -524
  14. package/src/base/createBasePlugin.ts +154 -154
  15. package/src/base/validators/chartOptionsValidator.ts +23 -23
  16. package/src/base/validators/chartParamsValidator.ts +133 -133
  17. package/src/base/validators/elementValidator.ts +13 -13
  18. package/src/base/validators/pluginsValidator.ts +14 -14
  19. package/src/defaults.ts +284 -284
  20. package/src/defineGridPlugin.ts +3 -3
  21. package/src/defineMultiGridPlugin.ts +3 -3
  22. package/src/defineMultiValuePlugin.ts +3 -3
  23. package/src/defineNoneDataPlugin.ts +4 -4
  24. package/src/defineRelationshipPlugin.ts +3 -3
  25. package/src/defineSeriesPlugin.ts +3 -3
  26. package/src/defineTreePlugin.ts +3 -3
  27. package/src/grid/computedDataFn.ts +129 -129
  28. package/src/grid/contextObserverCallback.ts +209 -201
  29. package/src/grid/dataFormatterValidator.ts +125 -125
  30. package/src/grid/dataValidator.ts +12 -12
  31. package/src/grid/gridObservables.ts +698 -694
  32. package/src/index.ts +20 -20
  33. package/src/multiGrid/computedDataFn.ts +123 -123
  34. package/src/multiGrid/contextObserverCallback.ts +109 -75
  35. package/src/multiGrid/dataFormatterValidator.ts +120 -120
  36. package/src/multiGrid/dataValidator.ts +12 -12
  37. package/src/multiGrid/multiGridObservables.ts +366 -357
  38. package/src/multiValue/computedDataFn.ts +113 -113
  39. package/src/multiValue/contextObserverCallback.ts +328 -328
  40. package/src/multiValue/dataFormatterValidator.ts +94 -94
  41. package/src/multiValue/dataValidator.ts +12 -12
  42. package/src/multiValue/multiValueObservables.ts +865 -865
  43. package/src/relationship/computedDataFn.ts +159 -159
  44. package/src/relationship/contextObserverCallback.ts +80 -80
  45. package/src/relationship/dataFormatterValidator.ts +13 -13
  46. package/src/relationship/dataValidator.ts +13 -13
  47. package/src/relationship/relationshipObservables.ts +84 -84
  48. package/src/series/computedDataFn.ts +88 -88
  49. package/src/series/contextObserverCallback.ts +132 -132
  50. package/src/series/dataFormatterValidator.ts +46 -46
  51. package/src/series/dataValidator.ts +12 -12
  52. package/src/series/seriesObservables.ts +209 -209
  53. package/src/tree/computedDataFn.ts +129 -129
  54. package/src/tree/contextObserverCallback.ts +58 -58
  55. package/src/tree/dataFormatterValidator.ts +13 -13
  56. package/src/tree/dataValidator.ts +13 -13
  57. package/src/tree/treeObservables.ts +105 -105
  58. package/src/utils/commonUtils.ts +55 -55
  59. package/src/utils/d3Scale.ts +198 -198
  60. package/src/utils/errorMessage.ts +40 -40
  61. package/src/utils/index.ts +3 -3
  62. package/src/utils/observables.ts +308 -308
  63. package/src/utils/orbchartsUtils.ts +396 -396
  64. package/src/utils/validator.ts +126 -126
  65. package/tsconfig.base.json +13 -13
  66. package/tsconfig.json +2 -2
  67. package/vite-env.d.ts +6 -6
  68. package/vite.config.js +22 -22
@@ -1,865 +1,865 @@
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 categoryLabelsObservable = ({ computedData$, fullDataFormatter$ }: {
145
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
146
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
147
- }) => {
148
- return computedData$.pipe(
149
- map(data => {
150
- return data
151
- .map(d => d[0] ? d[0].categoryLabel : '')
152
- // .filter(d => d != null && d != '')
153
- }),
154
- distinctUntilChanged((a, b) => {
155
- return JSON.stringify(a) === JSON.stringify(b)
156
- }),
157
- )
158
- }
159
-
160
- export const visibleComputedDataObservable = ({ computedData$ }: { computedData$: Observable<ComputedDataTypeMap<'multiValue'>> }) => {
161
- return computedData$.pipe(
162
- map(data => {
163
- return data
164
- .map(categoryData => {
165
- return categoryData.filter(d => d.visible == true)
166
- })
167
- .filter(categoryData => {
168
- return categoryData.length > 0
169
- })
170
- })
171
- )
172
- }
173
-
174
- export const ordinalScaleDomainObservable = ({ visibleComputedData$, fullDataFormatter$ }: {
175
- visibleComputedData$: Observable<ComputedDataMultiValue>
176
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
177
- }) => {
178
- return combineLatest({
179
- visibleComputedData: visibleComputedData$,
180
- fullDataFormatter: fullDataFormatter$,
181
- }).pipe(
182
- map(data => {
183
- let maxValue: number = data.visibleComputedData[0] && data.visibleComputedData[0][0] && data.visibleComputedData[0][0].value.length
184
- ? data.visibleComputedData[0][0].value.length - 1
185
- : 0
186
- const scaleDomain: [number, number] = [
187
- data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[0] === 'min'
188
- ? 0
189
- : data.fullDataFormatter.xAxis.scaleDomain[0] as number,
190
- data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[1] === 'max'
191
- ? maxValue
192
- : data.fullDataFormatter.xAxis.scaleDomain[1] as number
193
- ]
194
- return scaleDomain
195
- }),
196
- distinctUntilChanged((a, b) => a[0] === b[0] && a[1] === b[1])
197
- )
198
- }
199
-
200
- export const visibleComputedSumDataObservable = ({ visibleComputedData$, ordinalScaleDomain$ }: {
201
- visibleComputedData$: Observable<ComputedDataMultiValue>
202
- // fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
203
- ordinalScaleDomain$: Observable<[number, number]>
204
- }) => {
205
-
206
- return combineLatest({
207
- visibleComputedData: visibleComputedData$,
208
- ordinalScaleDomain: ordinalScaleDomain$,
209
- }).pipe(
210
- map(data => {
211
- return data.visibleComputedData.map(categoryData => {
212
- return categoryData
213
- .map((d, i) => {
214
- let newDatum = d as ComputedDatumWithSumMultiValue
215
- // 新增總計資料欄位
216
- newDatum.sum = newDatum.value
217
- // 只加總範圍內的
218
- .filter((d, i) => i >= data.ordinalScaleDomain[0] && i <= data.ordinalScaleDomain[1])
219
- .reduce((acc, curr) => acc + curr, 0)
220
- return newDatum
221
- })
222
- })
223
- })
224
- )
225
- }
226
-
227
- // Ranking資料 - 用 value[index] 排序
228
- export const visibleComputedRankingByIndexDataObservable = ({ xyValueIndex$, isCategorySeprate$, visibleComputedData$ }: {
229
- xyValueIndex$: Observable<[number, number]>
230
- isCategorySeprate$: Observable<boolean>
231
- visibleComputedData$: Observable<ComputedDatumMultiValue[][]>
232
- }) => {
233
-
234
- return combineLatest({
235
- isCategorySeprate: isCategorySeprate$,
236
- xyValueIndex: xyValueIndex$,
237
- visibleComputedData: visibleComputedData$
238
- }).pipe(
239
- switchMap(async d => d),
240
- map(data => {
241
- const xValueIndex = data.xyValueIndex[0]
242
- // -- category 分開 --
243
- if (data.isCategorySeprate) {
244
- return data.visibleComputedData
245
- .map(categoryData => {
246
- return categoryData
247
- .sort((a, b) => {
248
- const bValue = b.value[xValueIndex] ?? - Infinity // - Infinity 為最小值
249
- const aValue = a.value[xValueIndex] ?? - Infinity
250
-
251
- return bValue - aValue
252
- })
253
- })
254
- // -- 用 value[index] 排序 --
255
- } else {
256
- return [
257
- data.visibleComputedData
258
- .flat()
259
- .sort((a, b) => {
260
- const bValue = b.value[xValueIndex] ?? - Infinity // - Infinity 為最小值
261
- const aValue = a.value[xValueIndex] ?? - Infinity
262
-
263
- return bValue - aValue
264
- })
265
- ]
266
- }
267
- })
268
- )
269
- }
270
-
271
- // Ranking資料 - 用所有 valueIndex 加總資料排序
272
- export const visibleComputedRankingBySumDataObservable = ({ isCategorySeprate$, visibleComputedSumData$ }: {
273
- isCategorySeprate$: Observable<boolean>
274
- // visibleComputedData$: Observable<ComputedDatumMultiValue[][]>
275
- visibleComputedSumData$: Observable<ComputedDatumWithSumMultiValue[][]>
276
- }) => {
277
-
278
- return combineLatest({
279
- isCategorySeprate: isCategorySeprate$,
280
- visibleComputedSumData: visibleComputedSumData$
281
- }).pipe(
282
- switchMap(async d => d),
283
- map(data => {
284
- // -- category 分開 --
285
- if (data.isCategorySeprate) {
286
- return data.visibleComputedSumData
287
- .map(categoryData => {
288
- return categoryData
289
- .sort((a, b) => b.sum - a.sum)
290
- })
291
- // -- 用 value[index] 排序 --
292
- } else {
293
- return [
294
- data.visibleComputedSumData
295
- .flat()
296
- .sort((a, b) => b.sum - a.sum)
297
- ]
298
- }
299
- })
300
- )
301
- }
302
-
303
- export const visibleComputedXYDataObservable = ({ computedXYData$ }: { computedXYData$: Observable<ComputedXYDataMultiValue> }) => {
304
- return computedXYData$.pipe(
305
- map(data => {
306
- return data
307
- .map(categoryData => {
308
- return categoryData.filter(d => d.visible == true)
309
- })
310
- .filter(categoryData => {
311
- return categoryData.length > 0
312
- })
313
- })
314
- )
315
- }
316
-
317
- // 所有container位置(對應category)
318
- export const containerPositionObservable = ({ computedData$, fullDataFormatter$, layout$ }: {
319
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
320
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
321
- layout$: Observable<Layout>
322
- }): Observable<ContainerPositionScaled[]> => {
323
-
324
- const containerPosition$ = combineLatest({
325
- computedData: computedData$,
326
- fullDataFormatter: fullDataFormatter$,
327
- layout: layout$,
328
- }).pipe(
329
- switchMap(async (d) => d),
330
- map(data => {
331
- // 無資料時回傳預設container位置
332
- if (data.computedData.length === 0) {
333
- const defaultPositionArr: ContainerPositionScaled[] = [
334
- {
335
- "slotIndex": 0,
336
- "rowIndex": 0,
337
- "columnIndex": 0,
338
- "translate": [0, 0],
339
- "scale": [1, 1]
340
- }
341
- ]
342
- return defaultPositionArr
343
- }
344
- if (data.fullDataFormatter.separateCategory) {
345
- // -- 依slotIndexes計算 --
346
- return calcContainerPositionScaled(data.layout, data.fullDataFormatter.container, data.computedData.length)
347
- // return data.computedData.map((seriesData, seriesIndex) => {
348
- // const columnIndex = seriesIndex % data.fullDataFormatter.container.columnAmount
349
- // const rowIndex = Math.floor(seriesIndex / data.fullDataFormatter.container.columnAmount)
350
- // const { translate, scale } = calcMultiValueContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
351
- // return {
352
- // slotIndex: seriesIndex,
353
- // rowIndex,
354
- // columnIndex,
355
- // translate,
356
- // scale,
357
- // }
358
- // })
359
- } else {
360
- // -- 無拆分 --
361
- const containerPositionArr = calcContainerPositionScaled(data.layout, data.fullDataFormatter.container, 1)
362
- return data.computedData.map((d, i) => containerPositionArr[0]) // 每個series相同位置
363
- // const columnIndex = 0
364
- // const rowIndex = 0
365
- // return data.computedData.map((seriesData, seriesIndex) => {
366
- // const { translate, scale } = calcMultiValueContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
367
- // return {
368
- // slotIndex: 0,
369
- // rowIndex,
370
- // columnIndex,
371
- // translate,
372
- // scale,
373
- // }
374
- // })
375
- }
376
- })
377
- )
378
-
379
- return containerPosition$
380
- }
381
-
382
- export const filteredXYMinMaxDataObservable = ({ visibleComputedXYData$, xyMinMax$, xyValueIndex$, fullDataFormatter$ }: {
383
- visibleComputedXYData$: Observable<ComputedXYDataMultiValue>
384
- xyMinMax$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
385
- xyValueIndex$: Observable<[number, number]>
386
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
387
- }) => {
388
- return combineLatest({
389
- visibleComputedXYData: visibleComputedXYData$,
390
- xyMinMax: xyMinMax$,
391
- xyValueIndex: xyValueIndex$,
392
- fullDataFormatter: fullDataFormatter$,
393
- }).pipe(
394
- map(data => {
395
- // 所有可見資料依 dataFormatter 的 scale 設定篩選出最大小值
396
- const { minX, maxX, minY, maxY } = (() => {
397
-
398
- let { minX, maxX, minY, maxY } = data.xyMinMax
399
-
400
- if (data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' && minX > 0) {
401
- minX = 0
402
- } else if (typeof data.fullDataFormatter.xAxis.scaleDomain[0] === 'number') {
403
- minX = data.fullDataFormatter.xAxis.scaleDomain[0] as number
404
- }
405
- if (data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' && maxX < 0) {
406
- maxX = 0
407
- } else if (typeof data.fullDataFormatter.xAxis.scaleDomain[1] === 'number') {
408
- maxX = data.fullDataFormatter.xAxis.scaleDomain[1] as number
409
- }
410
- if (data.fullDataFormatter.yAxis.scaleDomain[0] === 'auto' && minY > 0) {
411
- minY = 0
412
- } else if (typeof data.fullDataFormatter.yAxis.scaleDomain[0] === 'number') {
413
- minY = data.fullDataFormatter.yAxis.scaleDomain[0] as number
414
- }
415
- if (data.fullDataFormatter.yAxis.scaleDomain[1] === 'auto' && maxY < 0) {
416
- maxY = 0
417
- } else if (typeof data.fullDataFormatter.yAxis.scaleDomain[1] === 'number') {
418
- maxY = data.fullDataFormatter.yAxis.scaleDomain[1] as number
419
- }
420
-
421
- return { minX, maxX, minY, maxY }
422
- })()
423
- // console.log({ minX, maxX, minY, maxY })
424
- let datumList: ComputedXYDatumMultiValue[] = []
425
- let minXDatum: ComputedXYDatumMultiValue | null = null
426
- let maxXDatum: ComputedXYDatumMultiValue | null = null
427
- let minYDatum: ComputedXYDatumMultiValue | null = null
428
- let maxYDatum: ComputedXYDatumMultiValue | null = null
429
- // console.log('data.visibleComputedXYData', data.visibleComputedXYData)
430
- // minX, maxX, minY, maxY 範圍內的最大最小值資料
431
- // console.log({ minX, maxX, minY, maxY })
432
- for (let categoryData of data.visibleComputedXYData) {
433
- for (let datum of categoryData) {
434
- const xValue = datum.value[data.xyValueIndex[0]]
435
- const yValue = datum.value[data.xyValueIndex[1]]
436
- // 比較矩形範圍(所以 minX, maxX, minY, maxY 要同時比較)
437
- if (xValue >= minX && xValue <= maxX && yValue >= minY && yValue <= maxY) {
438
- datumList.push(datum)
439
- if (minXDatum == null || xValue < minXDatum.value[data.xyValueIndex[0]]) {
440
- minXDatum = datum
441
- }
442
- if (maxXDatum == null || xValue > maxXDatum.value[data.xyValueIndex[0]]) {
443
- maxXDatum = datum
444
- }
445
- if (minYDatum == null || yValue < minYDatum.value[data.xyValueIndex[1]]) {
446
- minYDatum = datum
447
- }
448
- if (maxYDatum == null || yValue > maxYDatum.value[data.xyValueIndex[1]]) {
449
- maxYDatum = datum
450
- }
451
- }
452
- }
453
- }
454
-
455
- return {
456
- datumList,
457
- minXDatum,
458
- maxXDatum,
459
- minYDatum,
460
- maxYDatum
461
- }
462
- })
463
- )
464
- }
465
-
466
- export const graphicTransformObservable = ({ xyMinMax$, xyValueIndex$, filteredXYMinMaxData$, fullDataFormatter$, layout$ }: {
467
- xyMinMax$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
468
- xyValueIndex$: Observable<[number, number]>
469
- filteredXYMinMaxData$: Observable<{
470
- minXDatum: ComputedXYDatumMultiValue
471
- maxXDatum: ComputedXYDatumMultiValue
472
- minYDatum: ComputedXYDatumMultiValue
473
- maxYDatum: ComputedXYDatumMultiValue
474
- }>
475
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
476
- layout$: Observable<Layout>
477
- }): Observable<TransformData> => {
478
- const destroy$ = new Subject()
479
-
480
- function calcDataAreaTransform ({ xyMinMax, xyValueIndex, filteredXYMinMaxData, xAxis, yAxis, width, height }: {
481
- xyMinMax: { minX: number, maxX: number, minY: number, maxY: number }
482
- xyValueIndex: [number, number]
483
- filteredXYMinMaxData: {
484
- minXDatum: ComputedXYDatumMultiValue
485
- maxXDatum: ComputedXYDatumMultiValue
486
- minYDatum: ComputedXYDatumMultiValue
487
- maxYDatum: ComputedXYDatumMultiValue
488
- }
489
- xAxis: DataFormatterXYAxis
490
- yAxis: DataFormatterXYAxis
491
- width: number
492
- height: number
493
- }): TransformData {
494
- // const flatData = data.flat()
495
-
496
- let translateX = 0
497
- let translateY = 0
498
- let scaleX = 0
499
- let scaleY = 0
500
-
501
- // // minX, maxX, filteredMinX, filteredMaxX
502
- // let filteredMinX = 0
503
- // let filteredMaxX = 0
504
- // let [minX, maxX] = getMinMax(flatData.map(d => d.value[0]))
505
- // if (minX === maxX) {
506
- // minX = maxX - 1 // 避免最大及最小值相同造成無法計算scale
507
- // }
508
- // if (xAxis.scaleDomain[0] === 'auto' && filteredMinX > 0) {
509
- // filteredMinX = 0
510
- // } else if (typeof xAxis.scaleDomain[0] === 'number') {
511
- // filteredMinX = xAxis.scaleDomain[0] as number
512
- // } else {
513
- // filteredMinX = minX
514
- // }
515
- // if (xAxis.scaleDomain[1] === 'auto' && filteredMaxX < 0) {
516
- // filteredMaxX = 0
517
- // } else if (typeof xAxis.scaleDomain[1] === 'number') {
518
- // filteredMaxX = xAxis.scaleDomain[1] as number
519
- // } else {
520
- // filteredMaxX = maxX
521
- // }
522
- // if (filteredMinX === filteredMaxX) {
523
- // filteredMinX = filteredMaxX - 1 // 避免最大及最小值相同造成無法計算scale
524
- // }
525
-
526
- // // minY, maxY, filteredMinY, filteredMaxY
527
- // let filteredMinY = 0
528
- // let filteredMaxY = 0
529
- // let [minY, maxY] = getMinMax(flatData.map(d => d.value[1]))
530
- // console.log('filteredXYMinMaxData', filteredXYMinMaxData)
531
- let { minX, maxX, minY, maxY } = xyMinMax
532
- // console.log({ minX, maxX, minY, maxY })
533
- let filteredMinX = filteredXYMinMaxData.minXDatum.value[xyValueIndex[0]] ?? 0
534
- let filteredMaxX = filteredXYMinMaxData.maxXDatum.value[xyValueIndex[0]] ?? 0
535
- let filteredMinY = filteredXYMinMaxData.minYDatum.value[xyValueIndex[1]] ?? 0
536
- let filteredMaxY = filteredXYMinMaxData.maxYDatum.value[xyValueIndex[1]] ?? 0
537
-
538
- // if (yAxis.scaleDomain[0] === 'auto' && filteredMinY > 0) {
539
- // filteredMinY = 0
540
- // } else if (typeof yAxis.scaleDomain[0] === 'number') {
541
- // filteredMinY = yAxis.scaleDomain[0] as number
542
- // } else {
543
- // filteredMinY = minY
544
- // }
545
- // if (yAxis.scaleDomain[1] === 'auto' && filteredMaxY < 0) {
546
- // filteredMaxY = 0
547
- // } else if (typeof yAxis.scaleDomain[1] === 'number') {
548
- // filteredMaxY = yAxis.scaleDomain[1] as number
549
- // } else {
550
- // filteredMaxY = maxY
551
- // }
552
-
553
- // console.log({ minX, maxX, minY, maxY, filteredMinX, filteredMaxX, filteredMinY, filteredMaxY })
554
- if (filteredMinX === filteredMaxX && filteredMaxX === 0) {
555
- // 避免最大及最小值相同造成無法計算scale
556
- filteredMaxX = 1
557
- }
558
- if (filteredMinY === filteredMaxY && filteredMaxY === 0) {
559
- // 避免最大及最小值相同造成無法計算scale
560
- filteredMaxY = 1
561
- }
562
- if (minX === maxX && maxX === 0) {
563
- // 避免最大及最小值相同造成無法計算scale
564
- maxX = 1
565
- }
566
- if (minY === maxY && maxY === 0) {
567
- // 避免最大及最小值相同造成無法計算scale
568
- maxY = 1
569
- }
570
- // -- xScale --
571
- const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
572
- maxValue: filteredMaxX,
573
- minValue: filteredMinX,
574
- axisWidth: width,
575
- scaleDomain: xAxis.scaleDomain,
576
- scaleRange: xAxis.scaleRange
577
- })
578
-
579
- // -- translateX, scaleX --
580
- const rangeMinX = xScale(minX > 0 ? 0 : minX) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
581
- const rangeMaxX = xScale(maxX < 0 ? 0 : maxX) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
582
- translateX = rangeMinX
583
- const gWidth = rangeMaxX - rangeMinX
584
- scaleX = gWidth / width
585
- // console.log({ gWidth, width, rangeMaxX, rangeMinX, scaleX, translateX })
586
- // -- yScale --
587
- const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
588
- maxValue: filteredMaxY,
589
- minValue: filteredMinY,
590
- axisWidth: height,
591
- scaleDomain: yAxis.scaleDomain,
592
- scaleRange: yAxis.scaleRange,
593
- reverse: true
594
- })
595
-
596
- // -- translateY, scaleY --
597
- const rangeMinY = yScale(minY > 0 ? 0 : minY) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
598
- const rangeMaxY = yScale(maxY < 0 ? 0 : maxY) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
599
- translateY = rangeMaxY // 最大值的 y 最小(最上方)
600
- const gHeight = rangeMinY - rangeMaxY // 最大的 y 減最小的 y
601
- scaleY = gHeight / height
602
-
603
- return {
604
- translate: [translateX, translateY],
605
- scale: [scaleX, scaleY],
606
- rotate: 0,
607
- rotateX: 0,
608
- rotateY: 0,
609
- value: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`
610
- }
611
- }
612
-
613
- return new Observable(subscriber => {
614
- combineLatest({
615
- xyMinMax: xyMinMax$,
616
- xyValueIndex: xyValueIndex$,
617
- filteredXYMinMaxData: filteredXYMinMaxData$,
618
- fullDataFormatter: fullDataFormatter$,
619
- layout: layout$
620
- }).pipe(
621
- takeUntil(destroy$),
622
- switchMap(async (d) => d),
623
- ).subscribe(data => {
624
- if (!data.filteredXYMinMaxData.minXDatum || !data.filteredXYMinMaxData.maxXDatum
625
- || data.filteredXYMinMaxData.minXDatum.value[data.xyValueIndex[0]] == null || data.filteredXYMinMaxData.maxXDatum.value[data.xyValueIndex[0]] == null
626
- || !data.filteredXYMinMaxData.minYDatum || !data.filteredXYMinMaxData.maxYDatum
627
- || data.filteredXYMinMaxData.minYDatum.value[data.xyValueIndex[1]] == null || data.filteredXYMinMaxData.maxYDatum.value[data.xyValueIndex[1]] == null
628
- ) {
629
- return
630
- }
631
- const dataAreaTransformData = calcDataAreaTransform({
632
- xyMinMax: data.xyMinMax,
633
- xyValueIndex: data.xyValueIndex,
634
- filteredXYMinMaxData: data.filteredXYMinMaxData,
635
- xAxis: data.fullDataFormatter.xAxis,
636
- yAxis: data.fullDataFormatter.yAxis,
637
- width: data.layout.width,
638
- height: data.layout.height
639
- })
640
-
641
- // console.log('dataAreaTransformData', dataAreaTransformData)
642
-
643
- subscriber.next(dataAreaTransformData)
644
- })
645
-
646
- return function unscbscribe () {
647
- destroy$.next(undefined)
648
- }
649
- })
650
- }
651
-
652
- export const graphicReverseScaleObservable = ({ containerPosition$, graphicTransform$ }: {
653
- containerPosition$: Observable<ContainerPositionScaled[]>
654
- // multiValueAxesTransform$: Observable<TransformData>
655
- graphicTransform$: Observable<TransformData>
656
- }): Observable<[number, number][]> => {
657
- return combineLatest({
658
- containerPosition: containerPosition$,
659
- // multiValueAxesTransform: multiValueAxesTransform$,
660
- graphicTransform: graphicTransform$,
661
- }).pipe(
662
- switchMap(async (d) => d),
663
- map(data => {
664
- // if (data.multiValueAxesTransform.rotate == 0 || data.multiValueAxesTransform.rotate == 180) {
665
- return data.containerPosition.map((series, seriesIndex) => {
666
- return [
667
- 1 / data.graphicTransform.scale[0] / data.containerPosition[seriesIndex].scale[0],
668
- 1 / data.graphicTransform.scale[1] / data.containerPosition[seriesIndex].scale[1],
669
- ]
670
- })
671
- // } else {
672
- // return data.containerPosition.map((series, seriesIndex) => {
673
- // // 由於有垂直的旋轉,所以外層 (container) x和y的scale要互換
674
- // return [
675
- // 1 / data.graphicTransform.scale[0] / data.containerPosition[seriesIndex].scale[1],
676
- // 1 / data.graphicTransform.scale[1] / data.containerPosition[seriesIndex].scale[0],
677
- // ]
678
- // })
679
- // }
680
- }),
681
- )
682
- }
683
-
684
- // X 軸圖軸 - 用 value[index]
685
- export const xScaleObservable = ({ visibleComputedSumData$, fullDataFormatter$, filteredXYMinMaxData$, containerSize$ }: {
686
- visibleComputedSumData$: Observable<ComputedDatumMultiValue[][]>
687
- fullDataFormatter$: Observable<DataFormatterMultiValue>
688
- filteredXYMinMaxData$: Observable<{
689
- minXDatum: ComputedXYDatumMultiValue
690
- maxXDatum: ComputedXYDatumMultiValue
691
- minYDatum: ComputedXYDatumMultiValue
692
- maxYDatum: ComputedXYDatumMultiValue
693
- }>
694
- // layout$: Observable<Layout>
695
- containerSize$: Observable<ContainerSize>
696
- }) => {
697
- return combineLatest({
698
- visibleComputedSumData: visibleComputedSumData$,
699
- fullDataFormatter: fullDataFormatter$,
700
- containerSize: containerSize$,
701
- // xyMinMax: xyMinMax$
702
- filteredXYMinMaxData: filteredXYMinMaxData$
703
- }).pipe(
704
- switchMap(async (d) => d),
705
- map(data => {
706
- const valueIndex = data.fullDataFormatter.xAxis.valueIndex
707
- if (!data.filteredXYMinMaxData.minXDatum || !data.filteredXYMinMaxData.maxXDatum
708
- // || data.filteredXYMinMaxData.minXDatum.value[valueIndex] == null || data.filteredXYMinMaxData.maxXDatum.value[valueIndex] == null
709
- ) {
710
- return
711
- }
712
- let maxValue: number | null = data.filteredXYMinMaxData.maxXDatum.value[valueIndex]
713
- let minValue: number | null = data.filteredXYMinMaxData.minXDatum.value[valueIndex]
714
- if (maxValue === minValue && maxValue === 0) {
715
- // 避免最大及最小值同等於 0 造成無法計算scale
716
- maxValue = 1
717
- }
718
-
719
- const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
720
- maxValue,
721
- minValue,
722
- axisWidth: data.containerSize.width,
723
- scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
724
- scaleRange: data.fullDataFormatter.xAxis.scaleRange,
725
- })
726
- return xScale
727
- })
728
- )
729
- }
730
-
731
- export const yScaleObservable = ({ fullDataFormatter$, filteredXYMinMaxData$, containerSize$ }: {
732
- fullDataFormatter$: Observable<DataFormatterMultiValue>
733
- filteredXYMinMaxData$: Observable<{
734
- minXDatum: ComputedXYDatumMultiValue
735
- maxXDatum: ComputedXYDatumMultiValue
736
- minYDatum: ComputedXYDatumMultiValue
737
- maxYDatum: ComputedXYDatumMultiValue
738
- }>
739
- containerSize$: Observable<ContainerSize>
740
- }) => {
741
- return combineLatest({
742
- fullDataFormatter: fullDataFormatter$,
743
- containerSize: containerSize$,
744
- // xyMinMax: observer.xyMinMax$
745
- filteredXYMinMaxData: filteredXYMinMaxData$
746
- }).pipe(
747
- switchMap(async (d) => d),
748
- map(data => {
749
- const valueIndex = data.fullDataFormatter.yAxis.valueIndex
750
- if (!data.filteredXYMinMaxData.minYDatum || !data.filteredXYMinMaxData.maxYDatum
751
- || data.filteredXYMinMaxData.minYDatum.value[valueIndex] == null || data.filteredXYMinMaxData.maxYDatum.value[valueIndex] == null
752
- ) {
753
- return
754
- }
755
- let maxValue = data.filteredXYMinMaxData.maxYDatum.value[valueIndex]
756
- let minValue = data.filteredXYMinMaxData.minYDatum.value[valueIndex]
757
- if (maxValue === minValue && maxValue === 0) {
758
- // 避免最大及最小值同等於 0 造成無法計算scale
759
- maxValue = 1
760
- }
761
-
762
- const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
763
- maxValue,
764
- minValue,
765
- axisWidth: data.containerSize.height,
766
- scaleDomain: data.fullDataFormatter.yAxis.scaleDomain,
767
- scaleRange: data.fullDataFormatter.yAxis.scaleRange,
768
- reverse: true
769
- })
770
- return yScale
771
- })
772
- )
773
- }
774
-
775
- export const ordinalPaddingObservable = ({ ordinalScaleDomain$, computedData$, containerSize$ }: {
776
- // fullDataFormatter$: Observable<DataFormatterMultiValue>
777
- ordinalScaleDomain$: Observable<[number, number]>
778
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
779
- containerSize$: Observable<ContainerSize>
780
- }) => {
781
- return combineLatest({
782
- ordinalScaleDomain: ordinalScaleDomain$,
783
- containerSize: containerSize$,
784
- computedData: computedData$
785
- }).pipe(
786
- switchMap(async (d) => d),
787
- map(data => {
788
- let maxValue: number = data.computedData[0] && data.computedData[0][0] && data.computedData[0][0].value.length
789
- ? data.computedData[0][0].value.length - 1
790
- : 0
791
- let minValue: number = 0
792
-
793
- let axisWidth = data.containerSize.width
794
- // const scaleDomain: [number, number] = [
795
- // data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[0] === 'min'
796
- // ? 0
797
- // : data.fullDataFormatter.xAxis.scaleDomain[0] as number,
798
- // data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[1] === 'max'
799
- // ? maxValue
800
- // : data.fullDataFormatter.xAxis.scaleDomain[1] as number
801
- // ]
802
- const distance = data.ordinalScaleDomain[1] - data.ordinalScaleDomain[0]
803
- // console.log('distance', distance)
804
- if (distance >= 1) {
805
- return axisWidth / (distance + 1) / 2
806
- } else {
807
- return 0
808
- }
809
- })
810
- )
811
- }
812
-
813
- // 定性的 X 軸圖軸 - 用 value 的 index 計算
814
- export const ordinalScaleObservable = ({ ordinalScaleDomain$, computedData$, containerSize$, ordinalPadding$ }: {
815
- // fullDataFormatter$: Observable<DataFormatterMultiValue>
816
- ordinalScaleDomain$: Observable<[number, number]>
817
- computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
818
- containerSize$: Observable<ContainerSize>
819
- ordinalPadding$: Observable<number>
820
- }) => {
821
- return combineLatest({
822
- // fullDataFormatter: fullDataFormatter$,
823
- ordinalScaleDomain: ordinalScaleDomain$,
824
- computedData: computedData$,
825
- containerSize: containerSize$,
826
- ordinalPadding: ordinalPadding$,
827
- }).pipe(
828
- switchMap(async (d) => d),
829
- map(data => {
830
- let maxValue: number = data.computedData[0] && data.computedData[0][0] && data.computedData[0][0].value.length
831
- ? data.computedData[0][0].value.length - 1
832
- : 0
833
- let minValue: number = 0
834
- let axisWidth = data.containerSize.width - (data.ordinalPadding * 2)
835
- // const scaleDomain: [number, number] = [
836
- // data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[0] === 'min'
837
- // ? 0
838
- // : data.fullDataFormatter.xAxis.scaleDomain[0] as number,
839
- // data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[1] === 'max'
840
- // ? maxValue
841
- // : data.fullDataFormatter.xAxis.scaleDomain[1] as number
842
- // ]
843
- // const distance = scaleDomain[1] - scaleDomain[0]
844
- // // console.log('distance', distance)
845
- // if (distance >= 1) {
846
- // axisWidth = axisWidth - (axisWidth / (distance + 1))
847
- // }
848
-
849
- const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
850
- maxValue,
851
- minValue,
852
- axisWidth,
853
- scaleDomain: data.ordinalScaleDomain,
854
- scaleRange: [0, 1],
855
- })
856
-
857
- // const xScale = createLabelToAxisScale({
858
- // axisLabels: new Array(maxValue + 1).fill('').map((d, i) => String(i)),
859
- // axisWidth: data.containerSize.width,
860
- // padding: 0.5
861
- // })
862
- return xScale
863
- })
864
- )
865
- }
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 categoryLabelsObservable = ({ computedData$, fullDataFormatter$ }: {
145
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
146
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
147
+ }) => {
148
+ return computedData$.pipe(
149
+ map(data => {
150
+ return data
151
+ .map(d => d[0] ? d[0].categoryLabel : '')
152
+ // .filter(d => d != null && d != '')
153
+ }),
154
+ distinctUntilChanged((a, b) => {
155
+ return JSON.stringify(a) === JSON.stringify(b)
156
+ }),
157
+ )
158
+ }
159
+
160
+ export const visibleComputedDataObservable = ({ computedData$ }: { computedData$: Observable<ComputedDataTypeMap<'multiValue'>> }) => {
161
+ return computedData$.pipe(
162
+ map(data => {
163
+ return data
164
+ .map(categoryData => {
165
+ return categoryData.filter(d => d.visible == true)
166
+ })
167
+ .filter(categoryData => {
168
+ return categoryData.length > 0
169
+ })
170
+ })
171
+ )
172
+ }
173
+
174
+ export const ordinalScaleDomainObservable = ({ visibleComputedData$, fullDataFormatter$ }: {
175
+ visibleComputedData$: Observable<ComputedDataMultiValue>
176
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
177
+ }) => {
178
+ return combineLatest({
179
+ visibleComputedData: visibleComputedData$,
180
+ fullDataFormatter: fullDataFormatter$,
181
+ }).pipe(
182
+ map(data => {
183
+ let maxValue: number = data.visibleComputedData[0] && data.visibleComputedData[0][0] && data.visibleComputedData[0][0].value.length
184
+ ? data.visibleComputedData[0][0].value.length - 1
185
+ : 0
186
+ const scaleDomain: [number, number] = [
187
+ data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[0] === 'min'
188
+ ? 0
189
+ : data.fullDataFormatter.xAxis.scaleDomain[0] as number,
190
+ data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[1] === 'max'
191
+ ? maxValue
192
+ : data.fullDataFormatter.xAxis.scaleDomain[1] as number
193
+ ]
194
+ return scaleDomain
195
+ }),
196
+ distinctUntilChanged((a, b) => a[0] === b[0] && a[1] === b[1])
197
+ )
198
+ }
199
+
200
+ export const visibleComputedSumDataObservable = ({ visibleComputedData$, ordinalScaleDomain$ }: {
201
+ visibleComputedData$: Observable<ComputedDataMultiValue>
202
+ // fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
203
+ ordinalScaleDomain$: Observable<[number, number]>
204
+ }) => {
205
+
206
+ return combineLatest({
207
+ visibleComputedData: visibleComputedData$,
208
+ ordinalScaleDomain: ordinalScaleDomain$,
209
+ }).pipe(
210
+ map(data => {
211
+ return data.visibleComputedData.map(categoryData => {
212
+ return categoryData
213
+ .map((d, i) => {
214
+ let newDatum = d as ComputedDatumWithSumMultiValue
215
+ // 新增總計資料欄位
216
+ newDatum.sum = newDatum.value
217
+ // 只加總範圍內的
218
+ .filter((d, i) => i >= data.ordinalScaleDomain[0] && i <= data.ordinalScaleDomain[1])
219
+ .reduce((acc, curr) => acc + curr, 0)
220
+ return newDatum
221
+ })
222
+ })
223
+ })
224
+ )
225
+ }
226
+
227
+ // Ranking資料 - 用 value[index] 排序
228
+ export const visibleComputedRankingByIndexDataObservable = ({ xyValueIndex$, isCategorySeprate$, visibleComputedData$ }: {
229
+ xyValueIndex$: Observable<[number, number]>
230
+ isCategorySeprate$: Observable<boolean>
231
+ visibleComputedData$: Observable<ComputedDatumMultiValue[][]>
232
+ }) => {
233
+
234
+ return combineLatest({
235
+ isCategorySeprate: isCategorySeprate$,
236
+ xyValueIndex: xyValueIndex$,
237
+ visibleComputedData: visibleComputedData$
238
+ }).pipe(
239
+ switchMap(async d => d),
240
+ map(data => {
241
+ const xValueIndex = data.xyValueIndex[0]
242
+ // -- category 分開 --
243
+ if (data.isCategorySeprate) {
244
+ return data.visibleComputedData
245
+ .map(categoryData => {
246
+ return categoryData
247
+ .sort((a, b) => {
248
+ const bValue = b.value[xValueIndex] ?? - Infinity // - Infinity 為最小值
249
+ const aValue = a.value[xValueIndex] ?? - Infinity
250
+
251
+ return bValue - aValue
252
+ })
253
+ })
254
+ // -- 用 value[index] 排序 --
255
+ } else {
256
+ return [
257
+ data.visibleComputedData
258
+ .flat()
259
+ .sort((a, b) => {
260
+ const bValue = b.value[xValueIndex] ?? - Infinity // - Infinity 為最小值
261
+ const aValue = a.value[xValueIndex] ?? - Infinity
262
+
263
+ return bValue - aValue
264
+ })
265
+ ]
266
+ }
267
+ })
268
+ )
269
+ }
270
+
271
+ // Ranking資料 - 用所有 valueIndex 加總資料排序
272
+ export const visibleComputedRankingBySumDataObservable = ({ isCategorySeprate$, visibleComputedSumData$ }: {
273
+ isCategorySeprate$: Observable<boolean>
274
+ // visibleComputedData$: Observable<ComputedDatumMultiValue[][]>
275
+ visibleComputedSumData$: Observable<ComputedDatumWithSumMultiValue[][]>
276
+ }) => {
277
+
278
+ return combineLatest({
279
+ isCategorySeprate: isCategorySeprate$,
280
+ visibleComputedSumData: visibleComputedSumData$
281
+ }).pipe(
282
+ switchMap(async d => d),
283
+ map(data => {
284
+ // -- category 分開 --
285
+ if (data.isCategorySeprate) {
286
+ return data.visibleComputedSumData
287
+ .map(categoryData => {
288
+ return categoryData
289
+ .sort((a, b) => b.sum - a.sum)
290
+ })
291
+ // -- 用 value[index] 排序 --
292
+ } else {
293
+ return [
294
+ data.visibleComputedSumData
295
+ .flat()
296
+ .sort((a, b) => b.sum - a.sum)
297
+ ]
298
+ }
299
+ })
300
+ )
301
+ }
302
+
303
+ export const visibleComputedXYDataObservable = ({ computedXYData$ }: { computedXYData$: Observable<ComputedXYDataMultiValue> }) => {
304
+ return computedXYData$.pipe(
305
+ map(data => {
306
+ return data
307
+ .map(categoryData => {
308
+ return categoryData.filter(d => d.visible == true)
309
+ })
310
+ .filter(categoryData => {
311
+ return categoryData.length > 0
312
+ })
313
+ })
314
+ )
315
+ }
316
+
317
+ // 所有container位置(對應category)
318
+ export const containerPositionObservable = ({ computedData$, fullDataFormatter$, layout$ }: {
319
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
320
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
321
+ layout$: Observable<Layout>
322
+ }): Observable<ContainerPositionScaled[]> => {
323
+
324
+ const containerPosition$ = combineLatest({
325
+ computedData: computedData$,
326
+ fullDataFormatter: fullDataFormatter$,
327
+ layout: layout$,
328
+ }).pipe(
329
+ switchMap(async (d) => d),
330
+ map(data => {
331
+ // 無資料時回傳預設container位置
332
+ if (data.computedData.length === 0) {
333
+ const defaultPositionArr: ContainerPositionScaled[] = [
334
+ {
335
+ "slotIndex": 0,
336
+ "rowIndex": 0,
337
+ "columnIndex": 0,
338
+ "translate": [0, 0],
339
+ "scale": [1, 1]
340
+ }
341
+ ]
342
+ return defaultPositionArr
343
+ }
344
+ if (data.fullDataFormatter.separateCategory) {
345
+ // -- 依slotIndexes計算 --
346
+ return calcContainerPositionScaled(data.layout, data.fullDataFormatter.container, data.computedData.length)
347
+ // return data.computedData.map((seriesData, seriesIndex) => {
348
+ // const columnIndex = seriesIndex % data.fullDataFormatter.container.columnAmount
349
+ // const rowIndex = Math.floor(seriesIndex / data.fullDataFormatter.container.columnAmount)
350
+ // const { translate, scale } = calcMultiValueContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
351
+ // return {
352
+ // slotIndex: seriesIndex,
353
+ // rowIndex,
354
+ // columnIndex,
355
+ // translate,
356
+ // scale,
357
+ // }
358
+ // })
359
+ } else {
360
+ // -- 無拆分 --
361
+ const containerPositionArr = calcContainerPositionScaled(data.layout, data.fullDataFormatter.container, 1)
362
+ return data.computedData.map((d, i) => containerPositionArr[0]) // 每個series相同位置
363
+ // const columnIndex = 0
364
+ // const rowIndex = 0
365
+ // return data.computedData.map((seriesData, seriesIndex) => {
366
+ // const { translate, scale } = calcMultiValueContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
367
+ // return {
368
+ // slotIndex: 0,
369
+ // rowIndex,
370
+ // columnIndex,
371
+ // translate,
372
+ // scale,
373
+ // }
374
+ // })
375
+ }
376
+ })
377
+ )
378
+
379
+ return containerPosition$
380
+ }
381
+
382
+ export const filteredXYMinMaxDataObservable = ({ visibleComputedXYData$, xyMinMax$, xyValueIndex$, fullDataFormatter$ }: {
383
+ visibleComputedXYData$: Observable<ComputedXYDataMultiValue>
384
+ xyMinMax$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
385
+ xyValueIndex$: Observable<[number, number]>
386
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
387
+ }) => {
388
+ return combineLatest({
389
+ visibleComputedXYData: visibleComputedXYData$,
390
+ xyMinMax: xyMinMax$,
391
+ xyValueIndex: xyValueIndex$,
392
+ fullDataFormatter: fullDataFormatter$,
393
+ }).pipe(
394
+ map(data => {
395
+ // 所有可見資料依 dataFormatter 的 scale 設定篩選出最大小值
396
+ const { minX, maxX, minY, maxY } = (() => {
397
+
398
+ let { minX, maxX, minY, maxY } = data.xyMinMax
399
+
400
+ if (data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' && minX > 0) {
401
+ minX = 0
402
+ } else if (typeof data.fullDataFormatter.xAxis.scaleDomain[0] === 'number') {
403
+ minX = data.fullDataFormatter.xAxis.scaleDomain[0] as number
404
+ }
405
+ if (data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' && maxX < 0) {
406
+ maxX = 0
407
+ } else if (typeof data.fullDataFormatter.xAxis.scaleDomain[1] === 'number') {
408
+ maxX = data.fullDataFormatter.xAxis.scaleDomain[1] as number
409
+ }
410
+ if (data.fullDataFormatter.yAxis.scaleDomain[0] === 'auto' && minY > 0) {
411
+ minY = 0
412
+ } else if (typeof data.fullDataFormatter.yAxis.scaleDomain[0] === 'number') {
413
+ minY = data.fullDataFormatter.yAxis.scaleDomain[0] as number
414
+ }
415
+ if (data.fullDataFormatter.yAxis.scaleDomain[1] === 'auto' && maxY < 0) {
416
+ maxY = 0
417
+ } else if (typeof data.fullDataFormatter.yAxis.scaleDomain[1] === 'number') {
418
+ maxY = data.fullDataFormatter.yAxis.scaleDomain[1] as number
419
+ }
420
+
421
+ return { minX, maxX, minY, maxY }
422
+ })()
423
+ // console.log({ minX, maxX, minY, maxY })
424
+ let datumList: ComputedXYDatumMultiValue[] = []
425
+ let minXDatum: ComputedXYDatumMultiValue | null = null
426
+ let maxXDatum: ComputedXYDatumMultiValue | null = null
427
+ let minYDatum: ComputedXYDatumMultiValue | null = null
428
+ let maxYDatum: ComputedXYDatumMultiValue | null = null
429
+ // console.log('data.visibleComputedXYData', data.visibleComputedXYData)
430
+ // minX, maxX, minY, maxY 範圍內的最大最小值資料
431
+ // console.log({ minX, maxX, minY, maxY })
432
+ for (let categoryData of data.visibleComputedXYData) {
433
+ for (let datum of categoryData) {
434
+ const xValue = datum.value[data.xyValueIndex[0]]
435
+ const yValue = datum.value[data.xyValueIndex[1]]
436
+ // 比較矩形範圍(所以 minX, maxX, minY, maxY 要同時比較)
437
+ if (xValue >= minX && xValue <= maxX && yValue >= minY && yValue <= maxY) {
438
+ datumList.push(datum)
439
+ if (minXDatum == null || xValue < minXDatum.value[data.xyValueIndex[0]]) {
440
+ minXDatum = datum
441
+ }
442
+ if (maxXDatum == null || xValue > maxXDatum.value[data.xyValueIndex[0]]) {
443
+ maxXDatum = datum
444
+ }
445
+ if (minYDatum == null || yValue < minYDatum.value[data.xyValueIndex[1]]) {
446
+ minYDatum = datum
447
+ }
448
+ if (maxYDatum == null || yValue > maxYDatum.value[data.xyValueIndex[1]]) {
449
+ maxYDatum = datum
450
+ }
451
+ }
452
+ }
453
+ }
454
+
455
+ return {
456
+ datumList,
457
+ minXDatum,
458
+ maxXDatum,
459
+ minYDatum,
460
+ maxYDatum
461
+ }
462
+ })
463
+ )
464
+ }
465
+
466
+ export const graphicTransformObservable = ({ xyMinMax$, xyValueIndex$, filteredXYMinMaxData$, fullDataFormatter$, layout$ }: {
467
+ xyMinMax$: Observable<{ minX: number, maxX: number, minY: number, maxY: number }>
468
+ xyValueIndex$: Observable<[number, number]>
469
+ filteredXYMinMaxData$: Observable<{
470
+ minXDatum: ComputedXYDatumMultiValue
471
+ maxXDatum: ComputedXYDatumMultiValue
472
+ minYDatum: ComputedXYDatumMultiValue
473
+ maxYDatum: ComputedXYDatumMultiValue
474
+ }>
475
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
476
+ layout$: Observable<Layout>
477
+ }): Observable<TransformData> => {
478
+ const destroy$ = new Subject()
479
+
480
+ function calcDataAreaTransform ({ xyMinMax, xyValueIndex, filteredXYMinMaxData, xAxis, yAxis, width, height }: {
481
+ xyMinMax: { minX: number, maxX: number, minY: number, maxY: number }
482
+ xyValueIndex: [number, number]
483
+ filteredXYMinMaxData: {
484
+ minXDatum: ComputedXYDatumMultiValue
485
+ maxXDatum: ComputedXYDatumMultiValue
486
+ minYDatum: ComputedXYDatumMultiValue
487
+ maxYDatum: ComputedXYDatumMultiValue
488
+ }
489
+ xAxis: DataFormatterXYAxis
490
+ yAxis: DataFormatterXYAxis
491
+ width: number
492
+ height: number
493
+ }): TransformData {
494
+ // const flatData = data.flat()
495
+
496
+ let translateX = 0
497
+ let translateY = 0
498
+ let scaleX = 0
499
+ let scaleY = 0
500
+
501
+ // // minX, maxX, filteredMinX, filteredMaxX
502
+ // let filteredMinX = 0
503
+ // let filteredMaxX = 0
504
+ // let [minX, maxX] = getMinMax(flatData.map(d => d.value[0]))
505
+ // if (minX === maxX) {
506
+ // minX = maxX - 1 // 避免最大及最小值相同造成無法計算scale
507
+ // }
508
+ // if (xAxis.scaleDomain[0] === 'auto' && filteredMinX > 0) {
509
+ // filteredMinX = 0
510
+ // } else if (typeof xAxis.scaleDomain[0] === 'number') {
511
+ // filteredMinX = xAxis.scaleDomain[0] as number
512
+ // } else {
513
+ // filteredMinX = minX
514
+ // }
515
+ // if (xAxis.scaleDomain[1] === 'auto' && filteredMaxX < 0) {
516
+ // filteredMaxX = 0
517
+ // } else if (typeof xAxis.scaleDomain[1] === 'number') {
518
+ // filteredMaxX = xAxis.scaleDomain[1] as number
519
+ // } else {
520
+ // filteredMaxX = maxX
521
+ // }
522
+ // if (filteredMinX === filteredMaxX) {
523
+ // filteredMinX = filteredMaxX - 1 // 避免最大及最小值相同造成無法計算scale
524
+ // }
525
+
526
+ // // minY, maxY, filteredMinY, filteredMaxY
527
+ // let filteredMinY = 0
528
+ // let filteredMaxY = 0
529
+ // let [minY, maxY] = getMinMax(flatData.map(d => d.value[1]))
530
+ // console.log('filteredXYMinMaxData', filteredXYMinMaxData)
531
+ let { minX, maxX, minY, maxY } = xyMinMax
532
+ // console.log({ minX, maxX, minY, maxY })
533
+ let filteredMinX = filteredXYMinMaxData.minXDatum.value[xyValueIndex[0]] ?? 0
534
+ let filteredMaxX = filteredXYMinMaxData.maxXDatum.value[xyValueIndex[0]] ?? 0
535
+ let filteredMinY = filteredXYMinMaxData.minYDatum.value[xyValueIndex[1]] ?? 0
536
+ let filteredMaxY = filteredXYMinMaxData.maxYDatum.value[xyValueIndex[1]] ?? 0
537
+
538
+ // if (yAxis.scaleDomain[0] === 'auto' && filteredMinY > 0) {
539
+ // filteredMinY = 0
540
+ // } else if (typeof yAxis.scaleDomain[0] === 'number') {
541
+ // filteredMinY = yAxis.scaleDomain[0] as number
542
+ // } else {
543
+ // filteredMinY = minY
544
+ // }
545
+ // if (yAxis.scaleDomain[1] === 'auto' && filteredMaxY < 0) {
546
+ // filteredMaxY = 0
547
+ // } else if (typeof yAxis.scaleDomain[1] === 'number') {
548
+ // filteredMaxY = yAxis.scaleDomain[1] as number
549
+ // } else {
550
+ // filteredMaxY = maxY
551
+ // }
552
+
553
+ // console.log({ minX, maxX, minY, maxY, filteredMinX, filteredMaxX, filteredMinY, filteredMaxY })
554
+ if (filteredMinX === filteredMaxX && filteredMaxX === 0) {
555
+ // 避免最大及最小值相同造成無法計算scale
556
+ filteredMaxX = 1
557
+ }
558
+ if (filteredMinY === filteredMaxY && filteredMaxY === 0) {
559
+ // 避免最大及最小值相同造成無法計算scale
560
+ filteredMaxY = 1
561
+ }
562
+ if (minX === maxX && maxX === 0) {
563
+ // 避免最大及最小值相同造成無法計算scale
564
+ maxX = 1
565
+ }
566
+ if (minY === maxY && maxY === 0) {
567
+ // 避免最大及最小值相同造成無法計算scale
568
+ maxY = 1
569
+ }
570
+ // -- xScale --
571
+ const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
572
+ maxValue: filteredMaxX,
573
+ minValue: filteredMinX,
574
+ axisWidth: width,
575
+ scaleDomain: xAxis.scaleDomain,
576
+ scaleRange: xAxis.scaleRange
577
+ })
578
+
579
+ // -- translateX, scaleX --
580
+ const rangeMinX = xScale(minX > 0 ? 0 : minX) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
581
+ const rangeMaxX = xScale(maxX < 0 ? 0 : maxX) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
582
+ translateX = rangeMinX
583
+ const gWidth = rangeMaxX - rangeMinX
584
+ scaleX = gWidth / width
585
+ // console.log({ gWidth, width, rangeMaxX, rangeMinX, scaleX, translateX })
586
+ // -- yScale --
587
+ const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
588
+ maxValue: filteredMaxY,
589
+ minValue: filteredMinY,
590
+ axisWidth: height,
591
+ scaleDomain: yAxis.scaleDomain,
592
+ scaleRange: yAxis.scaleRange,
593
+ reverse: true
594
+ })
595
+
596
+ // -- translateY, scaleY --
597
+ const rangeMinY = yScale(minY > 0 ? 0 : minY) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
598
+ const rangeMaxY = yScale(maxY < 0 ? 0 : maxY) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
599
+ translateY = rangeMaxY // 最大值的 y 最小(最上方)
600
+ const gHeight = rangeMinY - rangeMaxY // 最大的 y 減最小的 y
601
+ scaleY = gHeight / height
602
+
603
+ return {
604
+ translate: [translateX, translateY],
605
+ scale: [scaleX, scaleY],
606
+ rotate: 0,
607
+ rotateX: 0,
608
+ rotateY: 0,
609
+ value: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`
610
+ }
611
+ }
612
+
613
+ return new Observable(subscriber => {
614
+ combineLatest({
615
+ xyMinMax: xyMinMax$,
616
+ xyValueIndex: xyValueIndex$,
617
+ filteredXYMinMaxData: filteredXYMinMaxData$,
618
+ fullDataFormatter: fullDataFormatter$,
619
+ layout: layout$
620
+ }).pipe(
621
+ takeUntil(destroy$),
622
+ switchMap(async (d) => d),
623
+ ).subscribe(data => {
624
+ if (!data.filteredXYMinMaxData.minXDatum || !data.filteredXYMinMaxData.maxXDatum
625
+ || data.filteredXYMinMaxData.minXDatum.value[data.xyValueIndex[0]] == null || data.filteredXYMinMaxData.maxXDatum.value[data.xyValueIndex[0]] == null
626
+ || !data.filteredXYMinMaxData.minYDatum || !data.filteredXYMinMaxData.maxYDatum
627
+ || data.filteredXYMinMaxData.minYDatum.value[data.xyValueIndex[1]] == null || data.filteredXYMinMaxData.maxYDatum.value[data.xyValueIndex[1]] == null
628
+ ) {
629
+ return
630
+ }
631
+ const dataAreaTransformData = calcDataAreaTransform({
632
+ xyMinMax: data.xyMinMax,
633
+ xyValueIndex: data.xyValueIndex,
634
+ filteredXYMinMaxData: data.filteredXYMinMaxData,
635
+ xAxis: data.fullDataFormatter.xAxis,
636
+ yAxis: data.fullDataFormatter.yAxis,
637
+ width: data.layout.width,
638
+ height: data.layout.height
639
+ })
640
+
641
+ // console.log('dataAreaTransformData', dataAreaTransformData)
642
+
643
+ subscriber.next(dataAreaTransformData)
644
+ })
645
+
646
+ return function unscbscribe () {
647
+ destroy$.next(undefined)
648
+ }
649
+ })
650
+ }
651
+
652
+ export const graphicReverseScaleObservable = ({ containerPosition$, graphicTransform$ }: {
653
+ containerPosition$: Observable<ContainerPositionScaled[]>
654
+ // multiValueAxesTransform$: Observable<TransformData>
655
+ graphicTransform$: Observable<TransformData>
656
+ }): Observable<[number, number][]> => {
657
+ return combineLatest({
658
+ containerPosition: containerPosition$,
659
+ // multiValueAxesTransform: multiValueAxesTransform$,
660
+ graphicTransform: graphicTransform$,
661
+ }).pipe(
662
+ switchMap(async (d) => d),
663
+ map(data => {
664
+ // if (data.multiValueAxesTransform.rotate == 0 || data.multiValueAxesTransform.rotate == 180) {
665
+ return data.containerPosition.map((series, seriesIndex) => {
666
+ return [
667
+ 1 / data.graphicTransform.scale[0] / data.containerPosition[seriesIndex].scale[0],
668
+ 1 / data.graphicTransform.scale[1] / data.containerPosition[seriesIndex].scale[1],
669
+ ]
670
+ })
671
+ // } else {
672
+ // return data.containerPosition.map((series, seriesIndex) => {
673
+ // // 由於有垂直的旋轉,所以外層 (container) x和y的scale要互換
674
+ // return [
675
+ // 1 / data.graphicTransform.scale[0] / data.containerPosition[seriesIndex].scale[1],
676
+ // 1 / data.graphicTransform.scale[1] / data.containerPosition[seriesIndex].scale[0],
677
+ // ]
678
+ // })
679
+ // }
680
+ }),
681
+ )
682
+ }
683
+
684
+ // X 軸圖軸 - 用 value[index]
685
+ export const xScaleObservable = ({ visibleComputedSumData$, fullDataFormatter$, filteredXYMinMaxData$, containerSize$ }: {
686
+ visibleComputedSumData$: Observable<ComputedDatumMultiValue[][]>
687
+ fullDataFormatter$: Observable<DataFormatterMultiValue>
688
+ filteredXYMinMaxData$: Observable<{
689
+ minXDatum: ComputedXYDatumMultiValue
690
+ maxXDatum: ComputedXYDatumMultiValue
691
+ minYDatum: ComputedXYDatumMultiValue
692
+ maxYDatum: ComputedXYDatumMultiValue
693
+ }>
694
+ // layout$: Observable<Layout>
695
+ containerSize$: Observable<ContainerSize>
696
+ }) => {
697
+ return combineLatest({
698
+ visibleComputedSumData: visibleComputedSumData$,
699
+ fullDataFormatter: fullDataFormatter$,
700
+ containerSize: containerSize$,
701
+ // xyMinMax: xyMinMax$
702
+ filteredXYMinMaxData: filteredXYMinMaxData$
703
+ }).pipe(
704
+ switchMap(async (d) => d),
705
+ map(data => {
706
+ const valueIndex = data.fullDataFormatter.xAxis.valueIndex
707
+ if (!data.filteredXYMinMaxData.minXDatum || !data.filteredXYMinMaxData.maxXDatum
708
+ // || data.filteredXYMinMaxData.minXDatum.value[valueIndex] == null || data.filteredXYMinMaxData.maxXDatum.value[valueIndex] == null
709
+ ) {
710
+ return
711
+ }
712
+ let maxValue: number | null = data.filteredXYMinMaxData.maxXDatum.value[valueIndex]
713
+ let minValue: number | null = data.filteredXYMinMaxData.minXDatum.value[valueIndex]
714
+ if (maxValue === minValue && maxValue === 0) {
715
+ // 避免最大及最小值同等於 0 造成無法計算scale
716
+ maxValue = 1
717
+ }
718
+
719
+ const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
720
+ maxValue,
721
+ minValue,
722
+ axisWidth: data.containerSize.width,
723
+ scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
724
+ scaleRange: data.fullDataFormatter.xAxis.scaleRange,
725
+ })
726
+ return xScale
727
+ })
728
+ )
729
+ }
730
+
731
+ export const yScaleObservable = ({ fullDataFormatter$, filteredXYMinMaxData$, containerSize$ }: {
732
+ fullDataFormatter$: Observable<DataFormatterMultiValue>
733
+ filteredXYMinMaxData$: Observable<{
734
+ minXDatum: ComputedXYDatumMultiValue
735
+ maxXDatum: ComputedXYDatumMultiValue
736
+ minYDatum: ComputedXYDatumMultiValue
737
+ maxYDatum: ComputedXYDatumMultiValue
738
+ }>
739
+ containerSize$: Observable<ContainerSize>
740
+ }) => {
741
+ return combineLatest({
742
+ fullDataFormatter: fullDataFormatter$,
743
+ containerSize: containerSize$,
744
+ // xyMinMax: observer.xyMinMax$
745
+ filteredXYMinMaxData: filteredXYMinMaxData$
746
+ }).pipe(
747
+ switchMap(async (d) => d),
748
+ map(data => {
749
+ const valueIndex = data.fullDataFormatter.yAxis.valueIndex
750
+ if (!data.filteredXYMinMaxData.minYDatum || !data.filteredXYMinMaxData.maxYDatum
751
+ || data.filteredXYMinMaxData.minYDatum.value[valueIndex] == null || data.filteredXYMinMaxData.maxYDatum.value[valueIndex] == null
752
+ ) {
753
+ return
754
+ }
755
+ let maxValue = data.filteredXYMinMaxData.maxYDatum.value[valueIndex]
756
+ let minValue = data.filteredXYMinMaxData.minYDatum.value[valueIndex]
757
+ if (maxValue === minValue && maxValue === 0) {
758
+ // 避免最大及最小值同等於 0 造成無法計算scale
759
+ maxValue = 1
760
+ }
761
+
762
+ const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
763
+ maxValue,
764
+ minValue,
765
+ axisWidth: data.containerSize.height,
766
+ scaleDomain: data.fullDataFormatter.yAxis.scaleDomain,
767
+ scaleRange: data.fullDataFormatter.yAxis.scaleRange,
768
+ reverse: true
769
+ })
770
+ return yScale
771
+ })
772
+ )
773
+ }
774
+
775
+ export const ordinalPaddingObservable = ({ ordinalScaleDomain$, computedData$, containerSize$ }: {
776
+ // fullDataFormatter$: Observable<DataFormatterMultiValue>
777
+ ordinalScaleDomain$: Observable<[number, number]>
778
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
779
+ containerSize$: Observable<ContainerSize>
780
+ }) => {
781
+ return combineLatest({
782
+ ordinalScaleDomain: ordinalScaleDomain$,
783
+ containerSize: containerSize$,
784
+ computedData: computedData$
785
+ }).pipe(
786
+ switchMap(async (d) => d),
787
+ map(data => {
788
+ let maxValue: number = data.computedData[0] && data.computedData[0][0] && data.computedData[0][0].value.length
789
+ ? data.computedData[0][0].value.length - 1
790
+ : 0
791
+ let minValue: number = 0
792
+
793
+ let axisWidth = data.containerSize.width
794
+ // const scaleDomain: [number, number] = [
795
+ // data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[0] === 'min'
796
+ // ? 0
797
+ // : data.fullDataFormatter.xAxis.scaleDomain[0] as number,
798
+ // data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[1] === 'max'
799
+ // ? maxValue
800
+ // : data.fullDataFormatter.xAxis.scaleDomain[1] as number
801
+ // ]
802
+ const distance = data.ordinalScaleDomain[1] - data.ordinalScaleDomain[0]
803
+ // console.log('distance', distance)
804
+ if (distance >= 1) {
805
+ return axisWidth / (distance + 1) / 2
806
+ } else {
807
+ return 0
808
+ }
809
+ })
810
+ )
811
+ }
812
+
813
+ // 定性的 X 軸圖軸 - 用 value 的 index 計算
814
+ export const ordinalScaleObservable = ({ ordinalScaleDomain$, computedData$, containerSize$, ordinalPadding$ }: {
815
+ // fullDataFormatter$: Observable<DataFormatterMultiValue>
816
+ ordinalScaleDomain$: Observable<[number, number]>
817
+ computedData$: Observable<ComputedDataTypeMap<'multiValue'>>
818
+ containerSize$: Observable<ContainerSize>
819
+ ordinalPadding$: Observable<number>
820
+ }) => {
821
+ return combineLatest({
822
+ // fullDataFormatter: fullDataFormatter$,
823
+ ordinalScaleDomain: ordinalScaleDomain$,
824
+ computedData: computedData$,
825
+ containerSize: containerSize$,
826
+ ordinalPadding: ordinalPadding$,
827
+ }).pipe(
828
+ switchMap(async (d) => d),
829
+ map(data => {
830
+ let maxValue: number = data.computedData[0] && data.computedData[0][0] && data.computedData[0][0].value.length
831
+ ? data.computedData[0][0].value.length - 1
832
+ : 0
833
+ let minValue: number = 0
834
+ let axisWidth = data.containerSize.width - (data.ordinalPadding * 2)
835
+ // const scaleDomain: [number, number] = [
836
+ // data.fullDataFormatter.xAxis.scaleDomain[0] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[0] === 'min'
837
+ // ? 0
838
+ // : data.fullDataFormatter.xAxis.scaleDomain[0] as number,
839
+ // data.fullDataFormatter.xAxis.scaleDomain[1] === 'auto' || data.fullDataFormatter.xAxis.scaleDomain[1] === 'max'
840
+ // ? maxValue
841
+ // : data.fullDataFormatter.xAxis.scaleDomain[1] as number
842
+ // ]
843
+ // const distance = scaleDomain[1] - scaleDomain[0]
844
+ // // console.log('distance', distance)
845
+ // if (distance >= 1) {
846
+ // axisWidth = axisWidth - (axisWidth / (distance + 1))
847
+ // }
848
+
849
+ const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
850
+ maxValue,
851
+ minValue,
852
+ axisWidth,
853
+ scaleDomain: data.ordinalScaleDomain,
854
+ scaleRange: [0, 1],
855
+ })
856
+
857
+ // const xScale = createLabelToAxisScale({
858
+ // axisLabels: new Array(maxValue + 1).fill('').map((d, i) => String(i)),
859
+ // axisWidth: data.containerSize.width,
860
+ // padding: 0.5
861
+ // })
862
+ return xScale
863
+ })
864
+ )
865
+ }