@orbcharts/plugins-basic 3.0.0-alpha.67 → 3.0.0-alpha.69

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +3436 -3358
  3. package/dist/orbcharts-plugins-basic.umd.js +14 -13
  4. package/dist/src/utils/d3Graphics.d.ts +10 -0
  5. package/package.json +42 -42
  6. package/src/base/BaseBarStack.ts +779 -779
  7. package/src/base/BaseBars.ts +764 -764
  8. package/src/base/BaseBarsTriangle.ts +672 -672
  9. package/src/base/BaseDots.ts +513 -513
  10. package/src/base/BaseGroupAxis.ts +675 -652
  11. package/src/base/BaseLegend.ts +642 -642
  12. package/src/base/BaseLineAreas.ts +628 -628
  13. package/src/base/BaseLines.ts +704 -704
  14. package/src/base/BaseValueAxis.ts +578 -578
  15. package/src/base/types.ts +2 -2
  16. package/src/grid/defaults.ts +128 -128
  17. package/src/grid/gridObservables.ts +543 -543
  18. package/src/grid/index.ts +15 -15
  19. package/src/grid/plugins/BarStack.ts +43 -43
  20. package/src/grid/plugins/Bars.ts +44 -44
  21. package/src/grid/plugins/BarsPN.ts +41 -41
  22. package/src/grid/plugins/BarsTriangle.ts +42 -42
  23. package/src/grid/plugins/Dots.ts +37 -37
  24. package/src/grid/plugins/GridLegend.ts +59 -59
  25. package/src/grid/plugins/GroupAux.ts +1014 -991
  26. package/src/grid/plugins/GroupAxis.ts +36 -36
  27. package/src/grid/plugins/LineAreas.ts +40 -40
  28. package/src/grid/plugins/Lines.ts +40 -40
  29. package/src/grid/plugins/ScalingArea.ts +174 -174
  30. package/src/grid/plugins/ValueAxis.ts +36 -36
  31. package/src/grid/plugins/ValueStackAxis.ts +38 -38
  32. package/src/grid/types.ts +123 -123
  33. package/src/index.ts +9 -9
  34. package/src/multiGrid/defaults.ts +158 -158
  35. package/src/multiGrid/index.ts +13 -13
  36. package/src/multiGrid/multiGridObservables.ts +49 -49
  37. package/src/multiGrid/plugins/MultiBarStack.ts +78 -78
  38. package/src/multiGrid/plugins/MultiBars.ts +77 -77
  39. package/src/multiGrid/plugins/MultiBarsTriangle.ts +77 -77
  40. package/src/multiGrid/plugins/MultiDots.ts +65 -65
  41. package/src/multiGrid/plugins/MultiGridLegend.ts +89 -89
  42. package/src/multiGrid/plugins/MultiGroupAxis.ts +70 -70
  43. package/src/multiGrid/plugins/MultiLineAreas.ts +77 -77
  44. package/src/multiGrid/plugins/MultiLines.ts +77 -77
  45. package/src/multiGrid/plugins/MultiValueAxis.ts +69 -69
  46. package/src/multiGrid/plugins/MultiValueStackAxis.ts +69 -69
  47. package/src/multiGrid/plugins/OverlappingValueAxes.ts +170 -170
  48. package/src/multiGrid/plugins/OverlappingValueStackAxes.ts +169 -169
  49. package/src/multiGrid/types.ts +72 -72
  50. package/src/noneData/defaults.ts +102 -102
  51. package/src/noneData/index.ts +3 -3
  52. package/src/noneData/plugins/Container.ts +10 -10
  53. package/src/noneData/plugins/Tooltip.ts +327 -327
  54. package/src/noneData/types.ts +26 -26
  55. package/src/series/defaults.ts +149 -149
  56. package/src/series/index.ts +9 -9
  57. package/src/series/plugins/Bubbles.ts +545 -545
  58. package/src/series/plugins/Pie.ts +584 -584
  59. package/src/series/plugins/PieEventTexts.ts +262 -262
  60. package/src/series/plugins/PieLabels.ts +604 -598
  61. package/src/series/plugins/Rose.ts +481 -481
  62. package/src/series/plugins/RoseLabels.ts +571 -565
  63. package/src/series/plugins/SeriesLegend.ts +59 -59
  64. package/src/series/seriesObservables.ts +145 -145
  65. package/src/series/seriesUtils.ts +51 -51
  66. package/src/series/types.ts +87 -87
  67. package/src/tree/defaults.ts +23 -23
  68. package/src/tree/index.ts +3 -3
  69. package/src/tree/plugins/TreeLegend.ts +59 -59
  70. package/src/tree/plugins/TreeMap.ts +305 -305
  71. package/src/tree/types.ts +23 -23
  72. package/src/utils/commonUtils.ts +21 -21
  73. package/src/utils/d3Graphics.ts +174 -124
  74. package/src/utils/d3Utils.ts +73 -73
  75. package/src/utils/observables.ts +14 -14
  76. package/src/utils/orbchartsUtils.ts +100 -100
  77. package/tsconfig.base.json +13 -13
  78. package/tsconfig.json +2 -2
  79. package/vite.config.js +22 -22
@@ -1,780 +1,780 @@
1
- import * as d3 from 'd3'
2
- import {
3
- iif,
4
- combineLatest,
5
- map,
6
- switchMap,
7
- takeUntil,
8
- distinctUntilChanged,
9
- Observable,
10
- Subject } from 'rxjs'
11
- import type { BasePluginFn } from './types'
12
- import type {
13
- ComputedDatumGrid,
14
- ComputedDataGrid,
15
- ComputedLayoutDatumGrid,
16
- ComputedLayoutDataGrid,
17
- DataFormatterGrid,
18
- EventGrid,
19
- ChartParams,
20
- GridContainerPosition,
21
- Layout,
22
- TransformData } from '@orbcharts/core'
23
- import { getD3TransitionEase } from '../utils/d3Utils'
24
- import { getClassName, getUniID } from '../utils/orbchartsUtils'
25
- import { gridSelectionsObservable } from '../grid/gridObservables'
26
- import { shareReplay } from 'rxjs/operators'
27
-
28
- export interface BaseBarStackParams {
29
- barWidth: number
30
- barGroupPadding: number
31
- barRadius: number | boolean
32
- }
33
-
34
- interface BaseBarStackContext {
35
- selection: d3.Selection<any, unknown, any, unknown>
36
- computedData$: Observable<ComputedDataGrid>
37
- computedLayoutData$: Observable<ComputedLayoutDataGrid>
38
- visibleComputedData$: Observable<ComputedDatumGrid[][]>
39
- visibleComputedLayoutData$: Observable<ComputedLayoutDataGrid>
40
- seriesLabels$: Observable<string[]>
41
- SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
42
- GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
43
- fullParams$: Observable<BaseBarStackParams>
44
- fullDataFormatter$: Observable<DataFormatterGrid>
45
- fullChartParams$: Observable<ChartParams>
46
- gridAxesTransform$: Observable<TransformData>
47
- gridGraphicTransform$: Observable<TransformData>
48
- gridGraphicReverseScale$: Observable<[number, number][]>
49
- gridAxesSize$: Observable<{
50
- width: number;
51
- height: number;
52
- }>
53
- gridHighlight$: Observable<ComputedDatumGrid[]>
54
- gridContainerPosition$: Observable<GridContainerPosition[]>
55
- isSeriesSeprate$: Observable<boolean>
56
- event$: Subject<EventGrid>
57
- }
58
-
59
-
60
- interface GraphicDatum extends ComputedLayoutDatumGrid {
61
- _barStartY: number // bar的起點y座標
62
- _barHeight: number // bar的高度
63
- }
64
-
65
- interface RenderBarParams {
66
- graphicGSelection: d3.Selection<SVGGElement, unknown, any, any>
67
- rectClassName: string
68
- barData: GraphicDatum[][]
69
- zeroY: number
70
- groupLabels: string[]
71
- // barScale: d3.ScalePoint<string>
72
- params: BaseBarStackParams
73
- chartParams: ChartParams
74
- barWidth: number
75
- transformedBarRadius: [number, number][]
76
- delayGroup: number
77
- transitionItem: number
78
- isSeriesSeprate: boolean
79
- }
80
-
81
- type ClipPathDatum = {
82
- id: string;
83
- // x: number;
84
- // y: number;
85
- width: number;
86
- height: number;
87
- }
88
-
89
- // const pluginName = 'BarStack'
90
- // const rectClassName = getClassName(pluginName, 'rect')
91
- // group的delay在動畫中的佔比(剩餘部份的時間為圖形本身的動畫時間,因為delay時間和最後一個group的動畫時間加總為1)
92
- const groupDelayProportionOfDuration = 0.3
93
-
94
- function calcBarWidth ({ axisWidth, groupAmount, barGroupPadding = 0 }: {
95
- axisWidth: number
96
- groupAmount: number
97
- barGroupPadding: number
98
- }) {
99
- const eachGroupWidth = axisWidth / (groupAmount - 1)
100
- const width = eachGroupWidth - barGroupPadding
101
- return width > 1 ? width : 1
102
-
103
- }
104
-
105
- // function makeBarScale (barWidth: number, seriesLabels: string[], params: BarStackParams) {
106
- // const barHalfWidth = barWidth! / 2
107
- // const barGroupWidth = barWidth * seriesLabels.length + params.barPadding! * seriesLabels.length
108
- // return d3.scalePoint()
109
- // .domain(seriesLabels)
110
- // .range([-barGroupWidth / 2 + barHalfWidth, barGroupWidth / 2 - barHalfWidth])
111
- // }
112
-
113
- function calcDelayGroup (barGroupAmount: number, totalDuration: number) {
114
- if (barGroupAmount <= 1) {
115
- // 一筆內計算會出錯所以不算
116
- return 0
117
- }
118
- return totalDuration / (barGroupAmount - 1) * groupDelayProportionOfDuration // 依group數量計算
119
- }
120
-
121
- function calctransitionItem (barGroupAmount: number, totalDuration: number) {
122
- if (barGroupAmount <= 1) {
123
- // 一筆內不會有delay
124
- return totalDuration
125
- }
126
- return totalDuration * (1 - groupDelayProportionOfDuration) // delay後剩餘的時間
127
- }
128
-
129
- function renderRectBars ({ graphicGSelection, rectClassName, barData, zeroY, groupLabels, params, chartParams, barWidth, transformedBarRadius, delayGroup, transitionItem, isSeriesSeprate }: RenderBarParams) {
130
-
131
- const barHalfWidth = barWidth! / 2
132
-
133
- graphicGSelection
134
- .each((seriesData, seriesIndex, g) => {
135
- d3.select(g[seriesIndex])
136
- .selectAll<SVGGElement, ComputedDatumGrid>(`rect.${rectClassName}`)
137
- .data(barData[seriesIndex] ?? [], d => d.id)
138
- .join(
139
- enter => {
140
- // console.log('enter')
141
- return enter
142
- .append('rect')
143
- .classed(rectClassName, true)
144
- .attr('cursor', 'pointer')
145
- .attr('height', d => 0)
146
- },
147
- update => update,
148
- exit => exit.remove()
149
- )
150
- .attr('transform', (d, i) => `translate(${(d ? d.axisX : 0) - barHalfWidth}, ${0})`)
151
- .attr('fill', d => d.color)
152
- .attr('y', d => zeroY)
153
- .attr('x', d =>0)
154
- .attr('width', barWidth!)
155
- .attr('rx', transformedBarRadius[seriesIndex][0] ?? 1)
156
- .attr('ry', transformedBarRadius[seriesIndex][1] ?? 1)
157
- .transition()
158
- .duration(transitionItem)
159
- .ease(getD3TransitionEase(chartParams.transitionEase))
160
- .delay((d, i) => d.groupIndex * delayGroup)
161
- .attr('y', d => d._barStartY)
162
- .attr('height', d => Math.abs(d._barHeight))
163
- })
164
-
165
- // const barGroup = graphicGSelection
166
- // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${gClassName}`)
167
- // .data(data, (d, i) => groupLabels[i])
168
- // .join(
169
- // enter => {
170
- // return enter
171
- // .append('g')
172
- // .classed(gClassName, true)
173
- // .attr('cursor', 'pointer')
174
- // },
175
- // update => update,
176
- // exit => exit.remove()
177
- // )
178
- // .attr('transform', (d, i) => `translate(${d[0] ? d[0].axisX : 0}, ${0})`)
179
- // .each((d, i, g) => {
180
- // const bars = d3.select(g[i])
181
- // .selectAll<SVGGElement, ComputedDatumGrid>('g')
182
- // .data(d, _d => _d.id)
183
- // .join(
184
- // enter => {
185
- // return enter
186
- // .append('g')
187
- // .classed(gContentClassName, true)
188
- // },
189
- // update => update,
190
- // exit => exit.remove()
191
- // )
192
- // .each((_d, _i, _g) => {
193
- // const rect = d3.select(_g[_i])
194
- // .selectAll<SVGRectElement, ComputedDatumGrid>('rect')
195
- // .data([_d], _d => _d.id)
196
- // .join(
197
- // enter => {
198
- // return enter
199
- // .append('rect')
200
- // .attr('y', d => zeroY)
201
- // .attr('height', d => 0)
202
- // },
203
- // update => update,
204
- // exit => exit.remove()
205
- // )
206
- // .attr('rx', transformedBarRadius[0])
207
- // .attr('ry', transformedBarRadius[1])
208
- // .attr('fill', d => d.color)
209
- // .attr('transform', `translate(${-barHalfWidth}, 0)`)
210
- // .attr('x', d => 0)
211
- // .attr('width', barWidth!)
212
- // .transition()
213
- // .duration(transitionItem)
214
- // .ease(getD3TransitionEase(chartParams.transitionEase))
215
- // .delay((d, i) => d.groupIndex * delayGroup)
216
- // .attr('y', d => d._barStartY)
217
- // .attr('height', d => Math.abs(d._barHeight))
218
- // })
219
-
220
- // })
221
-
222
- const graphicBarSelection: d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown> = graphicGSelection.selectAll(`rect.${rectClassName}`)
223
-
224
-
225
- return graphicBarSelection
226
- }
227
-
228
- function renderClipPath ({ defsSelection, clipPathData }: {
229
- defsSelection: d3.Selection<SVGDefsElement, any, any, any>
230
- clipPathData: ClipPathDatum[]
231
- }) {
232
- const clipPath = defsSelection
233
- .selectAll<SVGClipPathElement, Layout>('clipPath')
234
- .data(clipPathData)
235
- .join(
236
- enter => {
237
- return enter
238
- .append('clipPath')
239
- },
240
- update => update,
241
- exit => exit.remove()
242
- )
243
- .attr('id', d => d.id)
244
- .each((d, i, g) => {
245
- const rect = d3.select(g[i])
246
- .selectAll<SVGRectElement, typeof d>('rect')
247
- .data([d])
248
- .join(
249
- enter => {
250
- return enter
251
- .append('rect')
252
- },
253
- update => update,
254
- exit => exit.remove()
255
- )
256
- .attr('x', 0)
257
- .attr('y', 0)
258
- .attr('width', _d => _d.width)
259
- .attr('height', _d => _d.height)
260
- })
261
- }
262
-
263
- function highlight ({ selection, ids, fullChartParams }: {
264
- selection: d3.Selection<any, ComputedDatumGrid, any, any>
265
- ids: string[]
266
- fullChartParams: ChartParams
267
- }) {
268
- selection.interrupt('highlight')
269
-
270
- if (!ids.length) {
271
- // remove highlight
272
- selection
273
- .transition('highlight')
274
- .duration(200)
275
- .style('opacity', 1)
276
- return
277
- }
278
-
279
- selection
280
- .each((d, i, n) => {
281
- if (ids.includes(d.id)) {
282
- d3.select(n[i])
283
- .style('opacity', 1)
284
- } else {
285
- d3.select(n[i])
286
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
287
- }
288
- })
289
- }
290
-
291
-
292
- export const createBaseBarStack: BasePluginFn<BaseBarStackContext> = (pluginName: string, {
293
- selection,
294
- computedData$,
295
- computedLayoutData$,
296
- visibleComputedData$,
297
- visibleComputedLayoutData$,
298
- seriesLabels$,
299
- SeriesDataMap$,
300
- GroupDataMap$,
301
- fullParams$,
302
- fullDataFormatter$,
303
- fullChartParams$,
304
- gridAxesTransform$,
305
- gridGraphicTransform$,
306
- gridGraphicReverseScale$,
307
- gridAxesSize$,
308
- gridHighlight$,
309
- gridContainerPosition$,
310
- isSeriesSeprate$,
311
- event$
312
- }) => {
313
-
314
- const destroy$ = new Subject()
315
-
316
- const clipPathID = getUniID(pluginName, 'clipPath-box')
317
- const rectClassName = getClassName(pluginName, 'rect')
318
-
319
- const {
320
- seriesSelection$,
321
- axesSelection$,
322
- defsSelection$,
323
- graphicGSelection$
324
- } = gridSelectionsObservable({
325
- selection,
326
- pluginName,
327
- clipPathID,
328
- seriesLabels$,
329
- gridContainerPosition$,
330
- gridAxesTransform$,
331
- gridGraphicTransform$
332
- })
333
-
334
-
335
- const zeroY$ = visibleComputedLayoutData$.pipe(
336
- takeUntil(destroy$),
337
- map(d => d[0] && d[0][0]
338
- ? d[0][0].axisY - d[0][0].axisYFromZero
339
- : 0),
340
- distinctUntilChanged()
341
- )
342
-
343
- const barWidth$ = combineLatest({
344
- computedData: computedData$,
345
- // visibleComputedData: visibleComputedData$,
346
- params: fullParams$,
347
- axisSize: gridAxesSize$,
348
- isSeriesSeprate: isSeriesSeprate$
349
- }).pipe(
350
- takeUntil(destroy$),
351
- switchMap(async d => d),
352
- map(data => {
353
- const barWidth = data.params.barWidth
354
- ? data.params.barWidth
355
- : calcBarWidth({
356
- axisWidth: data.axisSize.width,
357
- groupAmount: data.computedData[0] ? data.computedData[0].length : 0,
358
- barGroupPadding: data.params.barGroupPadding
359
- })
360
- return barWidth
361
- }),
362
- distinctUntilChanged()
363
- )
364
-
365
- // 圓角的值 [rx, ry]
366
- const transformedBarRadius$: Observable<[number, number][]> = combineLatest({
367
- computedData: computedData$,
368
- // gridGraphicTransform: gridGraphicTransform$,
369
- barWidth: barWidth$,
370
- params: fullParams$,
371
- gridGraphicReverseScale: gridGraphicReverseScale$
372
- }).pipe(
373
- takeUntil(destroy$),
374
- switchMap(async data => data),
375
- map(data => {
376
- const barHalfWidth = data.barWidth! / 2
377
- const radius = data.params.barRadius === true ? barHalfWidth
378
- : data.params.barRadius === false ? 0
379
- : typeof data.params.barRadius == 'number' ? data.params.barRadius
380
- : 0
381
-
382
- return data.computedData.map((series, seriesIndex) => {
383
- const gridGraphicReverseScale = data.gridGraphicReverseScale[seriesIndex] ?? data.gridGraphicReverseScale[0]
384
-
385
- const transformedRx = radius * gridGraphicReverseScale[0]
386
- const transformedRy = radius * gridGraphicReverseScale[1]
387
-
388
- return [transformedRx, transformedRy]
389
- })
390
- })
391
- )
392
-
393
- // const seriesLabels$ = visibleComputedData$.pipe(
394
- // takeUntil(destroy$),
395
- // map(data => {
396
- // const SeriesLabelSet: Set<string> = new Set()
397
- // data.forEach(d => {
398
- // d.forEach(_d => {
399
- // SeriesLabelSet.add(_d.seriesLabel)
400
- // })
401
- // })
402
- // return Array.from(SeriesLabelSet)
403
- // })
404
- // )
405
-
406
- const groupLabels$ = visibleComputedData$.pipe(
407
- takeUntil(destroy$),
408
- map(data => {
409
- const GroupLabelSet: Set<string> = new Set()
410
- data.forEach(d => {
411
- d.forEach(_d => {
412
- GroupLabelSet.add(_d.groupLabel)
413
- })
414
- })
415
- return Array.from(GroupLabelSet)
416
- }),
417
- shareReplay(1)
418
- )
419
-
420
- const transitionDuration$ = fullChartParams$.pipe(
421
- takeUntil(destroy$),
422
- map(d => d.transitionDuration),
423
- distinctUntilChanged()
424
- )
425
-
426
- const delayGroup$ = new Observable<number>(subscriber => {
427
- combineLatest({
428
- groupLabels: groupLabels$,
429
- transitionDuration: transitionDuration$,
430
- }).pipe(
431
- switchMap(async d => d)
432
- ).subscribe(data => {
433
- const delay = calcDelayGroup(data.groupLabels.length, data.transitionDuration)
434
- subscriber.next(delay)
435
- })
436
- }).pipe(
437
- takeUntil(destroy$),
438
- distinctUntilChanged()
439
- )
440
-
441
- const transitionItem$ = new Observable<number>(subscriber => {
442
- combineLatest({
443
- groupLabels: groupLabels$,
444
- transitionDuration: transitionDuration$
445
- }).pipe(
446
- switchMap(async d => d)
447
- ).subscribe(data => {
448
- const transition = calctransitionItem(data.groupLabels.length, data.transitionDuration)
449
- subscriber.next(transition)
450
- })
451
- }).pipe(
452
- takeUntil(destroy$),
453
- distinctUntilChanged()
454
- )
455
-
456
- // const transposedVisibleData$: Observable<ComputedDataGrid> = visibleComputedData$.pipe(
457
- // takeUntil(destroy$),
458
- // map(data => {
459
- // console.log('visibleComputedData', data)
460
- // // 取得原始陣列的維度
461
- // const rows = data.length;
462
- // const cols = data.reduce((prev, current) => {
463
- // return Math.max(prev, current.length)
464
- // }, 0)
465
-
466
- // // 初始化轉換後的陣列
467
- // const transposedArray = new Array(cols).fill(null).map(() => new Array(rows).fill(null))
468
-
469
- // // 遍歷原始陣列,進行轉換
470
- // for (let i = 0; i < rows; i++) {
471
- // for (let j = 0; j < cols; j++) {
472
- // transposedArray[j][i] = data[i][j]
473
- // }
474
- // }
475
- // return transposedArray
476
- // })
477
- // )
478
-
479
- const groupScaleDomain$ = combineLatest({
480
- computedData: computedData$,
481
- dataFormatter: fullDataFormatter$,
482
- }).pipe(
483
- takeUntil(destroy$),
484
- switchMap(async d => d),
485
- map(data => {
486
- const groupMin = 0
487
- const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
488
- // const groupScaleDomainMin = data.dataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
489
- // ? groupMin // - data.dataFormatter.grid.groupAxis.scalePadding
490
- // : data.dataFormatter.grid.groupAxis.scaleDomain[0] as number // - data.dataFormatter.grid.groupAxis.scalePadding
491
- const groupScaleDomainMin = data.dataFormatter.grid.groupAxis.scaleDomain[0]
492
- const groupScaleDomainMax = data.dataFormatter.grid.groupAxis.scaleDomain[1] === 'max'
493
- ? groupMax // + data.dataFormatter.grid.groupAxis.scalePadding
494
- : data.dataFormatter.grid.groupAxis.scaleDomain[1] as number // + data.dataFormatter.grid.groupAxis.scalePadding
495
-
496
- return [groupScaleDomainMin, groupScaleDomainMax]
497
- })
498
- )
499
-
500
- // 堆疊後的高度對應圖軸的比例(最大值/最大值所在group的總和)
501
- const yRatio$ = combineLatest({
502
- visibleComputedLayoutData: visibleComputedLayoutData$,
503
- groupScaleDomain: groupScaleDomain$
504
- }).pipe(
505
- takeUntil(destroy$),
506
- switchMap(async d => d),
507
- map(data => {
508
- const groupScaleDomainMin = data.groupScaleDomain[0]
509
- const groupScaleDomainMax = data.groupScaleDomain[1]
510
- // 只選取group篩選範圍內的資料
511
- const filteredData = data.visibleComputedLayoutData
512
- .map(d => {
513
- return d.filter((_d, i) => {
514
- return _d.groupIndex >= groupScaleDomainMin && _d.groupIndex <= groupScaleDomainMax
515
- })
516
- })
517
-
518
- const filteredDataList = filteredData.flat()
519
- if (filteredDataList.length <= 1) {
520
- return 1
521
- }
522
-
523
- const maxValueDatum = filteredDataList.reduce((max, current) => {
524
- return current.value > max.value ? current : max;
525
- }, filteredDataList[0])
526
- const maxValueGroupIndex = maxValueDatum.groupIndex
527
- const maxValueGroupSum = filteredDataList
528
- .filter(d => d.groupIndex === maxValueGroupIndex)
529
- .reduce((sum, current) => {
530
- return sum + current.value
531
- }, 0)
532
- return maxValueDatum.value / maxValueGroupSum
533
- })
534
- )
535
-
536
- const stackedData$ = combineLatest({
537
- computedLayoutData: computedLayoutData$,
538
- yRatio: yRatio$,
539
- zeroY: zeroY$
540
- }).pipe(
541
- takeUntil(destroy$),
542
- map(data => {
543
- let accYArr: number[] = data.computedLayoutData[0]
544
- ? data.computedLayoutData[0].map(() => data.zeroY)
545
- : []
546
- return data.computedLayoutData.map((series, seriesIndex) => {
547
- return series.map((datum, groupIndex) => {
548
- const _barStartY = accYArr[groupIndex] // 前一次的累加高度
549
- let _barHeight = 0
550
- if (datum.visible) {
551
- _barHeight = datum.axisYFromZero * data.yRatio
552
- accYArr[groupIndex] = accYArr[groupIndex] + _barHeight // 累加高度
553
- }
554
- return <GraphicDatum>{
555
- ...datum,
556
- _barStartY,
557
- _barHeight
558
- }
559
- })
560
- })
561
- // return data.computedData.map(d => {
562
- // let accY = data.zeroY
563
- // return d.map(_d => {
564
- // const _barStartY = accY
565
- // const _barHeight = _d.axisYFromZero * data.yRatio
566
- // accY = accY + _barHeight
567
- // return <GraphicDatum>{
568
- // ..._d,
569
- // _barStartY,
570
- // _barHeight
571
- // }
572
- // })
573
- // })
574
- })
575
- )
576
-
577
- const noneStackedData$ = combineLatest({
578
- computedLayoutData: computedLayoutData$,
579
- // yRatio: yRatio$,
580
- zeroY: zeroY$
581
- }).pipe(
582
- takeUntil(destroy$),
583
- map(data => {
584
- return data.computedLayoutData.map((series, seriesIndex) => {
585
- return series.map((datum, groupIndex) => {
586
- return <GraphicDatum>{
587
- ...datum,
588
- _barStartY: data.zeroY,
589
- _barHeight: datum.axisYFromZero
590
- }
591
- })
592
- })
593
- })
594
- )
595
-
596
- const graphicData$ = isSeriesSeprate$.pipe(
597
- switchMap(isSeriesSeprate => {
598
- return iif(() => isSeriesSeprate, noneStackedData$, stackedData$)
599
- })
600
- )
601
-
602
- combineLatest({
603
- defsSelection: defsSelection$,
604
- gridAxesSize: gridAxesSize$,
605
- }).pipe(
606
- takeUntil(destroy$),
607
- switchMap(async d => d)
608
- ).subscribe(data => {
609
- const clipPathData = [{
610
- id: clipPathID,
611
- width: data.gridAxesSize.width,
612
- height: data.gridAxesSize.height
613
- }]
614
- renderClipPath({
615
- defsSelection: data.defsSelection,
616
- clipPathData
617
- })
618
- })
619
-
620
- const highlightTarget$ = fullChartParams$.pipe(
621
- takeUntil(destroy$),
622
- map(d => d.highlightTarget),
623
- distinctUntilChanged()
624
- )
625
-
626
- const barSelection$ = combineLatest({
627
- graphicGSelection: graphicGSelection$,
628
- graphicData: graphicData$,
629
- zeroY: zeroY$,
630
- groupLabels: groupLabels$,
631
- // barScale: barScale$,
632
- params: fullParams$,
633
- chartParams: fullChartParams$,
634
- highlightTarget: highlightTarget$,
635
- barWidth: barWidth$,
636
- transformedBarRadius: transformedBarRadius$,
637
- delayGroup: delayGroup$,
638
- transitionItem: transitionItem$,
639
- isSeriesSeprate: isSeriesSeprate$
640
- }).pipe(
641
- takeUntil(destroy$),
642
- switchMap(async (d) => d),
643
- map(data => {
644
- return renderRectBars({
645
- graphicGSelection: data.graphicGSelection,
646
- rectClassName,
647
- barData: data.graphicData,
648
- zeroY: data.zeroY,
649
- groupLabels: data.groupLabels,
650
- // barScale: data.barScale,
651
- params: data.params,
652
- chartParams: data.chartParams,
653
- barWidth: data.barWidth,
654
- transformedBarRadius: data.transformedBarRadius,
655
- delayGroup: data.delayGroup,
656
- transitionItem: data.transitionItem,
657
- isSeriesSeprate: data.isSeriesSeprate
658
- })
659
- })
660
- )
661
-
662
- combineLatest({
663
- barSelection: barSelection$,
664
- computedData: computedData$,
665
- highlightTarget: highlightTarget$,
666
- SeriesDataMap: SeriesDataMap$,
667
- GroupDataMap: GroupDataMap$,
668
- }).subscribe(data => {
669
-
670
- data.barSelection!
671
- .on('mouseover', (event, datum) => {
672
- event.stopPropagation()
673
-
674
- event$.next({
675
- type: 'grid',
676
- eventName: 'mouseover',
677
- pluginName,
678
- highlightTarget: data.highlightTarget,
679
- datum,
680
- gridIndex: datum.gridIndex,
681
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
682
- seriesIndex: datum.seriesIndex,
683
- seriesLabel: datum.seriesLabel,
684
- groups: data.GroupDataMap.get(datum.groupLabel)!,
685
- groupIndex: datum.groupIndex,
686
- groupLabel: datum.groupLabel,
687
- event,
688
- data: data.computedData
689
- })
690
- })
691
- .on('mousemove', (event, datum) => {
692
- event.stopPropagation()
693
-
694
- event$.next({
695
- type: 'grid',
696
- eventName: 'mousemove',
697
- pluginName,
698
- highlightTarget: data.highlightTarget,
699
- datum,
700
- gridIndex: datum.gridIndex,
701
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
702
- seriesIndex: datum.seriesIndex,
703
- seriesLabel: datum.seriesLabel,
704
- groups: data.GroupDataMap.get(datum.groupLabel)!,
705
- groupIndex: datum.groupIndex,
706
- groupLabel: datum.groupLabel,
707
- event,
708
- data: data.computedData
709
- })
710
- })
711
- .on('mouseout', (event, datum) => {
712
- event.stopPropagation()
713
-
714
- event$.next({
715
- type: 'grid',
716
- eventName: 'mouseout',
717
- pluginName,
718
- highlightTarget: data.highlightTarget,
719
- datum,
720
- gridIndex: datum.gridIndex,
721
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
722
- seriesIndex: datum.seriesIndex,
723
- seriesLabel: datum.seriesLabel,
724
- groups: data.GroupDataMap.get(datum.groupLabel)!,
725
- groupIndex: datum.groupIndex,
726
- groupLabel: datum.groupLabel,
727
- event,
728
- data: data.computedData
729
- })
730
- })
731
- .on('click', (event, datum) => {
732
- event.stopPropagation()
733
-
734
- event$.next({
735
- type: 'grid',
736
- eventName: 'click',
737
- pluginName,
738
- highlightTarget: data.highlightTarget,
739
- datum,
740
- gridIndex: datum.gridIndex,
741
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
742
- seriesIndex: datum.seriesIndex,
743
- seriesLabel: datum.seriesLabel,
744
- groups: data.GroupDataMap.get(datum.groupLabel)!,
745
- groupIndex: datum.groupIndex,
746
- groupLabel: datum.groupLabel,
747
- event,
748
- data: data.computedData
749
- })
750
- })
751
-
752
- })
753
-
754
- // const datumList$ = computedData$.pipe(
755
- // takeUntil(destroy$),
756
- // map(d => d.flat())
757
- // )
758
- // const highlight$ = highlightObservable({ datumList$, chartParams$, event$: store.event$ })
759
-
760
- combineLatest({
761
- barSelection: barSelection$,
762
- highlight: gridHighlight$.pipe(
763
- map(data => data.map(d => d.id))
764
- ),
765
- fullChartParams: fullChartParams$
766
- }).pipe(
767
- takeUntil(destroy$),
768
- switchMap(async d => d)
769
- ).subscribe(data => {
770
- highlight({
771
- selection: data.barSelection,
772
- ids: data.highlight,
773
- fullChartParams: data.fullChartParams
774
- })
775
- })
776
-
777
- return () => {
778
- destroy$.next(undefined)
779
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ iif,
4
+ combineLatest,
5
+ map,
6
+ switchMap,
7
+ takeUntil,
8
+ distinctUntilChanged,
9
+ Observable,
10
+ Subject } from 'rxjs'
11
+ import type { BasePluginFn } from './types'
12
+ import type {
13
+ ComputedDatumGrid,
14
+ ComputedDataGrid,
15
+ ComputedLayoutDatumGrid,
16
+ ComputedLayoutDataGrid,
17
+ DataFormatterGrid,
18
+ EventGrid,
19
+ ChartParams,
20
+ GridContainerPosition,
21
+ Layout,
22
+ TransformData } from '@orbcharts/core'
23
+ import { getD3TransitionEase } from '../utils/d3Utils'
24
+ import { getClassName, getUniID } from '../utils/orbchartsUtils'
25
+ import { gridSelectionsObservable } from '../grid/gridObservables'
26
+ import { shareReplay } from 'rxjs/operators'
27
+
28
+ export interface BaseBarStackParams {
29
+ barWidth: number
30
+ barGroupPadding: number
31
+ barRadius: number | boolean
32
+ }
33
+
34
+ interface BaseBarStackContext {
35
+ selection: d3.Selection<any, unknown, any, unknown>
36
+ computedData$: Observable<ComputedDataGrid>
37
+ computedLayoutData$: Observable<ComputedLayoutDataGrid>
38
+ visibleComputedData$: Observable<ComputedDatumGrid[][]>
39
+ visibleComputedLayoutData$: Observable<ComputedLayoutDataGrid>
40
+ seriesLabels$: Observable<string[]>
41
+ SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
42
+ GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
43
+ fullParams$: Observable<BaseBarStackParams>
44
+ fullDataFormatter$: Observable<DataFormatterGrid>
45
+ fullChartParams$: Observable<ChartParams>
46
+ gridAxesTransform$: Observable<TransformData>
47
+ gridGraphicTransform$: Observable<TransformData>
48
+ gridGraphicReverseScale$: Observable<[number, number][]>
49
+ gridAxesSize$: Observable<{
50
+ width: number;
51
+ height: number;
52
+ }>
53
+ gridHighlight$: Observable<ComputedDatumGrid[]>
54
+ gridContainerPosition$: Observable<GridContainerPosition[]>
55
+ isSeriesSeprate$: Observable<boolean>
56
+ event$: Subject<EventGrid>
57
+ }
58
+
59
+
60
+ interface GraphicDatum extends ComputedLayoutDatumGrid {
61
+ _barStartY: number // bar的起點y座標
62
+ _barHeight: number // bar的高度
63
+ }
64
+
65
+ interface RenderBarParams {
66
+ graphicGSelection: d3.Selection<SVGGElement, unknown, any, any>
67
+ rectClassName: string
68
+ barData: GraphicDatum[][]
69
+ zeroY: number
70
+ groupLabels: string[]
71
+ // barScale: d3.ScalePoint<string>
72
+ params: BaseBarStackParams
73
+ chartParams: ChartParams
74
+ barWidth: number
75
+ transformedBarRadius: [number, number][]
76
+ delayGroup: number
77
+ transitionItem: number
78
+ isSeriesSeprate: boolean
79
+ }
80
+
81
+ type ClipPathDatum = {
82
+ id: string;
83
+ // x: number;
84
+ // y: number;
85
+ width: number;
86
+ height: number;
87
+ }
88
+
89
+ // const pluginName = 'BarStack'
90
+ // const rectClassName = getClassName(pluginName, 'rect')
91
+ // group的delay在動畫中的佔比(剩餘部份的時間為圖形本身的動畫時間,因為delay時間和最後一個group的動畫時間加總為1)
92
+ const groupDelayProportionOfDuration = 0.3
93
+
94
+ function calcBarWidth ({ axisWidth, groupAmount, barGroupPadding = 0 }: {
95
+ axisWidth: number
96
+ groupAmount: number
97
+ barGroupPadding: number
98
+ }) {
99
+ const eachGroupWidth = axisWidth / (groupAmount - 1)
100
+ const width = eachGroupWidth - barGroupPadding
101
+ return width > 1 ? width : 1
102
+
103
+ }
104
+
105
+ // function makeBarScale (barWidth: number, seriesLabels: string[], params: BarStackParams) {
106
+ // const barHalfWidth = barWidth! / 2
107
+ // const barGroupWidth = barWidth * seriesLabels.length + params.barPadding! * seriesLabels.length
108
+ // return d3.scalePoint()
109
+ // .domain(seriesLabels)
110
+ // .range([-barGroupWidth / 2 + barHalfWidth, barGroupWidth / 2 - barHalfWidth])
111
+ // }
112
+
113
+ function calcDelayGroup (barGroupAmount: number, totalDuration: number) {
114
+ if (barGroupAmount <= 1) {
115
+ // 一筆內計算會出錯所以不算
116
+ return 0
117
+ }
118
+ return totalDuration / (barGroupAmount - 1) * groupDelayProportionOfDuration // 依group數量計算
119
+ }
120
+
121
+ function calctransitionItem (barGroupAmount: number, totalDuration: number) {
122
+ if (barGroupAmount <= 1) {
123
+ // 一筆內不會有delay
124
+ return totalDuration
125
+ }
126
+ return totalDuration * (1 - groupDelayProportionOfDuration) // delay後剩餘的時間
127
+ }
128
+
129
+ function renderRectBars ({ graphicGSelection, rectClassName, barData, zeroY, groupLabels, params, chartParams, barWidth, transformedBarRadius, delayGroup, transitionItem, isSeriesSeprate }: RenderBarParams) {
130
+
131
+ const barHalfWidth = barWidth! / 2
132
+
133
+ graphicGSelection
134
+ .each((seriesData, seriesIndex, g) => {
135
+ d3.select(g[seriesIndex])
136
+ .selectAll<SVGGElement, ComputedDatumGrid>(`rect.${rectClassName}`)
137
+ .data(barData[seriesIndex] ?? [], d => d.id)
138
+ .join(
139
+ enter => {
140
+ // console.log('enter')
141
+ return enter
142
+ .append('rect')
143
+ .classed(rectClassName, true)
144
+ .attr('cursor', 'pointer')
145
+ .attr('height', d => 0)
146
+ },
147
+ update => update,
148
+ exit => exit.remove()
149
+ )
150
+ .attr('transform', (d, i) => `translate(${(d ? d.axisX : 0) - barHalfWidth}, ${0})`)
151
+ .attr('fill', d => d.color)
152
+ .attr('y', d => zeroY)
153
+ .attr('x', d =>0)
154
+ .attr('width', barWidth!)
155
+ .attr('rx', transformedBarRadius[seriesIndex][0] ?? 1)
156
+ .attr('ry', transformedBarRadius[seriesIndex][1] ?? 1)
157
+ .transition()
158
+ .duration(transitionItem)
159
+ .ease(getD3TransitionEase(chartParams.transitionEase))
160
+ .delay((d, i) => d.groupIndex * delayGroup)
161
+ .attr('y', d => d._barStartY)
162
+ .attr('height', d => Math.abs(d._barHeight))
163
+ })
164
+
165
+ // const barGroup = graphicGSelection
166
+ // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${gClassName}`)
167
+ // .data(data, (d, i) => groupLabels[i])
168
+ // .join(
169
+ // enter => {
170
+ // return enter
171
+ // .append('g')
172
+ // .classed(gClassName, true)
173
+ // .attr('cursor', 'pointer')
174
+ // },
175
+ // update => update,
176
+ // exit => exit.remove()
177
+ // )
178
+ // .attr('transform', (d, i) => `translate(${d[0] ? d[0].axisX : 0}, ${0})`)
179
+ // .each((d, i, g) => {
180
+ // const bars = d3.select(g[i])
181
+ // .selectAll<SVGGElement, ComputedDatumGrid>('g')
182
+ // .data(d, _d => _d.id)
183
+ // .join(
184
+ // enter => {
185
+ // return enter
186
+ // .append('g')
187
+ // .classed(gContentClassName, true)
188
+ // },
189
+ // update => update,
190
+ // exit => exit.remove()
191
+ // )
192
+ // .each((_d, _i, _g) => {
193
+ // const rect = d3.select(_g[_i])
194
+ // .selectAll<SVGRectElement, ComputedDatumGrid>('rect')
195
+ // .data([_d], _d => _d.id)
196
+ // .join(
197
+ // enter => {
198
+ // return enter
199
+ // .append('rect')
200
+ // .attr('y', d => zeroY)
201
+ // .attr('height', d => 0)
202
+ // },
203
+ // update => update,
204
+ // exit => exit.remove()
205
+ // )
206
+ // .attr('rx', transformedBarRadius[0])
207
+ // .attr('ry', transformedBarRadius[1])
208
+ // .attr('fill', d => d.color)
209
+ // .attr('transform', `translate(${-barHalfWidth}, 0)`)
210
+ // .attr('x', d => 0)
211
+ // .attr('width', barWidth!)
212
+ // .transition()
213
+ // .duration(transitionItem)
214
+ // .ease(getD3TransitionEase(chartParams.transitionEase))
215
+ // .delay((d, i) => d.groupIndex * delayGroup)
216
+ // .attr('y', d => d._barStartY)
217
+ // .attr('height', d => Math.abs(d._barHeight))
218
+ // })
219
+
220
+ // })
221
+
222
+ const graphicBarSelection: d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown> = graphicGSelection.selectAll(`rect.${rectClassName}`)
223
+
224
+
225
+ return graphicBarSelection
226
+ }
227
+
228
+ function renderClipPath ({ defsSelection, clipPathData }: {
229
+ defsSelection: d3.Selection<SVGDefsElement, any, any, any>
230
+ clipPathData: ClipPathDatum[]
231
+ }) {
232
+ const clipPath = defsSelection
233
+ .selectAll<SVGClipPathElement, Layout>('clipPath')
234
+ .data(clipPathData)
235
+ .join(
236
+ enter => {
237
+ return enter
238
+ .append('clipPath')
239
+ },
240
+ update => update,
241
+ exit => exit.remove()
242
+ )
243
+ .attr('id', d => d.id)
244
+ .each((d, i, g) => {
245
+ const rect = d3.select(g[i])
246
+ .selectAll<SVGRectElement, typeof d>('rect')
247
+ .data([d])
248
+ .join(
249
+ enter => {
250
+ return enter
251
+ .append('rect')
252
+ },
253
+ update => update,
254
+ exit => exit.remove()
255
+ )
256
+ .attr('x', 0)
257
+ .attr('y', 0)
258
+ .attr('width', _d => _d.width)
259
+ .attr('height', _d => _d.height)
260
+ })
261
+ }
262
+
263
+ function highlight ({ selection, ids, fullChartParams }: {
264
+ selection: d3.Selection<any, ComputedDatumGrid, any, any>
265
+ ids: string[]
266
+ fullChartParams: ChartParams
267
+ }) {
268
+ selection.interrupt('highlight')
269
+
270
+ if (!ids.length) {
271
+ // remove highlight
272
+ selection
273
+ .transition('highlight')
274
+ .duration(200)
275
+ .style('opacity', 1)
276
+ return
277
+ }
278
+
279
+ selection
280
+ .each((d, i, n) => {
281
+ if (ids.includes(d.id)) {
282
+ d3.select(n[i])
283
+ .style('opacity', 1)
284
+ } else {
285
+ d3.select(n[i])
286
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
287
+ }
288
+ })
289
+ }
290
+
291
+
292
+ export const createBaseBarStack: BasePluginFn<BaseBarStackContext> = (pluginName: string, {
293
+ selection,
294
+ computedData$,
295
+ computedLayoutData$,
296
+ visibleComputedData$,
297
+ visibleComputedLayoutData$,
298
+ seriesLabels$,
299
+ SeriesDataMap$,
300
+ GroupDataMap$,
301
+ fullParams$,
302
+ fullDataFormatter$,
303
+ fullChartParams$,
304
+ gridAxesTransform$,
305
+ gridGraphicTransform$,
306
+ gridGraphicReverseScale$,
307
+ gridAxesSize$,
308
+ gridHighlight$,
309
+ gridContainerPosition$,
310
+ isSeriesSeprate$,
311
+ event$
312
+ }) => {
313
+
314
+ const destroy$ = new Subject()
315
+
316
+ const clipPathID = getUniID(pluginName, 'clipPath-box')
317
+ const rectClassName = getClassName(pluginName, 'rect')
318
+
319
+ const {
320
+ seriesSelection$,
321
+ axesSelection$,
322
+ defsSelection$,
323
+ graphicGSelection$
324
+ } = gridSelectionsObservable({
325
+ selection,
326
+ pluginName,
327
+ clipPathID,
328
+ seriesLabels$,
329
+ gridContainerPosition$,
330
+ gridAxesTransform$,
331
+ gridGraphicTransform$
332
+ })
333
+
334
+
335
+ const zeroY$ = visibleComputedLayoutData$.pipe(
336
+ takeUntil(destroy$),
337
+ map(d => d[0] && d[0][0]
338
+ ? d[0][0].axisY - d[0][0].axisYFromZero
339
+ : 0),
340
+ distinctUntilChanged()
341
+ )
342
+
343
+ const barWidth$ = combineLatest({
344
+ computedData: computedData$,
345
+ // visibleComputedData: visibleComputedData$,
346
+ params: fullParams$,
347
+ axisSize: gridAxesSize$,
348
+ isSeriesSeprate: isSeriesSeprate$
349
+ }).pipe(
350
+ takeUntil(destroy$),
351
+ switchMap(async d => d),
352
+ map(data => {
353
+ const barWidth = data.params.barWidth
354
+ ? data.params.barWidth
355
+ : calcBarWidth({
356
+ axisWidth: data.axisSize.width,
357
+ groupAmount: data.computedData[0] ? data.computedData[0].length : 0,
358
+ barGroupPadding: data.params.barGroupPadding
359
+ })
360
+ return barWidth
361
+ }),
362
+ distinctUntilChanged()
363
+ )
364
+
365
+ // 圓角的值 [rx, ry]
366
+ const transformedBarRadius$: Observable<[number, number][]> = combineLatest({
367
+ computedData: computedData$,
368
+ // gridGraphicTransform: gridGraphicTransform$,
369
+ barWidth: barWidth$,
370
+ params: fullParams$,
371
+ gridGraphicReverseScale: gridGraphicReverseScale$
372
+ }).pipe(
373
+ takeUntil(destroy$),
374
+ switchMap(async data => data),
375
+ map(data => {
376
+ const barHalfWidth = data.barWidth! / 2
377
+ const radius = data.params.barRadius === true ? barHalfWidth
378
+ : data.params.barRadius === false ? 0
379
+ : typeof data.params.barRadius == 'number' ? data.params.barRadius
380
+ : 0
381
+
382
+ return data.computedData.map((series, seriesIndex) => {
383
+ const gridGraphicReverseScale = data.gridGraphicReverseScale[seriesIndex] ?? data.gridGraphicReverseScale[0]
384
+
385
+ const transformedRx = radius * gridGraphicReverseScale[0]
386
+ const transformedRy = radius * gridGraphicReverseScale[1]
387
+
388
+ return [transformedRx, transformedRy]
389
+ })
390
+ })
391
+ )
392
+
393
+ // const seriesLabels$ = visibleComputedData$.pipe(
394
+ // takeUntil(destroy$),
395
+ // map(data => {
396
+ // const SeriesLabelSet: Set<string> = new Set()
397
+ // data.forEach(d => {
398
+ // d.forEach(_d => {
399
+ // SeriesLabelSet.add(_d.seriesLabel)
400
+ // })
401
+ // })
402
+ // return Array.from(SeriesLabelSet)
403
+ // })
404
+ // )
405
+
406
+ const groupLabels$ = visibleComputedData$.pipe(
407
+ takeUntil(destroy$),
408
+ map(data => {
409
+ const GroupLabelSet: Set<string> = new Set()
410
+ data.forEach(d => {
411
+ d.forEach(_d => {
412
+ GroupLabelSet.add(_d.groupLabel)
413
+ })
414
+ })
415
+ return Array.from(GroupLabelSet)
416
+ }),
417
+ shareReplay(1)
418
+ )
419
+
420
+ const transitionDuration$ = fullChartParams$.pipe(
421
+ takeUntil(destroy$),
422
+ map(d => d.transitionDuration),
423
+ distinctUntilChanged()
424
+ )
425
+
426
+ const delayGroup$ = new Observable<number>(subscriber => {
427
+ combineLatest({
428
+ groupLabels: groupLabels$,
429
+ transitionDuration: transitionDuration$,
430
+ }).pipe(
431
+ switchMap(async d => d)
432
+ ).subscribe(data => {
433
+ const delay = calcDelayGroup(data.groupLabels.length, data.transitionDuration)
434
+ subscriber.next(delay)
435
+ })
436
+ }).pipe(
437
+ takeUntil(destroy$),
438
+ distinctUntilChanged()
439
+ )
440
+
441
+ const transitionItem$ = new Observable<number>(subscriber => {
442
+ combineLatest({
443
+ groupLabels: groupLabels$,
444
+ transitionDuration: transitionDuration$
445
+ }).pipe(
446
+ switchMap(async d => d)
447
+ ).subscribe(data => {
448
+ const transition = calctransitionItem(data.groupLabels.length, data.transitionDuration)
449
+ subscriber.next(transition)
450
+ })
451
+ }).pipe(
452
+ takeUntil(destroy$),
453
+ distinctUntilChanged()
454
+ )
455
+
456
+ // const transposedVisibleData$: Observable<ComputedDataGrid> = visibleComputedData$.pipe(
457
+ // takeUntil(destroy$),
458
+ // map(data => {
459
+ // console.log('visibleComputedData', data)
460
+ // // 取得原始陣列的維度
461
+ // const rows = data.length;
462
+ // const cols = data.reduce((prev, current) => {
463
+ // return Math.max(prev, current.length)
464
+ // }, 0)
465
+
466
+ // // 初始化轉換後的陣列
467
+ // const transposedArray = new Array(cols).fill(null).map(() => new Array(rows).fill(null))
468
+
469
+ // // 遍歷原始陣列,進行轉換
470
+ // for (let i = 0; i < rows; i++) {
471
+ // for (let j = 0; j < cols; j++) {
472
+ // transposedArray[j][i] = data[i][j]
473
+ // }
474
+ // }
475
+ // return transposedArray
476
+ // })
477
+ // )
478
+
479
+ const groupScaleDomain$ = combineLatest({
480
+ computedData: computedData$,
481
+ dataFormatter: fullDataFormatter$,
482
+ }).pipe(
483
+ takeUntil(destroy$),
484
+ switchMap(async d => d),
485
+ map(data => {
486
+ const groupMin = 0
487
+ const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
488
+ // const groupScaleDomainMin = data.dataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
489
+ // ? groupMin // - data.dataFormatter.grid.groupAxis.scalePadding
490
+ // : data.dataFormatter.grid.groupAxis.scaleDomain[0] as number // - data.dataFormatter.grid.groupAxis.scalePadding
491
+ const groupScaleDomainMin = data.dataFormatter.grid.groupAxis.scaleDomain[0]
492
+ const groupScaleDomainMax = data.dataFormatter.grid.groupAxis.scaleDomain[1] === 'max'
493
+ ? groupMax // + data.dataFormatter.grid.groupAxis.scalePadding
494
+ : data.dataFormatter.grid.groupAxis.scaleDomain[1] as number // + data.dataFormatter.grid.groupAxis.scalePadding
495
+
496
+ return [groupScaleDomainMin, groupScaleDomainMax]
497
+ })
498
+ )
499
+
500
+ // 堆疊後的高度對應圖軸的比例(最大值/最大值所在group的總和)
501
+ const yRatio$ = combineLatest({
502
+ visibleComputedLayoutData: visibleComputedLayoutData$,
503
+ groupScaleDomain: groupScaleDomain$
504
+ }).pipe(
505
+ takeUntil(destroy$),
506
+ switchMap(async d => d),
507
+ map(data => {
508
+ const groupScaleDomainMin = data.groupScaleDomain[0]
509
+ const groupScaleDomainMax = data.groupScaleDomain[1]
510
+ // 只選取group篩選範圍內的資料
511
+ const filteredData = data.visibleComputedLayoutData
512
+ .map(d => {
513
+ return d.filter((_d, i) => {
514
+ return _d.groupIndex >= groupScaleDomainMin && _d.groupIndex <= groupScaleDomainMax
515
+ })
516
+ })
517
+
518
+ const filteredDataList = filteredData.flat()
519
+ if (filteredDataList.length <= 1) {
520
+ return 1
521
+ }
522
+
523
+ const maxValueDatum = filteredDataList.reduce((max, current) => {
524
+ return current.value > max.value ? current : max;
525
+ }, filteredDataList[0])
526
+ const maxValueGroupIndex = maxValueDatum.groupIndex
527
+ const maxValueGroupSum = filteredDataList
528
+ .filter(d => d.groupIndex === maxValueGroupIndex)
529
+ .reduce((sum, current) => {
530
+ return sum + current.value
531
+ }, 0)
532
+ return maxValueDatum.value / maxValueGroupSum
533
+ })
534
+ )
535
+
536
+ const stackedData$ = combineLatest({
537
+ computedLayoutData: computedLayoutData$,
538
+ yRatio: yRatio$,
539
+ zeroY: zeroY$
540
+ }).pipe(
541
+ takeUntil(destroy$),
542
+ map(data => {
543
+ let accYArr: number[] = data.computedLayoutData[0]
544
+ ? data.computedLayoutData[0].map(() => data.zeroY)
545
+ : []
546
+ return data.computedLayoutData.map((series, seriesIndex) => {
547
+ return series.map((datum, groupIndex) => {
548
+ const _barStartY = accYArr[groupIndex] // 前一次的累加高度
549
+ let _barHeight = 0
550
+ if (datum.visible) {
551
+ _barHeight = datum.axisYFromZero * data.yRatio
552
+ accYArr[groupIndex] = accYArr[groupIndex] + _barHeight // 累加高度
553
+ }
554
+ return <GraphicDatum>{
555
+ ...datum,
556
+ _barStartY,
557
+ _barHeight
558
+ }
559
+ })
560
+ })
561
+ // return data.computedData.map(d => {
562
+ // let accY = data.zeroY
563
+ // return d.map(_d => {
564
+ // const _barStartY = accY
565
+ // const _barHeight = _d.axisYFromZero * data.yRatio
566
+ // accY = accY + _barHeight
567
+ // return <GraphicDatum>{
568
+ // ..._d,
569
+ // _barStartY,
570
+ // _barHeight
571
+ // }
572
+ // })
573
+ // })
574
+ })
575
+ )
576
+
577
+ const noneStackedData$ = combineLatest({
578
+ computedLayoutData: computedLayoutData$,
579
+ // yRatio: yRatio$,
580
+ zeroY: zeroY$
581
+ }).pipe(
582
+ takeUntil(destroy$),
583
+ map(data => {
584
+ return data.computedLayoutData.map((series, seriesIndex) => {
585
+ return series.map((datum, groupIndex) => {
586
+ return <GraphicDatum>{
587
+ ...datum,
588
+ _barStartY: data.zeroY,
589
+ _barHeight: datum.axisYFromZero
590
+ }
591
+ })
592
+ })
593
+ })
594
+ )
595
+
596
+ const graphicData$ = isSeriesSeprate$.pipe(
597
+ switchMap(isSeriesSeprate => {
598
+ return iif(() => isSeriesSeprate, noneStackedData$, stackedData$)
599
+ })
600
+ )
601
+
602
+ combineLatest({
603
+ defsSelection: defsSelection$,
604
+ gridAxesSize: gridAxesSize$,
605
+ }).pipe(
606
+ takeUntil(destroy$),
607
+ switchMap(async d => d)
608
+ ).subscribe(data => {
609
+ const clipPathData = [{
610
+ id: clipPathID,
611
+ width: data.gridAxesSize.width,
612
+ height: data.gridAxesSize.height
613
+ }]
614
+ renderClipPath({
615
+ defsSelection: data.defsSelection,
616
+ clipPathData
617
+ })
618
+ })
619
+
620
+ const highlightTarget$ = fullChartParams$.pipe(
621
+ takeUntil(destroy$),
622
+ map(d => d.highlightTarget),
623
+ distinctUntilChanged()
624
+ )
625
+
626
+ const barSelection$ = combineLatest({
627
+ graphicGSelection: graphicGSelection$,
628
+ graphicData: graphicData$,
629
+ zeroY: zeroY$,
630
+ groupLabels: groupLabels$,
631
+ // barScale: barScale$,
632
+ params: fullParams$,
633
+ chartParams: fullChartParams$,
634
+ highlightTarget: highlightTarget$,
635
+ barWidth: barWidth$,
636
+ transformedBarRadius: transformedBarRadius$,
637
+ delayGroup: delayGroup$,
638
+ transitionItem: transitionItem$,
639
+ isSeriesSeprate: isSeriesSeprate$
640
+ }).pipe(
641
+ takeUntil(destroy$),
642
+ switchMap(async (d) => d),
643
+ map(data => {
644
+ return renderRectBars({
645
+ graphicGSelection: data.graphicGSelection,
646
+ rectClassName,
647
+ barData: data.graphicData,
648
+ zeroY: data.zeroY,
649
+ groupLabels: data.groupLabels,
650
+ // barScale: data.barScale,
651
+ params: data.params,
652
+ chartParams: data.chartParams,
653
+ barWidth: data.barWidth,
654
+ transformedBarRadius: data.transformedBarRadius,
655
+ delayGroup: data.delayGroup,
656
+ transitionItem: data.transitionItem,
657
+ isSeriesSeprate: data.isSeriesSeprate
658
+ })
659
+ })
660
+ )
661
+
662
+ combineLatest({
663
+ barSelection: barSelection$,
664
+ computedData: computedData$,
665
+ highlightTarget: highlightTarget$,
666
+ SeriesDataMap: SeriesDataMap$,
667
+ GroupDataMap: GroupDataMap$,
668
+ }).subscribe(data => {
669
+
670
+ data.barSelection!
671
+ .on('mouseover', (event, datum) => {
672
+ event.stopPropagation()
673
+
674
+ event$.next({
675
+ type: 'grid',
676
+ eventName: 'mouseover',
677
+ pluginName,
678
+ highlightTarget: data.highlightTarget,
679
+ datum,
680
+ gridIndex: datum.gridIndex,
681
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
682
+ seriesIndex: datum.seriesIndex,
683
+ seriesLabel: datum.seriesLabel,
684
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
685
+ groupIndex: datum.groupIndex,
686
+ groupLabel: datum.groupLabel,
687
+ event,
688
+ data: data.computedData
689
+ })
690
+ })
691
+ .on('mousemove', (event, datum) => {
692
+ event.stopPropagation()
693
+
694
+ event$.next({
695
+ type: 'grid',
696
+ eventName: 'mousemove',
697
+ pluginName,
698
+ highlightTarget: data.highlightTarget,
699
+ datum,
700
+ gridIndex: datum.gridIndex,
701
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
702
+ seriesIndex: datum.seriesIndex,
703
+ seriesLabel: datum.seriesLabel,
704
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
705
+ groupIndex: datum.groupIndex,
706
+ groupLabel: datum.groupLabel,
707
+ event,
708
+ data: data.computedData
709
+ })
710
+ })
711
+ .on('mouseout', (event, datum) => {
712
+ event.stopPropagation()
713
+
714
+ event$.next({
715
+ type: 'grid',
716
+ eventName: 'mouseout',
717
+ pluginName,
718
+ highlightTarget: data.highlightTarget,
719
+ datum,
720
+ gridIndex: datum.gridIndex,
721
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
722
+ seriesIndex: datum.seriesIndex,
723
+ seriesLabel: datum.seriesLabel,
724
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
725
+ groupIndex: datum.groupIndex,
726
+ groupLabel: datum.groupLabel,
727
+ event,
728
+ data: data.computedData
729
+ })
730
+ })
731
+ .on('click', (event, datum) => {
732
+ event.stopPropagation()
733
+
734
+ event$.next({
735
+ type: 'grid',
736
+ eventName: 'click',
737
+ pluginName,
738
+ highlightTarget: data.highlightTarget,
739
+ datum,
740
+ gridIndex: datum.gridIndex,
741
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
742
+ seriesIndex: datum.seriesIndex,
743
+ seriesLabel: datum.seriesLabel,
744
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
745
+ groupIndex: datum.groupIndex,
746
+ groupLabel: datum.groupLabel,
747
+ event,
748
+ data: data.computedData
749
+ })
750
+ })
751
+
752
+ })
753
+
754
+ // const datumList$ = computedData$.pipe(
755
+ // takeUntil(destroy$),
756
+ // map(d => d.flat())
757
+ // )
758
+ // const highlight$ = highlightObservable({ datumList$, chartParams$, event$: store.event$ })
759
+
760
+ combineLatest({
761
+ barSelection: barSelection$,
762
+ highlight: gridHighlight$.pipe(
763
+ map(data => data.map(d => d.id))
764
+ ),
765
+ fullChartParams: fullChartParams$
766
+ }).pipe(
767
+ takeUntil(destroy$),
768
+ switchMap(async d => d)
769
+ ).subscribe(data => {
770
+ highlight({
771
+ selection: data.barSelection,
772
+ ids: data.highlight,
773
+ fullChartParams: data.fullChartParams
774
+ })
775
+ })
776
+
777
+ return () => {
778
+ destroy$.next(undefined)
779
+ }
780
780
  }