@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.
- package/LICENSE +200 -200
- package/dist/orbcharts-core.es.js +2217 -2164
- package/dist/orbcharts-core.umd.js +5 -5
- package/lib/core-types.ts +7 -7
- package/package.json +46 -46
- package/src/AbstractChart.ts +57 -57
- package/src/GridChart.ts +24 -24
- package/src/MultiGridChart.ts +24 -24
- package/src/MultiValueChart.ts +24 -24
- package/src/RelationshipChart.ts +24 -24
- package/src/SeriesChart.ts +24 -24
- package/src/TreeChart.ts +24 -24
- package/src/base/createBaseChart.ts +524 -524
- package/src/base/createBasePlugin.ts +154 -154
- package/src/base/validators/chartOptionsValidator.ts +23 -23
- package/src/base/validators/chartParamsValidator.ts +133 -133
- package/src/base/validators/elementValidator.ts +13 -13
- package/src/base/validators/pluginsValidator.ts +14 -14
- package/src/defaults.ts +284 -284
- package/src/defineGridPlugin.ts +3 -3
- package/src/defineMultiGridPlugin.ts +3 -3
- package/src/defineMultiValuePlugin.ts +3 -3
- package/src/defineNoneDataPlugin.ts +4 -4
- package/src/defineRelationshipPlugin.ts +3 -3
- package/src/defineSeriesPlugin.ts +3 -3
- package/src/defineTreePlugin.ts +3 -3
- package/src/grid/computedDataFn.ts +129 -129
- package/src/grid/contextObserverCallback.ts +209 -201
- package/src/grid/dataFormatterValidator.ts +125 -125
- package/src/grid/dataValidator.ts +12 -12
- package/src/grid/gridObservables.ts +698 -694
- package/src/index.ts +20 -20
- package/src/multiGrid/computedDataFn.ts +123 -123
- package/src/multiGrid/contextObserverCallback.ts +109 -75
- package/src/multiGrid/dataFormatterValidator.ts +120 -120
- package/src/multiGrid/dataValidator.ts +12 -12
- package/src/multiGrid/multiGridObservables.ts +366 -357
- package/src/multiValue/computedDataFn.ts +113 -113
- package/src/multiValue/contextObserverCallback.ts +328 -328
- package/src/multiValue/dataFormatterValidator.ts +94 -94
- package/src/multiValue/dataValidator.ts +12 -12
- package/src/multiValue/multiValueObservables.ts +865 -865
- package/src/relationship/computedDataFn.ts +159 -159
- package/src/relationship/contextObserverCallback.ts +80 -80
- package/src/relationship/dataFormatterValidator.ts +13 -13
- package/src/relationship/dataValidator.ts +13 -13
- package/src/relationship/relationshipObservables.ts +84 -84
- package/src/series/computedDataFn.ts +88 -88
- package/src/series/contextObserverCallback.ts +132 -132
- package/src/series/dataFormatterValidator.ts +46 -46
- package/src/series/dataValidator.ts +12 -12
- package/src/series/seriesObservables.ts +209 -209
- package/src/tree/computedDataFn.ts +129 -129
- package/src/tree/contextObserverCallback.ts +58 -58
- package/src/tree/dataFormatterValidator.ts +13 -13
- package/src/tree/dataValidator.ts +13 -13
- package/src/tree/treeObservables.ts +105 -105
- package/src/utils/commonUtils.ts +55 -55
- package/src/utils/d3Scale.ts +198 -198
- package/src/utils/errorMessage.ts +40 -40
- package/src/utils/index.ts +3 -3
- package/src/utils/observables.ts +308 -308
- package/src/utils/orbchartsUtils.ts +396 -396
- package/src/utils/validator.ts +126 -126
- package/tsconfig.base.json +13 -13
- package/tsconfig.json +2 -2
- package/vite-env.d.ts +6 -6
- 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
|
+
}
|