@orbcharts/core 3.0.0-beta.3 → 3.0.0-beta.4

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