@orbcharts/plugins-basic 3.0.8 → 3.0.10

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