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