@orbcharts/core 3.0.0-beta.8 → 3.0.0-beta.9

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