@orbcharts/plugins-basic 3.0.0-alpha.41 → 3.0.0-alpha.43

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