@orbcharts/core 3.0.0-alpha.39 → 3.0.0-alpha.40

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 (86) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-core.es.js +6 -6
  3. package/dist/orbcharts-core.umd.js +1 -1
  4. package/dist/src/types/ContextObserverGrid.d.ts +1 -1
  5. package/dist/src/types/ContextObserverSeries.d.ts +1 -1
  6. package/dist/src/types/ContextObserverTree.d.ts +1 -1
  7. package/dist/src/utils/observables.d.ts +5 -5
  8. package/package.json +41 -41
  9. package/src/AbstractChart.ts +48 -48
  10. package/src/GridChart.ts +20 -20
  11. package/src/MultiGridChart.ts +20 -20
  12. package/src/MultiValueChart.ts +20 -20
  13. package/src/RelationshipChart.ts +20 -20
  14. package/src/SeriesChart.ts +20 -20
  15. package/src/TreeChart.ts +20 -20
  16. package/src/base/createBaseChart.ts +367 -367
  17. package/src/base/createBasePlugin.ts +89 -89
  18. package/src/defaults.ts +247 -247
  19. package/src/defineGridPlugin.ts +3 -3
  20. package/src/defineMultiGridPlugin.ts +3 -3
  21. package/src/defineMultiValuePlugin.ts +3 -3
  22. package/src/defineNoneDataPlugin.ts +4 -4
  23. package/src/defineRelationshipPlugin.ts +3 -3
  24. package/src/defineSeriesPlugin.ts +3 -3
  25. package/src/defineTreePlugin.ts +3 -3
  26. package/src/grid/computeGridData.ts +205 -205
  27. package/src/grid/createGridContextObserver.ts +124 -124
  28. package/src/grid/gridObservables.ts +486 -486
  29. package/src/index.ts +21 -21
  30. package/src/multiGrid/computeMultiGridData.ts +173 -173
  31. package/src/multiGrid/createMultiGridContextObserver.ts +34 -34
  32. package/src/multiGrid/multiGridObservables.ts +285 -285
  33. package/src/multiValue/computeMultiValueData.ts +136 -136
  34. package/src/multiValue/createMultiValueContextObserver.ts +12 -12
  35. package/src/relationship/computeRelationshipData.ts +106 -106
  36. package/src/relationship/createRelationshipContextObserver.ts +12 -12
  37. package/src/series/computeSeriesData.ts +153 -153
  38. package/src/series/createSeriesContextObserver.ts +33 -33
  39. package/src/series/seriesObservables.ts +23 -23
  40. package/src/tree/computeTreeData.ts +128 -128
  41. package/src/tree/createTreeContextObserver.ts +56 -56
  42. package/src/tree/treeObservables.ts +94 -94
  43. package/src/types/Chart.ts +48 -48
  44. package/src/types/ChartParams.ts +51 -51
  45. package/src/types/ComputedData.ts +82 -82
  46. package/src/types/ComputedDataGrid.ts +13 -13
  47. package/src/types/ComputedDataMultiGrid.ts +2 -2
  48. package/src/types/ComputedDataMultiValue.ts +9 -9
  49. package/src/types/ComputedDataRelationship.ts +19 -19
  50. package/src/types/ComputedDataSeries.ts +7 -7
  51. package/src/types/ComputedDataTree.ts +19 -19
  52. package/src/types/ContextObserver.ts +38 -38
  53. package/src/types/ContextObserverGrid.ts +33 -33
  54. package/src/types/ContextObserverMultiGrid.ts +27 -27
  55. package/src/types/ContextObserverMultiValue.ts +4 -4
  56. package/src/types/ContextObserverRelationship.ts +4 -4
  57. package/src/types/ContextObserverSeries.ts +7 -7
  58. package/src/types/ContextObserverTree.ts +10 -10
  59. package/src/types/ContextSubject.ts +18 -18
  60. package/src/types/Data.ts +45 -45
  61. package/src/types/DataFormatter.ts +95 -95
  62. package/src/types/DataFormatterGrid.ts +55 -55
  63. package/src/types/DataFormatterMultiGrid.ts +42 -42
  64. package/src/types/DataFormatterMultiValue.ts +20 -20
  65. package/src/types/DataFormatterRelationship.ts +22 -22
  66. package/src/types/DataFormatterSeries.ts +29 -29
  67. package/src/types/DataFormatterTree.ts +12 -12
  68. package/src/types/DataGrid.ts +11 -11
  69. package/src/types/DataMultiGrid.ts +6 -6
  70. package/src/types/DataMultiValue.ts +12 -12
  71. package/src/types/DataRelationship.ts +27 -27
  72. package/src/types/DataSeries.ts +11 -11
  73. package/src/types/DataTree.ts +20 -20
  74. package/src/types/Event.ts +153 -153
  75. package/src/types/Layout.ts +11 -11
  76. package/src/types/Padding.ts +5 -5
  77. package/src/types/Plugin.ts +60 -60
  78. package/src/types/TransformData.ts +7 -7
  79. package/src/types/index.ts +37 -37
  80. package/src/utils/commonUtils.ts +50 -50
  81. package/src/utils/d3Utils.ts +89 -89
  82. package/src/utils/index.ts +4 -4
  83. package/src/utils/observables.ts +181 -183
  84. package/src/utils/orbchartsUtils.ts +253 -253
  85. package/tsconfig.json +13 -13
  86. package/vite.config.js +44 -44
@@ -1,487 +1,487 @@
1
- import {
2
- combineLatest,
3
- distinctUntilChanged,
4
- filter,
5
- map,
6
- merge,
7
- takeUntil,
8
- shareReplay,
9
- switchMap,
10
- Subject,
11
- Observable } from 'rxjs'
12
- import type {
13
- AxisPosition,
14
- ChartType,
15
- ChartParams,
16
- ComputedDataTypeMap,
17
- ComputedDatumTypeMap,
18
- ContextObserverFn,
19
- DataTypeMap,
20
- DataFormatterTypeMap,
21
- DataFormatterGrid,
22
- DataFormatterGridContainer,
23
- DataFormatterValueAxis,
24
- DataFormatterGroupAxis,
25
- ContainerPosition,
26
- HighlightTarget,
27
- Layout,
28
- TransformData } from '../types'
29
- import { getMinAndMaxGrid } from '../utils/orbchartsUtils'
30
- import { createAxisLinearScale, createAxisPointScale, createAxisQuantizeScale } from '../utils/d3Utils'
31
- import { highlightObservable } from '../utils/observables'
32
- import { calcGridContainerPosition } from '../utils/orbchartsUtils'
33
- import { DATA_FORMATTER_GRID_GRID_DEFAULT } from '../defaults'
34
-
35
- export const gridAxesTransformObservable = ({ fullDataFormatter$, layout$ }: {
36
- fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
37
- layout$: Observable<Layout>
38
- }): Observable<TransformData> => {
39
- const destroy$ = new Subject()
40
-
41
- function calcAxesTransform ({ xAxis, yAxis, width, height }: {
42
- xAxis: DataFormatterGroupAxis | DataFormatterValueAxis,
43
- yAxis: DataFormatterValueAxis,
44
- width: number,
45
- height: number
46
- }): TransformData {
47
- if (!xAxis || !yAxis) {
48
- return {
49
- translate: [0, 0],
50
- scale: [1, 1],
51
- rotate: 0,
52
- rotateX: 0,
53
- rotateY: 0,
54
- value: ''
55
- }
56
- }
57
- // const width = size.width - fullChartParams.layout.left - fullChartParams.layout.right
58
- // const height = size.height - fullChartParams.layout.top - fullChartParams.layout.bottom
59
- let translateX = 0
60
- let translateY = 0
61
- let rotate = 0
62
- let rotateX = 0
63
- let rotateY = 0
64
- if (xAxis.position === 'bottom') {
65
- if (yAxis.position === 'left') {
66
- rotateX = 180
67
- translateY = height
68
- } else if (yAxis.position === 'right') {
69
- rotateX = 180
70
- rotateY = 180
71
- translateX = width
72
- translateY = height
73
- } else {
74
- // 預設
75
- rotateX = 180
76
- translateY = height
77
- }
78
- } else if (xAxis.position === 'top') {
79
- if (yAxis.position === 'left') {
80
- } else if (yAxis.position === 'right') {
81
- rotateY = 180
82
- translateX = width
83
- } else {
84
- // 預設
85
- rotateX = 180
86
- translateY = height
87
- }
88
- } else if (xAxis.position === 'left') {
89
- if (yAxis.position === 'bottom') {
90
- rotate = -90
91
- translateY = height
92
- } else if (yAxis.position === 'top') {
93
- rotate = -90
94
- rotateY = 180
95
- } else {
96
- // 預設
97
- rotateX = 180
98
- translateY = height
99
- }
100
- } else if (xAxis.position === 'right') {
101
- if (yAxis.position === 'bottom') {
102
- rotate = -90
103
- rotateX = 180
104
- translateY = height
105
- translateX = width
106
- } else if (yAxis.position === 'top') {
107
- rotate = -90
108
- rotateX = 180
109
- rotateY = 180
110
- translateX = width
111
- } else {
112
- // 預設
113
- rotateX = 180
114
- translateY = height
115
- }
116
- }
117
- // selection.style('transform', `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`)
118
-
119
- return {
120
- translate: [translateX, translateY],
121
- scale: [1, 1],
122
- rotate,
123
- rotateX,
124
- rotateY,
125
- value: `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
126
- }
127
- }
128
-
129
- return new Observable(subscriber => {
130
- combineLatest({
131
- fullDataFormatter: fullDataFormatter$,
132
- layout: layout$
133
- }).pipe(
134
- takeUntil(destroy$),
135
- switchMap(async (d) => d),
136
- ).subscribe(data => {
137
- const axesTransformData = calcAxesTransform({
138
- xAxis: data.fullDataFormatter.grid.groupAxis,
139
- yAxis: data.fullDataFormatter.grid.valueAxis,
140
- width: data.layout.width,
141
- height: data.layout.height
142
- })
143
-
144
- subscriber.next(axesTransformData)
145
- })
146
-
147
- return function unscbscribe () {
148
- destroy$.next(undefined)
149
- }
150
- })
151
- }
152
-
153
-
154
- export const gridAxesReverseTransformObservable = ({ gridAxesTransform$ }: {
155
- gridAxesTransform$: Observable<TransformData>
156
- }): Observable<TransformData> => {
157
- return gridAxesTransform$.pipe(
158
- map(d => {
159
- // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
160
- const translate: [number, number] = [0, 0] // 無需逆轉
161
- const scale: [number, number] = [1 / d.scale[0], 1 / d.scale[1]]
162
- const rotate = d.rotate * -1
163
- const rotateX = d.rotateX * -1
164
- const rotateY = d.rotateY * -1
165
- return {
166
- translate,
167
- scale,
168
- rotate,
169
- rotateX,
170
- rotateY,
171
- value: `translate(${translate[0]}px, ${translate[1]}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
172
- }
173
- }),
174
- )
175
- }
176
-
177
- export const gridGraphicTransformObservable = ({ computedData$, fullDataFormatter$, layout$ }: {
178
- computedData$: Observable<ComputedDataTypeMap<'grid'>>
179
- fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
180
- layout$: Observable<Layout>
181
- }): Observable<TransformData> => {
182
- const destroy$ = new Subject()
183
-
184
- function calcGridDataAreaTransform ({ data, groupAxis, valueAxis, width, height }: {
185
- data: ComputedDataTypeMap<'grid'>
186
- groupAxis: DataFormatterGroupAxis
187
- valueAxis: DataFormatterValueAxis
188
- width: number
189
- height: number
190
- }): TransformData {
191
- let translateX = 0
192
- let translateY = 0
193
- let scaleX = 0
194
- let scaleY = 0
195
-
196
- // -- groupScale --
197
- const groupAxisWidth = (groupAxis.position === 'top' || groupAxis.position === 'bottom')
198
- ? width
199
- : height
200
- const groupMin = 0
201
- const groupMax = data[0] ? data[0].length - 1 : 0
202
- const groupScaleDomainMin = groupAxis.scaleDomain[0] === 'auto'
203
- ? groupMin - groupAxis.scalePadding
204
- : groupAxis.scaleDomain[0] as number - groupAxis.scalePadding
205
- const groupScaleDomainMax = groupAxis.scaleDomain[1] === 'auto'
206
- ? groupMax + groupAxis.scalePadding
207
- : groupAxis.scaleDomain[1] as number + groupAxis.scalePadding
208
-
209
- const groupScale: d3.ScaleLinear<number, number> = createAxisLinearScale({
210
- maxValue: groupMax,
211
- minValue: groupMin,
212
- axisWidth: groupAxisWidth,
213
- // scaleDomain: groupAxis.scaleDomain,
214
- scaleDomain: [groupScaleDomainMin, groupScaleDomainMax],
215
- scaleRange: [0, 1]
216
- })
217
-
218
- // -- translateX, scaleX --
219
- const rangeMinX = groupScale(groupMin)
220
- const rangeMaxX = groupScale(groupMax)
221
- translateX = rangeMinX
222
- const gWidth = rangeMaxX - rangeMinX
223
- scaleX = gWidth / groupAxisWidth
224
-
225
- // -- valueScale --
226
- const filteredData = data.map((d, i) => {
227
- return d.filter((_d, _i) => {
228
- return _i >= groupScaleDomainMin && _i <= groupScaleDomainMax && _d.visible == true
229
- })
230
- })
231
-
232
- const filteredMinAndMax = getMinAndMaxGrid(filteredData)
233
-
234
- const valueAxisWidth = (valueAxis.position === 'left' || valueAxis.position === 'right')
235
- ? height
236
- : width
237
-
238
- const valueScale: d3.ScaleLinear<number, number> = createAxisLinearScale({
239
- maxValue: filteredMinAndMax[1],
240
- minValue: filteredMinAndMax[0],
241
- axisWidth: valueAxisWidth,
242
- scaleDomain: valueAxis.scaleDomain,
243
- scaleRange: valueAxis.scaleRange
244
- })
245
-
246
- // -- translateY, scaleY --
247
- const minAndMax = getMinAndMaxGrid(data)
248
- const rangeMinY = valueScale(minAndMax[0])
249
- const rangeMaxY = valueScale(minAndMax[1])
250
- translateY = rangeMinY
251
- const gHeight = rangeMaxY - rangeMinY
252
- scaleY = gHeight / valueAxisWidth
253
-
254
- return {
255
- translate: [translateX, translateY],
256
- scale: [scaleX, scaleY],
257
- rotate: 0,
258
- rotateX: 0,
259
- rotateY: 0,
260
- value: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`
261
- }
262
- }
263
-
264
- return new Observable(subscriber => {
265
- combineLatest({
266
- computedData: computedData$,
267
- fullDataFormatter: fullDataFormatter$,
268
- layout: layout$
269
- }).pipe(
270
- takeUntil(destroy$),
271
- switchMap(async (d) => d),
272
- ).subscribe(data => {
273
- const dataAreaTransformData = calcGridDataAreaTransform ({
274
- data: data.computedData,
275
- groupAxis: data.fullDataFormatter.grid.groupAxis,
276
- valueAxis: data.fullDataFormatter.grid.valueAxis,
277
- width: data.layout.width,
278
- height: data.layout.height
279
- })
280
-
281
- subscriber.next(dataAreaTransformData)
282
- })
283
-
284
- return function unscbscribe () {
285
- destroy$.next(undefined)
286
- }
287
- })
288
- }
289
-
290
- export const gridGraphicReverseScaleObservable = ({ gridContainer$, gridAxesTransform$, gridGraphicTransform$ }: {
291
- gridContainer$: Observable<ContainerPosition[]>
292
- gridAxesTransform$: Observable<TransformData>
293
- gridGraphicTransform$: Observable<TransformData>
294
- }): Observable<[number, number][]> => {
295
- return combineLatest({
296
- gridContainer: gridContainer$,
297
- gridAxesTransform: gridAxesTransform$,
298
- gridGraphicTransform: gridGraphicTransform$,
299
- }).pipe(
300
- switchMap(async (d) => d),
301
- map(data => {
302
- if (data.gridAxesTransform.rotate == 0 || data.gridAxesTransform.rotate == 180) {
303
- return data.gridContainer.map((series, seriesIndex) => {
304
- return [
305
- 1 / data.gridGraphicTransform.scale[0] / data.gridContainer[seriesIndex].scale[0],
306
- 1 / data.gridGraphicTransform.scale[1] / data.gridContainer[seriesIndex].scale[1],
307
- ]
308
- })
309
- } else {
310
- return data.gridContainer.map((series, seriesIndex) => {
311
- // 由於有垂直的旋轉,所以外層 (container) x和y的scale要互換
312
- return [
313
- 1 / data.gridGraphicTransform.scale[0] / data.gridContainer[seriesIndex].scale[1],
314
- 1 / data.gridGraphicTransform.scale[1] / data.gridContainer[seriesIndex].scale[0],
315
- ]
316
- })
317
- }
318
- }),
319
- )
320
- }
321
-
322
- export const gridAxesSizeObservable = ({ fullDataFormatter$, layout$ }: {
323
- fullDataFormatter$: Observable<DataFormatterGrid>
324
- layout$: Observable<Layout>
325
- }): Observable<{
326
- width: number;
327
- height: number;
328
- }> => {
329
- const destroy$ = new Subject()
330
-
331
- function calcAxesSize ({ xAxisPosition, yAxisPosition, width, height }: {
332
- xAxisPosition: AxisPosition
333
- yAxisPosition: AxisPosition
334
- width: number
335
- height: number
336
- }) {
337
- if ((xAxisPosition === 'bottom' || xAxisPosition === 'top') && (yAxisPosition === 'left' || yAxisPosition === 'right')) {
338
- return { width, height }
339
- } else if ((xAxisPosition === 'left' || xAxisPosition === 'right') && (yAxisPosition === 'bottom' || yAxisPosition === 'top')) {
340
- return {
341
- width: height,
342
- height: width
343
- }
344
- }
345
- }
346
-
347
- return new Observable(subscriber => {
348
- combineLatest({
349
- fullDataFormatter: fullDataFormatter$,
350
- layout: layout$
351
- }).pipe(
352
- takeUntil(destroy$),
353
- switchMap(async (d) => d),
354
- ).subscribe(data => {
355
-
356
- const axisSize = calcAxesSize({
357
- xAxisPosition: data.fullDataFormatter.grid.groupAxis.position,
358
- yAxisPosition: data.fullDataFormatter.grid.valueAxis.position,
359
- width: data.layout.width,
360
- height: data.layout.height,
361
- })
362
-
363
- subscriber.next(axisSize)
364
-
365
- return function unsubscribe () {
366
- destroy$.next(undefined)
367
- }
368
- })
369
- })
370
- }
371
-
372
- // export const gridHighlightObservable = ({ computedData$, fullChartParams$, event$ }: {
373
- // computedData$: Observable<ComputedDataTypeMap<'grid'>>
374
- // fullChartParams$: Observable<ChartParams>
375
- // event$: Subject<any>
376
- // }): Observable<string[]> => {
377
- // const datumList$ = computedData$.pipe(
378
- // map(d => d.flat())
379
- // )
380
- // return highlightObservable ({ datumList$, fullChartParams$, event$ })
381
- // }
382
-
383
- export const existSeriesLabelsObservable = ({ computedData$ }: { computedData$: Observable<ComputedDataTypeMap<'grid'>> }) => {
384
- return computedData$.pipe(
385
- map(data => {
386
- return data
387
- .filter(series => series.length)
388
- .map(series => {
389
- return series[0].seriesLabel
390
- })
391
- }),
392
- distinctUntilChanged((a, b) => {
393
- return JSON.stringify(a).length === JSON.stringify(b).length
394
- }),
395
- )
396
- }
397
-
398
- export const gridVisibleComputedDataObservable = ({ computedData$ }: { computedData$: Observable<ComputedDataTypeMap<'grid'>> }) => {
399
- return computedData$.pipe(
400
- map(data => {
401
- const visibleComputedData = data
402
- .map(d => {
403
- return d.filter(_d => {
404
- return _d.visible == true
405
- })
406
- })
407
- .filter(d => d.length)
408
- return visibleComputedData
409
- })
410
- )
411
- }
412
-
413
- export const isSeriesPositionSeprateObservable = ({ computedData$, fullDataFormatter$ }: {
414
- computedData$: Observable<ComputedDataTypeMap<'grid'>>
415
- fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
416
- }) => {
417
- return combineLatest({
418
- computedData: computedData$,
419
- fullDataFormatter: fullDataFormatter$
420
- }).pipe(
421
- map(data => {
422
- return data.fullDataFormatter.grid.seriesSlotIndexes && data.fullDataFormatter.grid.seriesSlotIndexes.length === data.computedData.length
423
- ? true
424
- : false
425
- }),
426
- distinctUntilChanged()
427
- )
428
- }
429
-
430
- // 所有container位置(對應series)
431
- export const gridContainerObservable = ({ computedData$, fullDataFormatter$, fullChartParams$, layout$ }: {
432
- computedData$: Observable<ComputedDataTypeMap<'grid'>>
433
- fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
434
- fullChartParams$: Observable<ChartParams>
435
- layout$: Observable<Layout>
436
- }) => {
437
-
438
- const gridContainer$ = combineLatest({
439
- computedData: computedData$,
440
- fullDataFormatter: fullDataFormatter$,
441
- fullChartParams: fullChartParams$,
442
- layout: layout$,
443
- }).pipe(
444
- switchMap(async (d) => d),
445
- map(data => {
446
-
447
- const grid = data.fullDataFormatter.grid
448
-
449
- // 有設定series定位
450
- const hasSeriesPosition = grid.seriesSlotIndexes && grid.seriesSlotIndexes.length === data.computedData.length
451
- ? true
452
- : false
453
-
454
- if (hasSeriesPosition) {
455
- // -- 依seriesSlotIndexes計算 --
456
- return data.computedData.map((seriesData, seriesIndex) => {
457
- const columnIndex = grid.seriesSlotIndexes[seriesIndex] % data.fullDataFormatter.container.columnAmount
458
- const rowIndex = Math.floor(grid.seriesSlotIndexes[seriesIndex] / data.fullDataFormatter.container.columnAmount)
459
- const { translate, scale } = calcGridContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
460
- return {
461
- slotIndex: grid.seriesSlotIndexes[seriesIndex],
462
- rowIndex,
463
- columnIndex,
464
- translate,
465
- scale,
466
- }
467
- })
468
- } else {
469
- // -- 依grid的slotIndex計算 --
470
- const columnIndex = grid.slotIndex % data.fullDataFormatter.container.columnAmount
471
- const rowIndex = Math.floor(grid.slotIndex / data.fullDataFormatter.container.columnAmount)
472
- return data.computedData.map((seriesData, seriesIndex) => {
473
- const { translate, scale } = calcGridContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
474
- return {
475
- slotIndex: grid.slotIndex,
476
- rowIndex,
477
- columnIndex,
478
- translate,
479
- scale,
480
- }
481
- })
482
- }
483
- })
484
- )
485
-
486
- return gridContainer$
1
+ import {
2
+ combineLatest,
3
+ distinctUntilChanged,
4
+ filter,
5
+ map,
6
+ merge,
7
+ takeUntil,
8
+ shareReplay,
9
+ switchMap,
10
+ Subject,
11
+ Observable } from 'rxjs'
12
+ import type {
13
+ AxisPosition,
14
+ ChartType,
15
+ ChartParams,
16
+ ComputedDataTypeMap,
17
+ ComputedDatumTypeMap,
18
+ ContextObserverFn,
19
+ DataTypeMap,
20
+ DataFormatterTypeMap,
21
+ DataFormatterGrid,
22
+ DataFormatterGridContainer,
23
+ DataFormatterValueAxis,
24
+ DataFormatterGroupAxis,
25
+ ContainerPosition,
26
+ HighlightTarget,
27
+ Layout,
28
+ TransformData } from '../types'
29
+ import { getMinAndMaxGrid } from '../utils/orbchartsUtils'
30
+ import { createAxisLinearScale, createAxisPointScale, createAxisQuantizeScale } from '../utils/d3Utils'
31
+ import { highlightObservable } from '../utils/observables'
32
+ import { calcGridContainerPosition } from '../utils/orbchartsUtils'
33
+ import { DATA_FORMATTER_GRID_GRID_DEFAULT } from '../defaults'
34
+
35
+ export const gridAxesTransformObservable = ({ fullDataFormatter$, layout$ }: {
36
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
37
+ layout$: Observable<Layout>
38
+ }): Observable<TransformData> => {
39
+ const destroy$ = new Subject()
40
+
41
+ function calcAxesTransform ({ xAxis, yAxis, width, height }: {
42
+ xAxis: DataFormatterGroupAxis | DataFormatterValueAxis,
43
+ yAxis: DataFormatterValueAxis,
44
+ width: number,
45
+ height: number
46
+ }): TransformData {
47
+ if (!xAxis || !yAxis) {
48
+ return {
49
+ translate: [0, 0],
50
+ scale: [1, 1],
51
+ rotate: 0,
52
+ rotateX: 0,
53
+ rotateY: 0,
54
+ value: ''
55
+ }
56
+ }
57
+ // const width = size.width - fullChartParams.layout.left - fullChartParams.layout.right
58
+ // const height = size.height - fullChartParams.layout.top - fullChartParams.layout.bottom
59
+ let translateX = 0
60
+ let translateY = 0
61
+ let rotate = 0
62
+ let rotateX = 0
63
+ let rotateY = 0
64
+ if (xAxis.position === 'bottom') {
65
+ if (yAxis.position === 'left') {
66
+ rotateX = 180
67
+ translateY = height
68
+ } else if (yAxis.position === 'right') {
69
+ rotateX = 180
70
+ rotateY = 180
71
+ translateX = width
72
+ translateY = height
73
+ } else {
74
+ // 預設
75
+ rotateX = 180
76
+ translateY = height
77
+ }
78
+ } else if (xAxis.position === 'top') {
79
+ if (yAxis.position === 'left') {
80
+ } else if (yAxis.position === 'right') {
81
+ rotateY = 180
82
+ translateX = width
83
+ } else {
84
+ // 預設
85
+ rotateX = 180
86
+ translateY = height
87
+ }
88
+ } else if (xAxis.position === 'left') {
89
+ if (yAxis.position === 'bottom') {
90
+ rotate = -90
91
+ translateY = height
92
+ } else if (yAxis.position === 'top') {
93
+ rotate = -90
94
+ rotateY = 180
95
+ } else {
96
+ // 預設
97
+ rotateX = 180
98
+ translateY = height
99
+ }
100
+ } else if (xAxis.position === 'right') {
101
+ if (yAxis.position === 'bottom') {
102
+ rotate = -90
103
+ rotateX = 180
104
+ translateY = height
105
+ translateX = width
106
+ } else if (yAxis.position === 'top') {
107
+ rotate = -90
108
+ rotateX = 180
109
+ rotateY = 180
110
+ translateX = width
111
+ } else {
112
+ // 預設
113
+ rotateX = 180
114
+ translateY = height
115
+ }
116
+ }
117
+ // selection.style('transform', `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`)
118
+
119
+ return {
120
+ translate: [translateX, translateY],
121
+ scale: [1, 1],
122
+ rotate,
123
+ rotateX,
124
+ rotateY,
125
+ value: `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
126
+ }
127
+ }
128
+
129
+ return new Observable(subscriber => {
130
+ combineLatest({
131
+ fullDataFormatter: fullDataFormatter$,
132
+ layout: layout$
133
+ }).pipe(
134
+ takeUntil(destroy$),
135
+ switchMap(async (d) => d),
136
+ ).subscribe(data => {
137
+ const axesTransformData = calcAxesTransform({
138
+ xAxis: data.fullDataFormatter.grid.groupAxis,
139
+ yAxis: data.fullDataFormatter.grid.valueAxis,
140
+ width: data.layout.width,
141
+ height: data.layout.height
142
+ })
143
+
144
+ subscriber.next(axesTransformData)
145
+ })
146
+
147
+ return function unscbscribe () {
148
+ destroy$.next(undefined)
149
+ }
150
+ })
151
+ }
152
+
153
+
154
+ export const gridAxesReverseTransformObservable = ({ gridAxesTransform$ }: {
155
+ gridAxesTransform$: Observable<TransformData>
156
+ }): Observable<TransformData> => {
157
+ return gridAxesTransform$.pipe(
158
+ map(d => {
159
+ // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
160
+ const translate: [number, number] = [0, 0] // 無需逆轉
161
+ const scale: [number, number] = [1 / d.scale[0], 1 / d.scale[1]]
162
+ const rotate = d.rotate * -1
163
+ const rotateX = d.rotateX * -1
164
+ const rotateY = d.rotateY * -1
165
+ return {
166
+ translate,
167
+ scale,
168
+ rotate,
169
+ rotateX,
170
+ rotateY,
171
+ value: `translate(${translate[0]}px, ${translate[1]}px) rotate(${rotate}deg) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
172
+ }
173
+ }),
174
+ )
175
+ }
176
+
177
+ export const gridGraphicTransformObservable = ({ computedData$, fullDataFormatter$, layout$ }: {
178
+ computedData$: Observable<ComputedDataTypeMap<'grid'>>
179
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
180
+ layout$: Observable<Layout>
181
+ }): Observable<TransformData> => {
182
+ const destroy$ = new Subject()
183
+
184
+ function calcGridDataAreaTransform ({ data, groupAxis, valueAxis, width, height }: {
185
+ data: ComputedDataTypeMap<'grid'>
186
+ groupAxis: DataFormatterGroupAxis
187
+ valueAxis: DataFormatterValueAxis
188
+ width: number
189
+ height: number
190
+ }): TransformData {
191
+ let translateX = 0
192
+ let translateY = 0
193
+ let scaleX = 0
194
+ let scaleY = 0
195
+
196
+ // -- groupScale --
197
+ const groupAxisWidth = (groupAxis.position === 'top' || groupAxis.position === 'bottom')
198
+ ? width
199
+ : height
200
+ const groupMin = 0
201
+ const groupMax = data[0] ? data[0].length - 1 : 0
202
+ const groupScaleDomainMin = groupAxis.scaleDomain[0] === 'auto'
203
+ ? groupMin - groupAxis.scalePadding
204
+ : groupAxis.scaleDomain[0] as number - groupAxis.scalePadding
205
+ const groupScaleDomainMax = groupAxis.scaleDomain[1] === 'auto'
206
+ ? groupMax + groupAxis.scalePadding
207
+ : groupAxis.scaleDomain[1] as number + groupAxis.scalePadding
208
+
209
+ const groupScale: d3.ScaleLinear<number, number> = createAxisLinearScale({
210
+ maxValue: groupMax,
211
+ minValue: groupMin,
212
+ axisWidth: groupAxisWidth,
213
+ // scaleDomain: groupAxis.scaleDomain,
214
+ scaleDomain: [groupScaleDomainMin, groupScaleDomainMax],
215
+ scaleRange: [0, 1]
216
+ })
217
+
218
+ // -- translateX, scaleX --
219
+ const rangeMinX = groupScale(groupMin)
220
+ const rangeMaxX = groupScale(groupMax)
221
+ translateX = rangeMinX
222
+ const gWidth = rangeMaxX - rangeMinX
223
+ scaleX = gWidth / groupAxisWidth
224
+
225
+ // -- valueScale --
226
+ const filteredData = data.map((d, i) => {
227
+ return d.filter((_d, _i) => {
228
+ return _i >= groupScaleDomainMin && _i <= groupScaleDomainMax && _d.visible == true
229
+ })
230
+ })
231
+
232
+ const filteredMinAndMax = getMinAndMaxGrid(filteredData)
233
+
234
+ const valueAxisWidth = (valueAxis.position === 'left' || valueAxis.position === 'right')
235
+ ? height
236
+ : width
237
+
238
+ const valueScale: d3.ScaleLinear<number, number> = createAxisLinearScale({
239
+ maxValue: filteredMinAndMax[1],
240
+ minValue: filteredMinAndMax[0],
241
+ axisWidth: valueAxisWidth,
242
+ scaleDomain: valueAxis.scaleDomain,
243
+ scaleRange: valueAxis.scaleRange
244
+ })
245
+
246
+ // -- translateY, scaleY --
247
+ const minAndMax = getMinAndMaxGrid(data)
248
+ const rangeMinY = valueScale(minAndMax[0])
249
+ const rangeMaxY = valueScale(minAndMax[1])
250
+ translateY = rangeMinY
251
+ const gHeight = rangeMaxY - rangeMinY
252
+ scaleY = gHeight / valueAxisWidth
253
+
254
+ return {
255
+ translate: [translateX, translateY],
256
+ scale: [scaleX, scaleY],
257
+ rotate: 0,
258
+ rotateX: 0,
259
+ rotateY: 0,
260
+ value: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`
261
+ }
262
+ }
263
+
264
+ return new Observable(subscriber => {
265
+ combineLatest({
266
+ computedData: computedData$,
267
+ fullDataFormatter: fullDataFormatter$,
268
+ layout: layout$
269
+ }).pipe(
270
+ takeUntil(destroy$),
271
+ switchMap(async (d) => d),
272
+ ).subscribe(data => {
273
+ const dataAreaTransformData = calcGridDataAreaTransform ({
274
+ data: data.computedData,
275
+ groupAxis: data.fullDataFormatter.grid.groupAxis,
276
+ valueAxis: data.fullDataFormatter.grid.valueAxis,
277
+ width: data.layout.width,
278
+ height: data.layout.height
279
+ })
280
+
281
+ subscriber.next(dataAreaTransformData)
282
+ })
283
+
284
+ return function unscbscribe () {
285
+ destroy$.next(undefined)
286
+ }
287
+ })
288
+ }
289
+
290
+ export const gridGraphicReverseScaleObservable = ({ gridContainer$, gridAxesTransform$, gridGraphicTransform$ }: {
291
+ gridContainer$: Observable<ContainerPosition[]>
292
+ gridAxesTransform$: Observable<TransformData>
293
+ gridGraphicTransform$: Observable<TransformData>
294
+ }): Observable<[number, number][]> => {
295
+ return combineLatest({
296
+ gridContainer: gridContainer$,
297
+ gridAxesTransform: gridAxesTransform$,
298
+ gridGraphicTransform: gridGraphicTransform$,
299
+ }).pipe(
300
+ switchMap(async (d) => d),
301
+ map(data => {
302
+ if (data.gridAxesTransform.rotate == 0 || data.gridAxesTransform.rotate == 180) {
303
+ return data.gridContainer.map((series, seriesIndex) => {
304
+ return [
305
+ 1 / data.gridGraphicTransform.scale[0] / data.gridContainer[seriesIndex].scale[0],
306
+ 1 / data.gridGraphicTransform.scale[1] / data.gridContainer[seriesIndex].scale[1],
307
+ ]
308
+ })
309
+ } else {
310
+ return data.gridContainer.map((series, seriesIndex) => {
311
+ // 由於有垂直的旋轉,所以外層 (container) x和y的scale要互換
312
+ return [
313
+ 1 / data.gridGraphicTransform.scale[0] / data.gridContainer[seriesIndex].scale[1],
314
+ 1 / data.gridGraphicTransform.scale[1] / data.gridContainer[seriesIndex].scale[0],
315
+ ]
316
+ })
317
+ }
318
+ }),
319
+ )
320
+ }
321
+
322
+ export const gridAxesSizeObservable = ({ fullDataFormatter$, layout$ }: {
323
+ fullDataFormatter$: Observable<DataFormatterGrid>
324
+ layout$: Observable<Layout>
325
+ }): Observable<{
326
+ width: number;
327
+ height: number;
328
+ }> => {
329
+ const destroy$ = new Subject()
330
+
331
+ function calcAxesSize ({ xAxisPosition, yAxisPosition, width, height }: {
332
+ xAxisPosition: AxisPosition
333
+ yAxisPosition: AxisPosition
334
+ width: number
335
+ height: number
336
+ }) {
337
+ if ((xAxisPosition === 'bottom' || xAxisPosition === 'top') && (yAxisPosition === 'left' || yAxisPosition === 'right')) {
338
+ return { width, height }
339
+ } else if ((xAxisPosition === 'left' || xAxisPosition === 'right') && (yAxisPosition === 'bottom' || yAxisPosition === 'top')) {
340
+ return {
341
+ width: height,
342
+ height: width
343
+ }
344
+ }
345
+ }
346
+
347
+ return new Observable(subscriber => {
348
+ combineLatest({
349
+ fullDataFormatter: fullDataFormatter$,
350
+ layout: layout$
351
+ }).pipe(
352
+ takeUntil(destroy$),
353
+ switchMap(async (d) => d),
354
+ ).subscribe(data => {
355
+
356
+ const axisSize = calcAxesSize({
357
+ xAxisPosition: data.fullDataFormatter.grid.groupAxis.position,
358
+ yAxisPosition: data.fullDataFormatter.grid.valueAxis.position,
359
+ width: data.layout.width,
360
+ height: data.layout.height,
361
+ })
362
+
363
+ subscriber.next(axisSize)
364
+
365
+ return function unsubscribe () {
366
+ destroy$.next(undefined)
367
+ }
368
+ })
369
+ })
370
+ }
371
+
372
+ // export const gridHighlightObservable = ({ computedData$, fullChartParams$, event$ }: {
373
+ // computedData$: Observable<ComputedDataTypeMap<'grid'>>
374
+ // fullChartParams$: Observable<ChartParams>
375
+ // event$: Subject<any>
376
+ // }): Observable<string[]> => {
377
+ // const datumList$ = computedData$.pipe(
378
+ // map(d => d.flat())
379
+ // )
380
+ // return highlightObservable ({ datumList$, fullChartParams$, event$ })
381
+ // }
382
+
383
+ export const existSeriesLabelsObservable = ({ computedData$ }: { computedData$: Observable<ComputedDataTypeMap<'grid'>> }) => {
384
+ return computedData$.pipe(
385
+ map(data => {
386
+ return data
387
+ .filter(series => series.length)
388
+ .map(series => {
389
+ return series[0].seriesLabel
390
+ })
391
+ }),
392
+ distinctUntilChanged((a, b) => {
393
+ return JSON.stringify(a).length === JSON.stringify(b).length
394
+ }),
395
+ )
396
+ }
397
+
398
+ export const gridVisibleComputedDataObservable = ({ computedData$ }: { computedData$: Observable<ComputedDataTypeMap<'grid'>> }) => {
399
+ return computedData$.pipe(
400
+ map(data => {
401
+ const visibleComputedData = data
402
+ .map(d => {
403
+ return d.filter(_d => {
404
+ return _d.visible == true
405
+ })
406
+ })
407
+ .filter(d => d.length)
408
+ return visibleComputedData
409
+ })
410
+ )
411
+ }
412
+
413
+ export const isSeriesPositionSeprateObservable = ({ computedData$, fullDataFormatter$ }: {
414
+ computedData$: Observable<ComputedDataTypeMap<'grid'>>
415
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
416
+ }) => {
417
+ return combineLatest({
418
+ computedData: computedData$,
419
+ fullDataFormatter: fullDataFormatter$
420
+ }).pipe(
421
+ map(data => {
422
+ return data.fullDataFormatter.grid.seriesSlotIndexes && data.fullDataFormatter.grid.seriesSlotIndexes.length === data.computedData.length
423
+ ? true
424
+ : false
425
+ }),
426
+ distinctUntilChanged()
427
+ )
428
+ }
429
+
430
+ // 所有container位置(對應series)
431
+ export const gridContainerObservable = ({ computedData$, fullDataFormatter$, fullChartParams$, layout$ }: {
432
+ computedData$: Observable<ComputedDataTypeMap<'grid'>>
433
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'grid'>>
434
+ fullChartParams$: Observable<ChartParams>
435
+ layout$: Observable<Layout>
436
+ }) => {
437
+
438
+ const gridContainer$ = combineLatest({
439
+ computedData: computedData$,
440
+ fullDataFormatter: fullDataFormatter$,
441
+ fullChartParams: fullChartParams$,
442
+ layout: layout$,
443
+ }).pipe(
444
+ switchMap(async (d) => d),
445
+ map(data => {
446
+
447
+ const grid = data.fullDataFormatter.grid
448
+
449
+ // 有設定series定位
450
+ const hasSeriesPosition = grid.seriesSlotIndexes && grid.seriesSlotIndexes.length === data.computedData.length
451
+ ? true
452
+ : false
453
+
454
+ if (hasSeriesPosition) {
455
+ // -- 依seriesSlotIndexes計算 --
456
+ return data.computedData.map((seriesData, seriesIndex) => {
457
+ const columnIndex = grid.seriesSlotIndexes[seriesIndex] % data.fullDataFormatter.container.columnAmount
458
+ const rowIndex = Math.floor(grid.seriesSlotIndexes[seriesIndex] / data.fullDataFormatter.container.columnAmount)
459
+ const { translate, scale } = calcGridContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
460
+ return {
461
+ slotIndex: grid.seriesSlotIndexes[seriesIndex],
462
+ rowIndex,
463
+ columnIndex,
464
+ translate,
465
+ scale,
466
+ }
467
+ })
468
+ } else {
469
+ // -- 依grid的slotIndex計算 --
470
+ const columnIndex = grid.slotIndex % data.fullDataFormatter.container.columnAmount
471
+ const rowIndex = Math.floor(grid.slotIndex / data.fullDataFormatter.container.columnAmount)
472
+ return data.computedData.map((seriesData, seriesIndex) => {
473
+ const { translate, scale } = calcGridContainerPosition(data.layout, data.fullDataFormatter.container, rowIndex, columnIndex)
474
+ return {
475
+ slotIndex: grid.slotIndex,
476
+ rowIndex,
477
+ columnIndex,
478
+ translate,
479
+ scale,
480
+ }
481
+ })
482
+ }
483
+ })
484
+ )
485
+
486
+ return gridContainer$
487
487
  }