@orbcharts/core 3.0.5 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-core.es.js +2217 -2164
  3. package/dist/orbcharts-core.umd.js +5 -5
  4. package/lib/core-types.ts +7 -7
  5. package/package.json +46 -46
  6. package/src/AbstractChart.ts +57 -57
  7. package/src/GridChart.ts +24 -24
  8. package/src/MultiGridChart.ts +24 -24
  9. package/src/MultiValueChart.ts +24 -24
  10. package/src/RelationshipChart.ts +24 -24
  11. package/src/SeriesChart.ts +24 -24
  12. package/src/TreeChart.ts +24 -24
  13. package/src/base/createBaseChart.ts +524 -524
  14. package/src/base/createBasePlugin.ts +154 -154
  15. package/src/base/validators/chartOptionsValidator.ts +23 -23
  16. package/src/base/validators/chartParamsValidator.ts +133 -133
  17. package/src/base/validators/elementValidator.ts +13 -13
  18. package/src/base/validators/pluginsValidator.ts +14 -14
  19. package/src/defaults.ts +284 -284
  20. package/src/defineGridPlugin.ts +3 -3
  21. package/src/defineMultiGridPlugin.ts +3 -3
  22. package/src/defineMultiValuePlugin.ts +3 -3
  23. package/src/defineNoneDataPlugin.ts +4 -4
  24. package/src/defineRelationshipPlugin.ts +3 -3
  25. package/src/defineSeriesPlugin.ts +3 -3
  26. package/src/defineTreePlugin.ts +3 -3
  27. package/src/grid/computedDataFn.ts +129 -129
  28. package/src/grid/contextObserverCallback.ts +209 -201
  29. package/src/grid/dataFormatterValidator.ts +125 -125
  30. package/src/grid/dataValidator.ts +12 -12
  31. package/src/grid/gridObservables.ts +698 -694
  32. package/src/index.ts +20 -20
  33. package/src/multiGrid/computedDataFn.ts +123 -123
  34. package/src/multiGrid/contextObserverCallback.ts +109 -75
  35. package/src/multiGrid/dataFormatterValidator.ts +120 -120
  36. package/src/multiGrid/dataValidator.ts +12 -12
  37. package/src/multiGrid/multiGridObservables.ts +366 -357
  38. package/src/multiValue/computedDataFn.ts +113 -113
  39. package/src/multiValue/contextObserverCallback.ts +328 -328
  40. package/src/multiValue/dataFormatterValidator.ts +94 -94
  41. package/src/multiValue/dataValidator.ts +12 -12
  42. package/src/multiValue/multiValueObservables.ts +865 -865
  43. package/src/relationship/computedDataFn.ts +159 -159
  44. package/src/relationship/contextObserverCallback.ts +80 -80
  45. package/src/relationship/dataFormatterValidator.ts +13 -13
  46. package/src/relationship/dataValidator.ts +13 -13
  47. package/src/relationship/relationshipObservables.ts +84 -84
  48. package/src/series/computedDataFn.ts +88 -88
  49. package/src/series/contextObserverCallback.ts +132 -132
  50. package/src/series/dataFormatterValidator.ts +46 -46
  51. package/src/series/dataValidator.ts +12 -12
  52. package/src/series/seriesObservables.ts +209 -209
  53. package/src/tree/computedDataFn.ts +129 -129
  54. package/src/tree/contextObserverCallback.ts +58 -58
  55. package/src/tree/dataFormatterValidator.ts +13 -13
  56. package/src/tree/dataValidator.ts +13 -13
  57. package/src/tree/treeObservables.ts +105 -105
  58. package/src/utils/commonUtils.ts +55 -55
  59. package/src/utils/d3Scale.ts +198 -198
  60. package/src/utils/errorMessage.ts +40 -40
  61. package/src/utils/index.ts +3 -3
  62. package/src/utils/observables.ts +308 -308
  63. package/src/utils/orbchartsUtils.ts +396 -396
  64. package/src/utils/validator.ts +126 -126
  65. package/tsconfig.base.json +13 -13
  66. package/tsconfig.json +2 -2
  67. package/vite-env.d.ts +6 -6
  68. package/vite.config.js +22 -22
@@ -1,695 +1,699 @@
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) === JSON.stringify(b)
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
- } else {
276
- // -- 無拆分 --
277
- const gridContainerPositionArr = calcContainerPositionScaled(data.layout, data.fullDataFormatter.container, 1)
278
- return data.computedData.map((d, i) => gridContainerPositionArr[0]) // 每個series相同位置
279
- }
280
- })
281
- )
282
-
283
- return gridContainerPosition$
284
- }
285
-
286
- // 將原本的value全部替換成加總後的value
287
- export const computedStackedDataObservables = ({ isSeriesSeprate$, computedData$ }: {
288
- isSeriesSeprate$: Observable<boolean>
289
- computedData$: Observable<ComputedDataGrid>
290
- }): Observable<ComputedDataGrid> => {
291
- const stackedData$: Observable<ComputedDataGrid> = computedData$.pipe(
292
- map(data => {
293
- // 將同一group的value加總起來
294
- const stackedValue = new Array(data[0] ? data[0].length : 0)
295
- .fill(null)
296
- .map((_, i) => {
297
- return data.reduce((prev, current) => {
298
- if (current && current[i]) {
299
- const currentValue = current[i].value == null || current[i].visible == false
300
- ? 0
301
- : current[i].value!
302
- return prev + currentValue
303
- }
304
- return prev
305
- }, 0)
306
- })
307
- // 將原本的value全部替換成加總後的value
308
- const computedData = data.map((series, seriesIndex) => {
309
- return series.map((d, i) => {
310
- return {
311
- ...d,
312
- value: stackedValue[i],
313
- }
314
- })
315
- })
316
- return computedData
317
- }),
318
- )
319
-
320
- return isSeriesSeprate$.pipe(
321
- switchMap(isSeriesSeprate => {
322
- return iif(() => isSeriesSeprate, computedData$, stackedData$)
323
- })
324
- )
325
- }
326
-
327
- export const groupScaleDomainValueObservable = ({ computedData$, fullDataFormatter$ }: {
328
- computedData$: Observable<ComputedDataGrid>
329
- fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
330
- }): Observable<[number, number]> => {
331
- return combineLatest({
332
- computedData: computedData$,
333
- fullDataFormatter: fullDataFormatter$
334
- }).pipe(
335
- switchMap(async (d) => d),
336
- map(data => {
337
- const groupAxis = data.fullDataFormatter.groupAxis
338
- const groupMin = 0
339
- const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
340
- // const groupScaleDomainMin = groupAxis.scaleDomain[0] === 'min'
341
- // ? groupMin - groupAxis.scalePadding
342
- // : groupAxis.scaleDomain[0] as number - groupAxis.scalePadding
343
- const groupScaleDomainMin = groupAxis.scaleDomain[0] - groupAxis.scalePadding
344
- const groupScaleDomainMax = groupAxis.scaleDomain[1] === 'max'
345
- ? groupMax + groupAxis.scalePadding
346
- : groupAxis.scaleDomain[1] as number + groupAxis.scalePadding
347
-
348
- return [groupScaleDomainMin, groupScaleDomainMax]
349
- })
350
- )
351
- }
352
-
353
- export const filteredMinMaxValueObservable = ({ computedData$, groupScaleDomainValue$ }: {
354
- computedData$: Observable<ComputedDataGrid>
355
- // fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
356
- groupScaleDomainValue$: Observable<[number, number]>
357
- }) => {
358
- return combineLatest({
359
- computedData: computedData$,
360
- // fullDataFormatter: fullDataFormatter$,
361
- groupScaleDomainValue: groupScaleDomainValue$
362
- }).pipe(
363
- map(data => {
364
- const filteredData = data.computedData.map((d, i) => {
365
- return d.filter((_d, _i) => {
366
- return _i >= data.groupScaleDomainValue[0] && _i <= data.groupScaleDomainValue[1] && _d.visible == true
367
- })
368
- })
369
-
370
- const filteredMinMax = getMinMaxGrid(filteredData)
371
- // if (filteredMinMax[0] === filteredMinMax[1]) {
372
- // filteredMinMax[0] = filteredMinMax[1] - 1 // 避免最大及最小值相同造成無法計算scale
373
- // }
374
- return filteredMinMax
375
- }),
376
- )
377
- }
378
-
379
- export const gridAxesTransformObservable = ({ fullDataFormatter$, layout$ }: {
380
- fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
381
- layout$: Observable<Layout>
382
- }): Observable<TransformData> => {
383
- const destroy$ = new Subject()
384
-
385
- function calcAxesTransform ({ xAxis, yAxis, width, height }: {
386
- xAxis: DataFormatterGroupAxis | DataFormatterValueAxis,
387
- yAxis: DataFormatterValueAxis,
388
- width: number,
389
- height: number
390
- }): TransformData {
391
- if (!xAxis || !yAxis) {
392
- return {
393
- translate: [0, 0],
394
- scale: [1, 1],
395
- rotate: 0,
396
- rotateX: 0,
397
- rotateY: 0,
398
- value: ''
399
- }
400
- }
401
- // const width = size.width - fullChartParams.layout.left - fullChartParams.layout.right
402
- // const height = size.height - fullChartParams.layout.top - fullChartParams.layout.bottom
403
- let translateX = 0
404
- let translateY = 0
405
- let rotate = 0
406
- let rotateX = 0
407
- let rotateY = 0
408
- if (xAxis.position === 'bottom') {
409
- if (yAxis.position === 'left') {
410
- rotateX = 180
411
- translateY = height
412
- } else if (yAxis.position === 'right') {
413
- rotateX = 180
414
- rotateY = 180
415
- translateX = width
416
- translateY = height
417
- } else {
418
- // 預設
419
- rotateX = 180
420
- translateY = height
421
- }
422
- } else if (xAxis.position === 'top') {
423
- if (yAxis.position === 'left') {
424
- } else if (yAxis.position === 'right') {
425
- rotateY = 180
426
- translateX = width
427
- } else {
428
- // 預設
429
- rotateX = 180
430
- translateY = height
431
- }
432
- } else if (xAxis.position === 'left') {
433
- if (yAxis.position === 'bottom') {
434
- rotate = -90
435
- translateY = height
436
- } else if (yAxis.position === 'top') {
437
- rotate = -90
438
- rotateY = 180
439
- } else {
440
- // 預設
441
- rotateX = 180
442
- translateY = height
443
- }
444
- } else if (xAxis.position === 'right') {
445
- if (yAxis.position === 'bottom') {
446
- rotate = -90
447
- rotateX = 180
448
- translateY = height
449
- translateX = width
450
- } else if (yAxis.position === 'top') {
451
- rotate = -90
452
- rotateX = 180
453
- rotateY = 180
454
- translateX = width
455
- } else {
456
- // 預設
457
- rotateX = 180
458
- translateY = height
459
- }
460
- } else {
461
- // 預設
462
- rotateX = 180
463
- translateY = height
464
- }
465
- // selection.style('transform', `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`)
466
-
467
- return {
468
- translate: [translateX, translateY],
469
- scale: [1, 1],
470
- rotate,
471
- rotateX,
472
- rotateY,
473
- value: `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
474
- }
475
- }
476
-
477
- return new Observable(subscriber => {
478
- combineLatest({
479
- fullDataFormatter: fullDataFormatter$,
480
- layout: layout$
481
- }).pipe(
482
- takeUntil(destroy$),
483
- switchMap(async (d) => d),
484
- ).subscribe(data => {
485
- const axesTransformData = calcAxesTransform({
486
- xAxis: data.fullDataFormatter.groupAxis,
487
- yAxis: data.fullDataFormatter.valueAxis,
488
- width: data.layout.width,
489
- height: data.layout.height
490
- })
491
-
492
- subscriber.next(axesTransformData)
493
- })
494
-
495
- return function unscbscribe () {
496
- destroy$.next(undefined)
497
- }
498
- })
499
- }
500
-
501
-
502
- export const gridAxesReverseTransformObservable = ({ gridAxesTransform$ }: {
503
- gridAxesTransform$: Observable<TransformData>
504
- }): Observable<TransformData> => {
505
- return gridAxesTransform$.pipe(
506
- map(d => {
507
- // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
508
- const translate: [number, number] = [0, 0] // 無需逆轉
509
- const scale: [number, number] = [1 / d.scale[0], 1 / d.scale[1]]
510
- const rotate = d.rotate * -1
511
- const rotateX = d.rotateX * -1
512
- const rotateY = d.rotateY * -1
513
- return {
514
- translate,
515
- scale,
516
- rotate,
517
- rotateX,
518
- rotateY,
519
- value: `translate(${translate[0]}px, ${translate[1]}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotate(${rotate}deg)`
520
- }
521
- }),
522
- )
523
- }
524
-
525
- export const gridGraphicTransformObservable = ({ computedData$, groupScaleDomainValue$, filteredMinMaxValue$, fullDataFormatter$, layout$ }: {
526
- computedData$: Observable<ComputedDataTypeMap<'grid'>>
527
- groupScaleDomainValue$: Observable<[number, number]>
528
- filteredMinMaxValue$: Observable<[number, number]>
529
- fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
530
- layout$: Observable<Layout>
531
- }): Observable<TransformData> => {
532
- const destroy$ = new Subject()
533
-
534
- function calcGridDataAreaTransform ({ data, groupAxis, valueAxis, groupScaleDomainValue, filteredMinMaxValue, width, height }: {
535
- data: ComputedDataTypeMap<'grid'>
536
- groupAxis: DataFormatterGroupAxis
537
- valueAxis: DataFormatterValueAxis
538
- groupScaleDomainValue: [number, number],
539
- filteredMinMaxValue: [number, number],
540
- width: number
541
- height: number
542
- }): TransformData {
543
- let translateX = 0
544
- let translateY = 0
545
- let scaleX = 0
546
- let scaleY = 0
547
-
548
- // -- groupScale --
549
- const groupAxisWidth = (groupAxis.position === 'top' || groupAxis.position === 'bottom')
550
- ? width
551
- : height
552
- const groupMin = 0
553
- const groupMax = data[0] ? data[0].length - 1 : 0
554
- // const groupScaleDomainMin = groupAxis.scaleDomain[0] - groupAxis.scalePadding
555
- // const groupScaleDomainMax = groupAxis.scaleDomain[1] === 'max'
556
- // ? groupMax + groupAxis.scalePadding
557
- // : groupAxis.scaleDomain[1] as number + groupAxis.scalePadding
558
-
559
- const groupScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
560
- maxValue: groupMax,
561
- minValue: groupMin,
562
- axisWidth: groupAxisWidth,
563
- // scaleDomain: groupAxis.scaleDomain,
564
- scaleDomain: groupScaleDomainValue,
565
- scaleRange: [0, 1]
566
- })
567
-
568
- // -- translateX, scaleX --
569
- const rangeMinX = groupScale(groupMin)
570
- const rangeMaxX = groupScale(groupMax)
571
- if (groupMin == groupMax) {
572
- // 當group只有一個
573
- translateX = 0
574
- scaleX = 1
575
- } else {
576
- translateX = rangeMinX
577
- const gWidth = rangeMaxX - rangeMinX
578
- scaleX = gWidth / groupAxisWidth
579
- }
580
-
581
- // -- valueScale --
582
- // const filteredData = data.map((d, i) => {
583
- // return d.filter((_d, _i) => {
584
- // return _i >= groupScaleDomainMin && _i <= groupScaleDomainMax && _d.visible == true
585
- // })
586
- // })
587
-
588
- // const filteredMinMax = getMinMaxGrid(filteredData)
589
- if (filteredMinMaxValue[0] === filteredMinMaxValue[1] && filteredMinMaxValue[1] === 0) {
590
- // filteredMinMaxValue[0] = filteredMinMaxValue[1] - 1 // 避免最大及最小值相同造成無法計算scale
591
- filteredMinMaxValue[1] = 1 // 避免最大及最小值同等於 0 造成無法計算scale
592
- }
593
-
594
- const valueAxisWidth = (valueAxis.position === 'left' || valueAxis.position === 'right')
595
- ? height
596
- : width
597
-
598
- const valueScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
599
- maxValue: filteredMinMaxValue[1],
600
- minValue: filteredMinMaxValue[0],
601
- axisWidth: valueAxisWidth,
602
- scaleDomain: valueAxis.scaleDomain,
603
- scaleRange: valueAxis.scaleRange
604
- })
605
- // console.log({
606
- // maxValue: filteredMinMaxValue[1],
607
- // minValue: filteredMinMaxValue[0],
608
- // axisWidth: valueAxisWidth,
609
- // scaleDomain: valueAxis.scaleDomain,
610
- // scaleRange: valueAxis.scaleRange
611
- // })
612
- // -- translateY, scaleY --
613
- const minMax = getMinMaxGrid(data)
614
- if (minMax[0] === minMax[1] && minMax[1] === 0) {
615
- // minMax[0] = minMax[1] - 1 // 避免最大及最小值相同造成無法計算scale
616
- minMax[1] = 1 // 避免最大及最小值同等於 0 造成無法計算scale
617
- }
618
- // const rangeMinY = valueScale(minMax[0])
619
- const rangeMinY = valueScale(minMax[0] > 0 ? 0 : minMax[0]) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
620
- const rangeMaxY = valueScale(minMax[1] < 0 ? 0 : minMax[1]) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
621
- translateY = rangeMinY
622
- const gHeight = rangeMaxY - rangeMinY
623
- scaleY = gHeight / valueAxisWidth
624
-
625
- return {
626
- translate: [translateX, translateY],
627
- scale: [scaleX, scaleY],
628
- rotate: 0,
629
- rotateX: 0,
630
- rotateY: 0,
631
- value: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`
632
- }
633
- }
634
-
635
- return new Observable(subscriber => {
636
- combineLatest({
637
- computedData: computedData$,
638
- groupScaleDomainValue: groupScaleDomainValue$,
639
- filteredMinMaxValue: filteredMinMaxValue$,
640
- fullDataFormatter: fullDataFormatter$,
641
- layout: layout$
642
- }).pipe(
643
- takeUntil(destroy$),
644
- switchMap(async (d) => d),
645
- ).subscribe(data => {
646
- const dataAreaTransformData = calcGridDataAreaTransform ({
647
- data: data.computedData,
648
- groupAxis: data.fullDataFormatter.groupAxis,
649
- valueAxis: data.fullDataFormatter.valueAxis,
650
- groupScaleDomainValue: data.groupScaleDomainValue,
651
- filteredMinMaxValue: data.filteredMinMaxValue,
652
- width: data.layout.width,
653
- height: data.layout.height
654
- })
655
-
656
- subscriber.next(dataAreaTransformData)
657
- })
658
-
659
- return function unscbscribe () {
660
- destroy$.next(undefined)
661
- }
662
- })
663
- }
664
-
665
- export const gridGraphicReverseScaleObservable = ({ gridContainerPosition$, gridAxesTransform$, gridGraphicTransform$ }: {
666
- gridContainerPosition$: Observable<ContainerPositionScaled[]>
667
- gridAxesTransform$: Observable<TransformData>
668
- gridGraphicTransform$: Observable<TransformData>
669
- }): Observable<[number, number][]> => {
670
- return combineLatest({
671
- gridContainerPosition: gridContainerPosition$,
672
- gridAxesTransform: gridAxesTransform$,
673
- gridGraphicTransform: gridGraphicTransform$,
674
- }).pipe(
675
- switchMap(async (d) => d),
676
- map(data => {
677
- if (data.gridAxesTransform.rotate == 0 || data.gridAxesTransform.rotate == 180) {
678
- return data.gridContainerPosition.map((series, seriesIndex) => {
679
- return [
680
- 1 / data.gridGraphicTransform.scale[0] / data.gridContainerPosition[seriesIndex].scale[0],
681
- 1 / data.gridGraphicTransform.scale[1] / data.gridContainerPosition[seriesIndex].scale[1],
682
- ]
683
- })
684
- } else {
685
- return data.gridContainerPosition.map((series, seriesIndex) => {
686
- // 由於有垂直的旋轉,所以外層 (container) x和y的scale要互換
687
- return [
688
- 1 / data.gridGraphicTransform.scale[0] / data.gridContainerPosition[seriesIndex].scale[1],
689
- 1 / data.gridGraphicTransform.scale[1] / data.gridContainerPosition[seriesIndex].scale[0],
690
- ]
691
- })
692
- }
693
- }),
694
- )
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) === JSON.stringify(b)
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
+ } else {
276
+ // -- 無拆分 --
277
+ const gridContainerPositionArr = calcContainerPositionScaled(data.layout, data.fullDataFormatter.container, 1)
278
+ return data.computedData.map((d, i) => gridContainerPositionArr[0]) // 每個series相同位置
279
+ }
280
+ })
281
+ )
282
+
283
+ return gridContainerPosition$
284
+ }
285
+
286
+ // 將原本的value全部替換成加總後的value
287
+ export const computedStackedDataObservables = ({ isSeriesSeprate$, computedData$ }: {
288
+ isSeriesSeprate$: Observable<boolean>
289
+ computedData$: Observable<ComputedDataGrid>
290
+ }): Observable<ComputedDataGrid> => {
291
+ const stackedData$: Observable<ComputedDataGrid> = computedData$.pipe(
292
+ map(data => {
293
+ // 將同一group的value加總起來
294
+ const stackedValue = new Array(data[0] ? data[0].length : 0)
295
+ .fill(null)
296
+ .map((_, i) => {
297
+ return data.reduce((prev, current) => {
298
+ if (current && current[i]) {
299
+ const currentValue = current[i].value == null || current[i].visible == false
300
+ ? 0
301
+ : current[i].value!
302
+ return prev + currentValue
303
+ }
304
+ return prev
305
+ }, 0)
306
+ })
307
+ // 將原本的value全部替換成加總後的value
308
+ const computedData = data.map((series, seriesIndex) => {
309
+ return series.map((d, i) => {
310
+ return {
311
+ ...d,
312
+ value: stackedValue[i],
313
+ }
314
+ })
315
+ })
316
+ return computedData
317
+ }),
318
+ )
319
+
320
+ return isSeriesSeprate$.pipe(
321
+ switchMap(isSeriesSeprate => {
322
+ return iif(() => isSeriesSeprate, computedData$, stackedData$)
323
+ })
324
+ )
325
+ }
326
+
327
+ export const groupScaleDomainValueObservable = ({ computedData$, fullDataFormatter$ }: {
328
+ computedData$: Observable<ComputedDataGrid>
329
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
330
+ }): Observable<[number, number]> => {
331
+ return combineLatest({
332
+ computedData: computedData$,
333
+ fullDataFormatter: fullDataFormatter$
334
+ }).pipe(
335
+ switchMap(async (d) => d),
336
+ map(data => {
337
+ const groupAxis = data.fullDataFormatter.groupAxis
338
+ const groupMin = 0
339
+ const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
340
+ // const groupScaleDomainMin = groupAxis.scaleDomain[0] === 'min'
341
+ // ? groupMin - groupAxis.scalePadding
342
+ // : groupAxis.scaleDomain[0] as number - groupAxis.scalePadding
343
+ const groupScaleDomainMin = groupAxis.scaleDomain[0] - groupAxis.scalePadding
344
+ const groupScaleDomainMax = groupAxis.scaleDomain[1] === 'max'
345
+ ? groupMax + groupAxis.scalePadding
346
+ : groupAxis.scaleDomain[1] as number + groupAxis.scalePadding
347
+
348
+ return [groupScaleDomainMin, groupScaleDomainMax]
349
+ })
350
+ )
351
+ }
352
+
353
+ export const filteredMinMaxValueObservable = ({ computedData$, groupScaleDomainValue$ }: {
354
+ computedData$: Observable<ComputedDataGrid>
355
+ // fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
356
+ groupScaleDomainValue$: Observable<[number, number]>
357
+ }) => {
358
+ return combineLatest({
359
+ computedData: computedData$,
360
+ // fullDataFormatter: fullDataFormatter$,
361
+ groupScaleDomainValue: groupScaleDomainValue$
362
+ }).pipe(
363
+ map(data => {
364
+ const filteredData = data.computedData.map((d, i) => {
365
+ return d.filter((_d, _i) => {
366
+ return _i >= data.groupScaleDomainValue[0] && _i <= data.groupScaleDomainValue[1] && _d.visible == true
367
+ })
368
+ })
369
+
370
+ const filteredMinMax = getMinMaxGrid(filteredData)
371
+ // if (filteredMinMax[0] === filteredMinMax[1]) {
372
+ // filteredMinMax[0] = filteredMinMax[1] - 1 // 避免最大及最小值相同造成無法計算scale
373
+ // }
374
+ return filteredMinMax
375
+ }),
376
+ )
377
+ }
378
+
379
+ export const gridAxesTransformObservable = ({ fullDataFormatter$, layout$ }: {
380
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
381
+ layout$: Observable<Layout>
382
+ }): Observable<TransformData> => {
383
+ const destroy$ = new Subject()
384
+
385
+ function calcAxesTransform ({ xAxis, yAxis, width, height }: {
386
+ xAxis: DataFormatterGroupAxis | DataFormatterValueAxis,
387
+ yAxis: DataFormatterValueAxis,
388
+ width: number,
389
+ height: number
390
+ }): TransformData {
391
+ if (!xAxis || !yAxis) {
392
+ return {
393
+ translate: [0, 0],
394
+ scale: [1, 1],
395
+ rotate: 0,
396
+ rotateX: 0,
397
+ rotateY: 0,
398
+ value: ''
399
+ }
400
+ }
401
+ // const width = size.width - fullChartParams.layout.left - fullChartParams.layout.right
402
+ // const height = size.height - fullChartParams.layout.top - fullChartParams.layout.bottom
403
+ let translateX = 0
404
+ let translateY = 0
405
+ let rotate = 0
406
+ let rotateX = 0
407
+ let rotateY = 0
408
+ if (xAxis.position === 'bottom') {
409
+ if (yAxis.position === 'left') {
410
+ rotateX = 180
411
+ translateY = height
412
+ } else if (yAxis.position === 'right') {
413
+ rotateX = 180
414
+ rotateY = 180
415
+ translateX = width
416
+ translateY = height
417
+ } else {
418
+ // 預設
419
+ rotateX = 180
420
+ translateY = height
421
+ }
422
+ } else if (xAxis.position === 'top') {
423
+ if (yAxis.position === 'left') {
424
+ } else if (yAxis.position === 'right') {
425
+ rotateY = 180
426
+ translateX = width
427
+ } else {
428
+ // 預設
429
+ rotateX = 180
430
+ translateY = height
431
+ }
432
+ } else if (xAxis.position === 'left') {
433
+ if (yAxis.position === 'bottom') {
434
+ rotate = -90
435
+ translateY = height
436
+ } else if (yAxis.position === 'top') {
437
+ rotate = -90
438
+ rotateY = 180
439
+ } else {
440
+ // 預設
441
+ rotateX = 180
442
+ translateY = height
443
+ }
444
+ } else if (xAxis.position === 'right') {
445
+ if (yAxis.position === 'bottom') {
446
+ rotate = -90
447
+ rotateX = 180
448
+ translateY = height
449
+ translateX = width
450
+ } else if (yAxis.position === 'top') {
451
+ rotate = -90
452
+ rotateX = 180
453
+ rotateY = 180
454
+ translateX = width
455
+ } else {
456
+ // 預設
457
+ rotateX = 180
458
+ translateY = height
459
+ }
460
+ } else {
461
+ // 預設
462
+ rotateX = 180
463
+ translateY = height
464
+ }
465
+ // selection.style('transform', `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`)
466
+
467
+ return {
468
+ translate: [translateX, translateY],
469
+ scale: [1, 1],
470
+ rotate,
471
+ rotateX,
472
+ rotateY,
473
+ value: `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
474
+ }
475
+ }
476
+
477
+ return new Observable(subscriber => {
478
+ combineLatest({
479
+ fullDataFormatter: fullDataFormatter$,
480
+ layout: layout$
481
+ }).pipe(
482
+ takeUntil(destroy$),
483
+ switchMap(async (d) => d),
484
+ ).subscribe(data => {
485
+ const axesTransformData = calcAxesTransform({
486
+ xAxis: data.fullDataFormatter.groupAxis,
487
+ yAxis: data.fullDataFormatter.valueAxis,
488
+ width: data.layout.width,
489
+ height: data.layout.height
490
+ })
491
+
492
+ subscriber.next(axesTransformData)
493
+ })
494
+
495
+ return function unscbscribe () {
496
+ destroy$.next(undefined)
497
+ }
498
+ })
499
+ }
500
+
501
+
502
+ export const gridAxesReverseTransformObservable = ({ gridAxesTransform$ }: {
503
+ gridAxesTransform$: Observable<TransformData>
504
+ }): Observable<TransformData> => {
505
+ return gridAxesTransform$.pipe(
506
+ map(d => {
507
+ // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
508
+ const translate: [number, number] = [0, 0] // 無需逆轉
509
+ const scale: [number, number] = [1 / d.scale[0], 1 / d.scale[1]]
510
+ const rotate = d.rotate * -1
511
+ const rotateX = d.rotateX * -1
512
+ const rotateY = d.rotateY * -1
513
+ return {
514
+ translate,
515
+ scale,
516
+ rotate,
517
+ rotateX,
518
+ rotateY,
519
+ value: `translate(${translate[0]}px, ${translate[1]}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotate(${rotate}deg)`
520
+ }
521
+ }),
522
+ )
523
+ }
524
+
525
+ export const gridGraphicTransformObservable = ({ computedData$, groupScaleDomainValue$, filteredMinMaxValue$, fullDataFormatter$, layout$ }: {
526
+ computedData$: Observable<ComputedDataTypeMap<'grid'>>
527
+ groupScaleDomainValue$: Observable<[number, number]>
528
+ filteredMinMaxValue$: Observable<[number, number]>
529
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
530
+ layout$: Observable<Layout>
531
+ }): Observable<TransformData> => {
532
+ const destroy$ = new Subject()
533
+
534
+ function calcGridDataAreaTransform ({ data, groupAxis, valueAxis, groupScaleDomainValue, filteredMinMaxValue, width, height }: {
535
+ data: ComputedDataTypeMap<'grid'>
536
+ groupAxis: DataFormatterGroupAxis
537
+ valueAxis: DataFormatterValueAxis
538
+ groupScaleDomainValue: [number, number],
539
+ filteredMinMaxValue: [number, number],
540
+ width: number
541
+ height: number
542
+ }): TransformData {
543
+ let translateX = 0
544
+ let translateY = 0
545
+ let scaleX = 0
546
+ let scaleY = 0
547
+
548
+ // -- groupScale --
549
+ const groupAxisWidth = (groupAxis.position === 'top' || groupAxis.position === 'bottom')
550
+ ? width
551
+ : height
552
+ const groupMin = 0
553
+ const groupMax = data[0] ? data[0].length - 1 : 0
554
+ // const groupScaleDomainMin = groupAxis.scaleDomain[0] - groupAxis.scalePadding
555
+ // const groupScaleDomainMax = groupAxis.scaleDomain[1] === 'max'
556
+ // ? groupMax + groupAxis.scalePadding
557
+ // : groupAxis.scaleDomain[1] as number + groupAxis.scalePadding
558
+
559
+ const groupScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
560
+ maxValue: groupMax,
561
+ minValue: groupMin,
562
+ axisWidth: groupAxisWidth,
563
+ // scaleDomain: groupAxis.scaleDomain,
564
+ scaleDomain: groupScaleDomainValue,
565
+ scaleRange: [0, 1]
566
+ })
567
+
568
+ // -- translateX, scaleX --
569
+ const rangeMinX = groupScale(groupMin)
570
+ const rangeMaxX = groupScale(groupMax)
571
+ if (groupMin == groupMax) {
572
+ // 當group只有一個
573
+ translateX = 0
574
+ scaleX = 1
575
+ } else {
576
+ translateX = rangeMinX
577
+ const gWidth = rangeMaxX - rangeMinX
578
+ scaleX = gWidth / groupAxisWidth
579
+ }
580
+
581
+ // -- valueScale --
582
+ // const filteredData = data.map((d, i) => {
583
+ // return d.filter((_d, _i) => {
584
+ // return _i >= groupScaleDomainMin && _i <= groupScaleDomainMax && _d.visible == true
585
+ // })
586
+ // })
587
+
588
+ // const filteredMinMax = getMinMaxGrid(filteredData)
589
+ const filteredMin = filteredMinMaxValue[0]
590
+ let filteredMax = filteredMinMaxValue[1]
591
+ if (filteredMin === filteredMax && filteredMax === 0) {
592
+ // filteredMinMaxValue[0] = filteredMinMaxValue[1] - 1 // 避免最大及最小值相同造成無法計算scale
593
+ filteredMax = 1 // 避免最大及最小值同等於 0 造成無法計算scale
594
+ }
595
+
596
+ const valueAxisWidth = (valueAxis.position === 'left' || valueAxis.position === 'right')
597
+ ? height
598
+ : width
599
+
600
+ const valueScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
601
+ maxValue: filteredMax,
602
+ minValue: filteredMin,
603
+ axisWidth: valueAxisWidth,
604
+ scaleDomain: valueAxis.scaleDomain,
605
+ scaleRange: valueAxis.scaleRange
606
+ })
607
+ // console.log({
608
+ // maxValue: filteredMinMaxValue[1],
609
+ // minValue: filteredMinMaxValue[0],
610
+ // axisWidth: valueAxisWidth,
611
+ // scaleDomain: valueAxis.scaleDomain,
612
+ // scaleRange: valueAxis.scaleRange
613
+ // })
614
+ // -- translateY, scaleY --
615
+ const minMax = getMinMaxGrid(data)
616
+ const min = minMax[0]
617
+ let max = minMax[1]
618
+ if (min === max && max === 0) {
619
+ // minMax[0] = minMax[1] - 1 // 避免最大及最小值相同造成無法計算scale
620
+ max = 1 // 避免最大及最小值同等於 0 造成無法計算scale
621
+ }
622
+ // const rangeMinY = valueScale(minMax[0])
623
+ const rangeMinY = valueScale(minMax[0] > 0 ? 0 : minMax[0]) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
624
+ const rangeMaxY = valueScale(minMax[1] < 0 ? 0 : minMax[1]) // * 因為原本的座標就是以 0 到最大值或最小值範範圍計算的,所以這邊也是用同樣的方式計算
625
+ translateY = rangeMinY
626
+ const gHeight = rangeMaxY - rangeMinY
627
+ scaleY = gHeight / valueAxisWidth
628
+
629
+ return {
630
+ translate: [translateX, translateY],
631
+ scale: [scaleX, scaleY],
632
+ rotate: 0,
633
+ rotateX: 0,
634
+ rotateY: 0,
635
+ value: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`
636
+ }
637
+ }
638
+
639
+ return new Observable(subscriber => {
640
+ combineLatest({
641
+ computedData: computedData$,
642
+ groupScaleDomainValue: groupScaleDomainValue$,
643
+ filteredMinMaxValue: filteredMinMaxValue$,
644
+ fullDataFormatter: fullDataFormatter$,
645
+ layout: layout$
646
+ }).pipe(
647
+ takeUntil(destroy$),
648
+ switchMap(async (d) => d),
649
+ ).subscribe(data => {
650
+ const dataAreaTransformData = calcGridDataAreaTransform ({
651
+ data: data.computedData,
652
+ groupAxis: data.fullDataFormatter.groupAxis,
653
+ valueAxis: data.fullDataFormatter.valueAxis,
654
+ groupScaleDomainValue: data.groupScaleDomainValue,
655
+ filteredMinMaxValue: data.filteredMinMaxValue,
656
+ width: data.layout.width,
657
+ height: data.layout.height
658
+ })
659
+
660
+ subscriber.next(dataAreaTransformData)
661
+ })
662
+
663
+ return function unscbscribe () {
664
+ destroy$.next(undefined)
665
+ }
666
+ })
667
+ }
668
+
669
+ export const gridGraphicReverseScaleObservable = ({ gridContainerPosition$, gridAxesTransform$, gridGraphicTransform$ }: {
670
+ gridContainerPosition$: Observable<ContainerPositionScaled[]>
671
+ gridAxesTransform$: Observable<TransformData>
672
+ gridGraphicTransform$: Observable<TransformData>
673
+ }): Observable<[number, number][]> => {
674
+ return combineLatest({
675
+ gridContainerPosition: gridContainerPosition$,
676
+ gridAxesTransform: gridAxesTransform$,
677
+ gridGraphicTransform: gridGraphicTransform$,
678
+ }).pipe(
679
+ switchMap(async (d) => d),
680
+ map(data => {
681
+ if (data.gridAxesTransform.rotate == 0 || data.gridAxesTransform.rotate == 180) {
682
+ return data.gridContainerPosition.map((series, seriesIndex) => {
683
+ return [
684
+ 1 / data.gridGraphicTransform.scale[0] / data.gridContainerPosition[seriesIndex].scale[0],
685
+ 1 / data.gridGraphicTransform.scale[1] / data.gridContainerPosition[seriesIndex].scale[1],
686
+ ]
687
+ })
688
+ } else {
689
+ return data.gridContainerPosition.map((series, seriesIndex) => {
690
+ // 由於有垂直的旋轉,所以外層 (container) x和y的scale要互換
691
+ return [
692
+ 1 / data.gridGraphicTransform.scale[0] / data.gridContainerPosition[seriesIndex].scale[1],
693
+ 1 / data.gridGraphicTransform.scale[1] / data.gridContainerPosition[seriesIndex].scale[0],
694
+ ]
695
+ })
696
+ }
697
+ }),
698
+ )
695
699
  }