@orbcharts/core 3.0.1 → 3.0.3

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