@orbcharts/plugins-basic 3.0.6 → 3.0.7

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 (116) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +2332 -2316
  3. package/dist/orbcharts-plugins-basic.umd.js +32 -32
  4. package/lib/core-types.ts +7 -7
  5. package/lib/core.ts +6 -6
  6. package/lib/gridObservables.ts +6 -6
  7. package/lib/plugins-basic-types.ts +6 -6
  8. package/package.json +48 -48
  9. package/src/base/BaseBars.ts +765 -765
  10. package/src/base/BaseBarsTriangle.ts +676 -676
  11. package/src/base/BaseDots.ts +464 -464
  12. package/src/base/BaseGroupAxis.ts +691 -691
  13. package/src/base/BaseLegend.ts +684 -684
  14. package/src/base/BaseLineAreas.ts +629 -629
  15. package/src/base/BaseLines.ts +706 -706
  16. package/src/base/BaseOrdinalBubbles.ts +729 -729
  17. package/src/base/BaseRacingBars.ts +582 -582
  18. package/src/base/BaseRacingLabels.ts +404 -404
  19. package/src/base/BaseRacingValueLabels.ts +403 -403
  20. package/src/base/BaseStackedBars.ts +782 -782
  21. package/src/base/BaseTooltip.ts +408 -386
  22. package/src/base/BaseValueAxis.ts +600 -600
  23. package/src/base/BaseXAxis.ts +427 -427
  24. package/src/base/BaseXZoom.ts +241 -241
  25. package/src/base/BaseYAxis.ts +389 -389
  26. package/src/base/types.ts +2 -2
  27. package/src/const.ts +30 -30
  28. package/src/grid/defaults.ts +213 -213
  29. package/src/grid/gridObservables.ts +635 -635
  30. package/src/grid/index.ts +16 -16
  31. package/src/grid/plugins/Bars.ts +69 -69
  32. package/src/grid/plugins/BarsPN.ts +66 -66
  33. package/src/grid/plugins/BarsTriangle.ts +73 -73
  34. package/src/grid/plugins/Dots.ts +68 -68
  35. package/src/grid/plugins/GridLegend.ts +107 -107
  36. package/src/grid/plugins/GridTooltip.ts +66 -66
  37. package/src/grid/plugins/GroupAux.ts +1095 -1095
  38. package/src/grid/plugins/GroupAxis.ts +73 -73
  39. package/src/grid/plugins/GroupZoom.ts +218 -218
  40. package/src/grid/plugins/LineAreas.ts +65 -65
  41. package/src/grid/plugins/Lines.ts +59 -59
  42. package/src/grid/plugins/StackedBars.ts +64 -64
  43. package/src/grid/plugins/StackedValueAxis.ts +96 -96
  44. package/src/grid/plugins/ValueAxis.ts +94 -94
  45. package/src/index.ts +6 -6
  46. package/src/multiGrid/defaults.ts +244 -244
  47. package/src/multiGrid/index.ts +14 -14
  48. package/src/multiGrid/multiGridObservables.ts +50 -50
  49. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  50. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  51. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  52. package/src/multiGrid/plugins/MultiGridLegend.ts +169 -169
  53. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  54. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  55. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  56. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  57. package/src/multiGrid/plugins/MultiStackedBars.ts +106 -106
  58. package/src/multiGrid/plugins/MultiStackedValueAxis.ts +134 -134
  59. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  60. package/src/multiGrid/plugins/OverlappingStackedValueAxes.ts +300 -300
  61. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  62. package/src/multiValue/defaults.ts +523 -523
  63. package/src/multiValue/index.ts +16 -16
  64. package/src/multiValue/multiValueObservables.ts +781 -781
  65. package/src/multiValue/plugins/MultiValueLegend.ts +107 -107
  66. package/src/multiValue/plugins/MultiValueTooltip.ts +66 -66
  67. package/src/multiValue/plugins/OrdinalAux.ts +660 -660
  68. package/src/multiValue/plugins/OrdinalAxis.ts +524 -524
  69. package/src/multiValue/plugins/OrdinalBubbles.ts +226 -226
  70. package/src/multiValue/plugins/OrdinalZoom.ts +57 -57
  71. package/src/multiValue/plugins/RacingBars.ts +375 -375
  72. package/src/multiValue/plugins/RacingCounterTexts.ts +300 -300
  73. package/src/multiValue/plugins/RacingValueAxis.ts +114 -114
  74. package/src/multiValue/plugins/Scatter.ts +486 -486
  75. package/src/multiValue/plugins/ScatterBubbles.ts +635 -635
  76. package/src/multiValue/plugins/XAxis.ts +107 -107
  77. package/src/multiValue/plugins/XYAux.ts +683 -683
  78. package/src/multiValue/plugins/XYAxes.ts +194 -194
  79. package/src/multiValue/plugins/XYAxes_legacy.ts +683 -683
  80. package/src/multiValue/plugins/XZoom.ts +40 -40
  81. package/src/noneData/defaults.ts +102 -102
  82. package/src/noneData/index.ts +3 -3
  83. package/src/noneData/plugins/Container.ts +27 -27
  84. package/src/noneData/plugins/Tooltip.ts +373 -373
  85. package/src/relationship/defaults.ts +221 -221
  86. package/src/relationship/index.ts +5 -5
  87. package/src/relationship/plugins/ForceDirected.ts +1056 -1056
  88. package/src/relationship/plugins/ForceDirectedBubbles.ts +1294 -1294
  89. package/src/relationship/plugins/RelationshipLegend.ts +100 -100
  90. package/src/relationship/plugins/RelationshipTooltip.ts +66 -66
  91. package/src/relationship/relationshipObservables.ts +49 -49
  92. package/src/series/defaults.ts +223 -223
  93. package/src/series/index.ts +9 -9
  94. package/src/series/plugins/Bubbles.ts +784 -781
  95. package/src/series/plugins/Pie.ts +622 -622
  96. package/src/series/plugins/PieEventTexts.ts +283 -283
  97. package/src/series/plugins/PieLabels.ts +639 -639
  98. package/src/series/plugins/Rose.ts +515 -515
  99. package/src/series/plugins/RoseLabels.ts +599 -599
  100. package/src/series/plugins/SeriesLegend.ts +107 -107
  101. package/src/series/plugins/SeriesTooltip.ts +66 -66
  102. package/src/series/seriesObservables.ts +168 -168
  103. package/src/series/seriesUtils.ts +51 -51
  104. package/src/tree/defaults.ts +102 -102
  105. package/src/tree/index.ts +4 -4
  106. package/src/tree/plugins/TreeLegend.ts +100 -100
  107. package/src/tree/plugins/TreeMap.ts +341 -341
  108. package/src/tree/plugins/TreeTooltip.ts +66 -66
  109. package/src/utils/commonUtils.ts +31 -31
  110. package/src/utils/d3Graphics.ts +176 -176
  111. package/src/utils/d3Utils.ts +92 -92
  112. package/src/utils/observables.ts +14 -14
  113. package/src/utils/orbchartsUtils.ts +129 -129
  114. package/tsconfig.base.json +13 -13
  115. package/tsconfig.json +2 -2
  116. package/vite.config.js +22 -22
@@ -1,730 +1,730 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- map,
5
- switchMap,
6
- takeUntil,
7
- distinctUntilChanged,
8
- shareReplay,
9
- Observable,
10
- Subject } from 'rxjs'
11
- import type { BasePluginFn } from './types'
12
- import type {
13
- ComputedDatumMultiValue,
14
- ComputedDatumWithSumMultiValue,
15
- ComputedDataMultiValue,
16
- // ComputedLayoutDataGrid,
17
- DataFormatterTypeMap,
18
- ContainerPositionScaled,
19
- ContainerSize,
20
- EventMultiValue,
21
- ChartParams,
22
- Layout,
23
- TransformData } from '../../lib/core-types'
24
- import type { BaseOrdinalBubblesParams } from '../../lib/plugins-basic-types'
25
- import { getD3TransitionEase } from '../utils/d3Utils'
26
- import { getClassName, getUniID } from '../utils/orbchartsUtils'
27
- import { multiValueContainerSelectionsObservable } from '../multiValue/multiValueObservables'
28
-
29
- // export interface BaseBarsParams {
30
- // // barType: BarType
31
- // barWidth: number
32
- // barPadding: number
33
- // barGroupPadding: number // 群組和群組間的間隔
34
- // barRadius: number | boolean
35
- // }
36
-
37
- interface BaseRacingBarsContext {
38
- selection: d3.Selection<any, unknown, any, unknown>
39
- computedData$: Observable<ComputedDataMultiValue>
40
- visibleComputedRankingData$: Observable<ComputedDatumWithSumMultiValue[][]>
41
- CategoryDataMap$: Observable<Map<string, ComputedDatumMultiValue[]>>
42
- valueLabels$: Observable<string[]>
43
- fullParams$: Observable<BaseOrdinalBubblesParams >
44
- fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
45
- fullChartParams$: Observable<ChartParams>
46
- // xyValueIndex$: Observable<[number, number]>
47
- highlight$: Observable<ComputedDatumMultiValue[]>
48
- rankingItemHeight$: Observable<number>
49
- rankingScaleList$: Observable<d3.ScalePoint<string>[]>
50
- containerPosition$: Observable<ContainerPositionScaled[]>
51
- containerSize$: Observable<ContainerSize>
52
- ordinalScale$: Observable<d3.ScaleLinear<number, number>>
53
- ordinalPadding$: Observable<number>
54
- isCategorySeprate$: Observable<boolean>
55
- event$: Subject<EventMultiValue>
56
- }
57
-
58
- interface RenderGraphicGParams {
59
- containerSelection: d3.Selection<SVGGElement, ComputedDatumMultiValue[], any, any>
60
- paddingGClassName: string
61
- itemGClassName: string
62
- bubbleData: BubblesDatum[][]
63
- // rankingScaleList: d3.ScalePoint<string>[]
64
- transitionDuration: number
65
- transitionEase: string
66
- ordinalPadding: number
67
- }
68
-
69
- // 對應到 value 裡的每個值
70
- interface BubbleValueDatum {
71
- valueIndex: number
72
- x: number
73
- y: number
74
- r: number
75
- opacity: number
76
- // _originR: number // 紀錄變化前的r
77
- _refDatum: ComputedDatumWithSumMultiValue // reference到資料本身
78
- }
79
-
80
- interface BubblesDatum extends ComputedDatumWithSumMultiValue {
81
- graphicValue: Array<BubbleValueDatum>
82
- // _visibleValue: number[]
83
- }
84
-
85
- type ClipPathDatum = {
86
- id: string;
87
- // x: number;
88
- // y: number;
89
- width: number;
90
- height: number;
91
- }
92
-
93
-
94
- function renderGraphicG ({ containerSelection, paddingGClassName, itemGClassName, bubbleData, transitionDuration, transitionEase, ordinalPadding }: RenderGraphicGParams) {
95
- containerSelection
96
- .each((_, categoryIndex, g) => {
97
- const container = d3.select(g[categoryIndex])
98
- container.selectAll<SVGGElement, ComputedDatumMultiValue>(`g.${paddingGClassName}`)
99
- .data([0])
100
- .join(
101
- enter => {
102
- return enter
103
- .append('g')
104
- .attr('class', paddingGClassName)
105
- },
106
- update => update,
107
- exit => exit.remove()
108
- )
109
- .attr('transform', `translate(${ordinalPadding}, 0)`)
110
- .each((d, i, g) => {
111
- const paddingG = d3.select(g[i])
112
- paddingG.selectAll<SVGGElement, ComputedDatumMultiValue>(`g.${itemGClassName}`)
113
- .data(bubbleData[categoryIndex] ?? [], d => d.id)
114
- .join(
115
- enter => {
116
- return enter
117
- .append('g')
118
- .attr('class', itemGClassName)
119
- .attr('cursor', 'pointer')
120
- .attr('transform', d => {
121
- return `translate(0, ${d.graphicValue[0] ? d.graphicValue[0].y : 0})`
122
- })
123
- },
124
- update => {
125
- return update
126
- .transition()
127
- .duration(transitionDuration)
128
- // .ease(d3.easeLinear)
129
- .ease(getD3TransitionEase(transitionEase))
130
- .attr('transform', d => {
131
- return `translate(0, ${d.graphicValue[0] ? d.graphicValue[0].y : 0})`
132
- })
133
- },
134
- exit => exit.remove()
135
- )
136
- })
137
- })
138
-
139
- const graphicBarSelection: d3.Selection<SVGRectElement, BubblesDatum, SVGGElement, unknown> = containerSelection.selectAll(`g.${itemGClassName}`)
140
-
141
- return graphicBarSelection
142
- }
143
-
144
- function renderBubbles ({ graphicGSelection, transitionDuration }: {
145
- graphicGSelection: d3.Selection<SVGGElement, BubblesDatum, any, any>
146
- // bubblesData: BubblesDatum[][]
147
- // fullParams: BaseOrdinalBubblesParams
148
- // fullChartParams: ChartParams
149
- // sumSeries: boolean
150
- transitionDuration: number
151
- }) {
152
-
153
- graphicGSelection
154
- .each((datum, i, g) => {
155
- const _graphicGSelection = d3.select(g[i])
156
-
157
- _graphicGSelection.selectAll<SVGCircleElement, typeof datum>('circle')
158
- .data(datum.graphicValue)
159
- .join('circle')
160
- .attr('fill', d => datum.color)
161
- .style('opacity', d => d.opacity)
162
- .transition()
163
- .duration(100)
164
- .ease(d3.easeLinear)
165
- .attr('cx', d => d.x)
166
- // .attr('cy', d => d.y)
167
- .attr('r', d => d.r)
168
-
169
- // const itemGSelection = _graphicGSelection.selectAll<SVGGElement, BubblesDatum>("g")
170
- // .data(bubblesData[i], (d) => d.id)
171
- // .join('g')
172
- // .each((datum, datumIndex, _g) => {
173
- // const _itemGSelection = d3.select(_g[datumIndex])
174
-
175
- // _itemGSelection.selectAll<SVGCircleElement, typeof datum>('circle')
176
- // .data(datum.graphicValue)
177
- // .join('circle')
178
- // .attr('fill', d => datum.color)
179
- // .style('opacity', 0.8)
180
- // .transition()
181
- // .duration(200)
182
- // .ease(d3.easeLinear)
183
- // .attr('cx', d => d.x)
184
- // .attr('cy', d => d.y)
185
- // .attr('r', d => d.r)
186
- // })
187
- })
188
-
189
- // const itemGSelection = selection.selectAll<SVGGElement, BubblesDatum[]>("g")
190
- // .data(bubblesData)
191
- // .join(
192
- // enter => {
193
- // return enter
194
- // .append('g')
195
- // .attr('cursor', 'pointer')
196
- // },
197
- // update => {
198
- // return update
199
- // },
200
- // exit => {
201
- // return exit
202
- // .remove()
203
- // }
204
- // )
205
- // .each((d, i, nodes) => {
206
- // const g = d3.select(nodes[i])
207
- // g.selectAll<SVGCircleElement, BubblesDatum>("circle")
208
- // .data(d)
209
- // })
210
-
211
- const bubblesSelection: d3.Selection<SVGRectElement, BubbleValueDatum, SVGGElement, unknown> = graphicGSelection.selectAll(`circle`)
212
-
213
- return bubblesSelection
214
- }
215
-
216
- function renderClipPath ({ defsSelection, clipPathData }: {
217
- defsSelection: d3.Selection<SVGDefsElement, any, any, any>
218
- clipPathData: ClipPathDatum[]
219
- }) {
220
- const clipPath = defsSelection
221
- .selectAll<SVGClipPathElement, Layout>('clipPath')
222
- .data(clipPathData)
223
- .join(
224
- enter => {
225
- return enter
226
- .append('clipPath')
227
- },
228
- update => update,
229
- exit => exit.remove()
230
- )
231
- .attr('id', d => d.id)
232
- .each((d, i, g) => {
233
- const rect = d3.select(g[i])
234
- .selectAll<SVGRectElement, typeof d>('rect')
235
- .data([d])
236
- .join(
237
- enter => {
238
- return enter
239
- .append('rect')
240
- },
241
- update => update,
242
- exit => exit.remove()
243
- )
244
- .attr('x', 0)
245
- .attr('y', 0)
246
- .attr('width', _d => _d.width)
247
- .attr('height', _d => _d.height)
248
- })
249
- }
250
-
251
- function highlight ({ selection, ids, fullChartParams }: {
252
- selection: d3.Selection<any, BubbleValueDatum, any, any>
253
- ids: string[]
254
- fullChartParams: ChartParams
255
- }) {
256
- selection.interrupt('highlight')
257
-
258
- if (!ids.length) {
259
- // remove highlight
260
- selection
261
- .transition('highlight')
262
- .duration(200)
263
- .style('opacity', (d, i) => d.opacity)
264
- return
265
- }
266
-
267
- selection
268
- .each((d, i, n) => {
269
- const datum = d._refDatum
270
- if (ids.includes(datum.id)) {
271
- d3.select(n[i])
272
- .style('opacity', (d: BubbleValueDatum) => d.opacity)
273
- } else {
274
- d3.select(n[i])
275
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
276
- }
277
- })
278
- }
279
-
280
-
281
- export const createBaseOrdinalBubbles: BasePluginFn<BaseRacingBarsContext> = (pluginName: string, {
282
- selection,
283
- computedData$,
284
- visibleComputedRankingData$,
285
- // xyValueIndex$,
286
- // categoryLabels$,
287
- CategoryDataMap$,
288
- valueLabels$,
289
- fullParams$,
290
- fullDataFormatter$,
291
- fullChartParams$,
292
- // layout$,
293
- // graphicTransform$,
294
- // graphicReverseScale$,
295
- highlight$,
296
- // computedRankingAmountList$,
297
- rankingItemHeight$,
298
- rankingScaleList$,
299
- containerPosition$,
300
- containerSize$,
301
- // layout$,
302
- ordinalScale$,
303
- ordinalPadding$,
304
- isCategorySeprate$,
305
- event$
306
- }) => {
307
-
308
- const destroy$ = new Subject()
309
-
310
- const clipPathID = getUniID(pluginName, 'clipPath-box')
311
- const paddingGClassName = getClassName(pluginName, 'padding-g')
312
- const itemGClassName = getClassName(pluginName, 'item-g')
313
- const bubbleClassName = getClassName(pluginName, 'bubble')
314
- // const containerClassName = getClassName(pluginName, 'container')
315
-
316
-
317
- const containerSelection$ = multiValueContainerSelectionsObservable({
318
- selection,
319
- pluginName,
320
- clipPathID,
321
- computedData$,
322
- containerPosition$,
323
- isCategorySeprate$,
324
- }).pipe(
325
- takeUntil(destroy$),
326
- )
327
-
328
- containerSize$.subscribe(data => {
329
- const defsSelection = selection.selectAll<SVGDefsElement, any>('defs')
330
- .data([clipPathID])
331
- .join('defs')
332
- const clipPathData = [{
333
- id: clipPathID,
334
- width: data.width,
335
- height: data.height
336
- }]
337
- renderClipPath({
338
- defsSelection: defsSelection,
339
- clipPathData,
340
- // textReverseTransform: data.textReverseTransform
341
- })
342
- })
343
-
344
- const maxRadius$ = combineLatest({
345
- sizeAdjust: fullParams$.pipe(
346
- map(p => p.bubble.sizeAdjust),
347
- distinctUntilChanged()
348
- ),
349
- rankingItemHeight: rankingItemHeight$
350
- }).pipe(
351
- takeUntil(destroy$),
352
- switchMap(async d => d),
353
- map(d => (d.rankingItemHeight * d.sizeAdjust) / 2),
354
- distinctUntilChanged(),
355
- shareReplay(1)
356
- )
357
-
358
- const scaleDomain$ = combineLatest({
359
- visibleComputedRankingData: visibleComputedRankingData$,
360
- scaleDomain: fullDataFormatter$.pipe(
361
- map(d => d.xAxis.scaleDomain),
362
- ),
363
- }).pipe(
364
- takeUntil(destroy$),
365
- switchMap(async d => d),
366
- map(data => {
367
- const firstValue = data.visibleComputedRankingData[0] && data.visibleComputedRankingData[0][0]
368
- ? data.visibleComputedRankingData[0][0].value
369
- : []
370
- let startIndex = data.scaleDomain[0] === 'auto' || data.scaleDomain[0] === 'min'
371
- ? 0
372
- : data.scaleDomain[0]
373
- let endIndex = data.scaleDomain[1] === 'auto' || data.scaleDomain[1] === 'max'
374
- ? firstValue.length - 1 // 用第一筆資料判斷value長度
375
- : data.scaleDomain[1]
376
- return [startIndex, endIndex]
377
- }),
378
- distinctUntilChanged((a, b) => a[0] === b[0] && a[1] === b[1]),
379
- shareReplay(1)
380
- )
381
-
382
- const minMaxValue$ = combineLatest({
383
- visibleComputedRankingData: visibleComputedRankingData$,
384
- scaleDomain: scaleDomain$,
385
- }).pipe(
386
- takeUntil(destroy$),
387
- switchMap(async d => d),
388
- map(data => {
389
- let minValue = 0
390
- let maxValue = 0
391
- // console.log(data.scaleDomain[0], data.scaleDomain[1])
392
- data.visibleComputedRankingData.forEach(categoryData => {
393
- categoryData.forEach(datum => {
394
- for (let i = data.scaleDomain[0]; i <= data.scaleDomain[1]; i++) {
395
- const v = datum.value[i]
396
- if (v == null) {
397
- continue
398
- }
399
- if (v > maxValue) {
400
- maxValue = v
401
- } else if (v < minValue) {
402
- minValue = v
403
- }
404
- }
405
- })
406
- })
407
- // console.log([minValue, maxValue])
408
- return [minValue, maxValue]
409
- }),
410
- distinctUntilChanged(),
411
- shareReplay(1)
412
- )
413
-
414
-
415
- const opacityScale$ = combineLatest({
416
- minMaxValue: minMaxValue$,
417
- fullParams: fullParams$
418
- }).pipe(
419
- takeUntil(destroy$),
420
- map(data => {
421
- const opacityScale = d3.scaleLinear()
422
- .domain(data.minMaxValue)
423
- .range(data.fullParams.bubble.valueLinearOpacity)
424
- return opacityScale
425
- }),
426
- distinctUntilChanged(),
427
- shareReplay(1)
428
- )
429
-
430
- const radiusScale$ = combineLatest({
431
- maxRadius: maxRadius$,
432
- minMaxValue: minMaxValue$,
433
- arcScaleType: fullParams$.pipe(
434
- map(p => p.bubble.arcScaleType),
435
- distinctUntilChanged()
436
- )
437
- }).pipe(
438
- takeUntil(destroy$),
439
- switchMap(async d => d),
440
- map(data => {
441
- // 半徑比例尺
442
- const radiusScale = d3.scalePow()
443
- .domain([0, data.minMaxValue[1]])
444
- .range([2, data.maxRadius]) // 最小半徑為2
445
- .exponent(data.arcScaleType === 'area'
446
- ? 0.5 // 數值映射面積(0.5為取平方根)
447
- : 1 // 數值映射半徑
448
- )
449
- return radiusScale
450
- }),
451
- distinctUntilChanged(),
452
- shareReplay(1)
453
- )
454
-
455
- const bubbleData$: Observable<BubblesDatum[][]> = combineLatest({
456
- visibleComputedRankingData: visibleComputedRankingData$,
457
- // computedData: computedData$,
458
- // fullParams: fullParams$,
459
- // fullChartParams: fullChartParams$,
460
- radiusScale: radiusScale$,
461
- rankingScaleList: rankingScaleList$,
462
- ordinalScale: ordinalScale$,
463
- opacityScale: opacityScale$,
464
- scaleDomain: scaleDomain$,
465
- containerSize: containerSize$,
466
- }).pipe(
467
- takeUntil(destroy$),
468
- switchMap(async d => d),
469
- map(data => {
470
- // console.log('data.visibleComputedRankingData', data.visibleComputedRankingData)
471
- return data.visibleComputedRankingData.map((categoryData, categoryIndex) => {
472
- const rankingScale = data.rankingScaleList[categoryIndex]
473
-
474
- return categoryData.map((_d, i) => {
475
- const d = _d as BubblesDatum
476
- const graphicValue = d.value.map((v, vIndex) => {
477
- // console.log('data.ordinalScale(vIndex)', data.ordinalScale(vIndex))
478
- // const opacity = vIndex < data.scaleDomain[0] || vIndex > data.scaleDomain[1]
479
- // ? 0
480
- // : data.opacityScale(v)
481
- let x = data.ordinalScale(vIndex)
482
-
483
- if (data.scaleDomain[0] === data.scaleDomain[1]) {
484
- if (vIndex > data.scaleDomain[0]) {
485
- x += data.containerSize.width * 1.5
486
- } else if (vIndex < data.scaleDomain[0]) {
487
- x -= data.containerSize.width * 1.5
488
- }
489
- }
490
-
491
- return {
492
- valueIndex: vIndex,
493
- x,
494
- y: rankingScale(d.label),
495
- r: data.radiusScale(v),
496
- opacity: data.opacityScale(v),
497
- // _originR: data.radiusScale(v)
498
- _refDatum: d // reference到資料本身
499
- }
500
- })
501
-
502
- // d._visibleValue = [] // highlight的時候才寫入
503
- return {
504
- ...d,
505
- graphicValue
506
- }
507
- })
508
- })
509
- })
510
- )
511
-
512
- const transitionDuration$ = fullChartParams$.pipe(
513
- takeUntil(destroy$),
514
- map(d => d.transitionDuration),
515
- distinctUntilChanged()
516
- )
517
-
518
- const transitionEase$ = fullChartParams$.pipe(
519
- takeUntil(destroy$),
520
- map(d => d.transitionEase),
521
- distinctUntilChanged()
522
- )
523
-
524
- const graphicGSelection$ = combineLatest({
525
- containerSelection: containerSelection$,
526
- bubbleData: bubbleData$,
527
- rankingScaleList: rankingScaleList$,
528
- transitionDuration: transitionDuration$,
529
- transitionEase: transitionEase$,
530
- ordinalPadding: ordinalPadding$
531
- }).pipe(
532
- takeUntil(destroy$),
533
- switchMap(async (d) => d),
534
- map(data => {
535
- // console.log('bubbleData', data.bubbleData)
536
- return renderGraphicG({
537
- containerSelection: data.containerSelection,
538
- paddingGClassName,
539
- itemGClassName,
540
- bubbleData: data.bubbleData,
541
- // rankingScaleList: data.rankingScaleList,
542
- transitionDuration: data.transitionDuration,
543
- transitionEase: data.transitionEase,
544
- ordinalPadding: data.ordinalPadding
545
- })
546
- })
547
- )
548
-
549
- const graphicSelection$ = combineLatest({
550
- graphicGSelection: graphicGSelection$,
551
- // xyValueIndex: xyValueIndex$,
552
- ordinalScale: ordinalScale$,
553
- bubbleData: bubbleData$,
554
- transitionDuration: transitionDuration$,
555
- fullParams: fullParams$,
556
- }).pipe(
557
- takeUntil(destroy$),
558
- switchMap(async (d) => d),
559
- map(data => {
560
-
561
- return renderBubbles({
562
- graphicGSelection: data.graphicGSelection,
563
- // bubblesData: data.bubbleData
564
- transitionDuration: data.transitionDuration
565
- })
566
- }),
567
- shareReplay(1)
568
- )
569
-
570
- const highlightTarget$ = fullChartParams$.pipe(
571
- takeUntil(destroy$),
572
- map(d => d.highlightTarget),
573
- distinctUntilChanged()
574
- )
575
-
576
- combineLatest({
577
- graphicSelection: graphicSelection$,
578
- computedData: computedData$,
579
- CategoryDataMap: CategoryDataMap$,
580
- valueLabels: valueLabels$,
581
- highlightTarget: highlightTarget$,
582
- fullDataFormatter: fullDataFormatter$,
583
- }).pipe(
584
- takeUntil(destroy$),
585
- switchMap(async (d) => d),
586
- ).subscribe(data => {
587
-
588
- data.graphicSelection
589
- .on('mouseover', (event, valueDatum) => {
590
- // event.stopPropagation()
591
-
592
- // reference 到資料本身
593
- const datum = valueDatum._refDatum
594
-
595
- // 只顯示目前的值
596
- // datum._visibleValue = [datum.value[valueDatum.index]]
597
- // 只顯示總數
598
- // datum._visibleValue = [datum.sum]
599
-
600
- event$.next({
601
- type: 'multiValue',
602
- eventName: 'mouseover',
603
- pluginName,
604
- highlightTarget: data.highlightTarget,
605
- valueDetail: [
606
- {
607
- value: datum.value[valueDatum.valueIndex],
608
- valueIndex: valueDatum.valueIndex,
609
- valueLabel: data.valueLabels[valueDatum.valueIndex]
610
- },
611
- ],
612
- datum: datum,
613
- category: data.CategoryDataMap.get(datum.categoryLabel)!,
614
- categoryIndex: datum.categoryIndex,
615
- categoryLabel: datum.categoryLabel,
616
- data: data.computedData,
617
- event,
618
- })
619
- })
620
- .on('mousemove', (event, valueDatum) => {
621
- // event.stopPropagation()
622
-
623
- // reference 到資料本身
624
- const datum = valueDatum._refDatum
625
-
626
- // 只顯示目前的值
627
- // datum._visibleValue = [datum.value[valueDatum.index]]
628
- // 只顯示總數
629
- // datum._visibleValue = [datum.sum]
630
- event$.next({
631
- type: 'multiValue',
632
- eventName: 'mousemove',
633
- pluginName,
634
- highlightTarget: data.highlightTarget,
635
- valueDetail: [
636
- {
637
- value: datum.value[valueDatum.valueIndex],
638
- valueIndex: valueDatum.valueIndex,
639
- valueLabel: data.valueLabels[valueDatum.valueIndex]
640
- },
641
- ],
642
- datum,
643
- category: data.CategoryDataMap.get(datum.categoryLabel)!,
644
- categoryIndex: datum.categoryIndex,
645
- categoryLabel: datum.categoryLabel,
646
- data: data.computedData,
647
- event,
648
- })
649
- })
650
- .on('mouseout', (event, valueDatum) => {
651
- // event.stopPropagation()
652
-
653
- // reference 到資料本身
654
- const datum = valueDatum._refDatum
655
-
656
- event$.next({
657
- type: 'multiValue',
658
- eventName: 'mouseout',
659
- pluginName,
660
- highlightTarget: data.highlightTarget,
661
- valueDetail: [
662
- {
663
- value: datum.value[valueDatum.valueIndex],
664
- valueIndex: valueDatum.valueIndex,
665
- valueLabel: data.valueLabels[valueDatum.valueIndex]
666
- },
667
- ],
668
- datum,
669
- category: data.CategoryDataMap.get(datum.categoryLabel)!,
670
- categoryIndex: datum.categoryIndex,
671
- categoryLabel: datum.categoryLabel,
672
- data: data.computedData,
673
- event,
674
- })
675
- })
676
- .on('click', (event, valueDatum) => {
677
- // event.stopPropagation()
678
-
679
- // reference 到資料本身
680
- const datum = valueDatum._refDatum
681
-
682
- // 只顯示目前的值
683
- // datum._visibleValue = [datum.value[valueDatum.index]]
684
- // 只顯示總數
685
- // datum._visibleValue = [datum.sum]
686
-
687
- event$.next({
688
- type: 'multiValue',
689
- eventName: 'click',
690
- pluginName,
691
- highlightTarget: data.highlightTarget,
692
- valueDetail: [
693
- {
694
- value: datum.value[valueDatum.valueIndex],
695
- valueIndex: valueDatum.valueIndex,
696
- valueLabel: data.valueLabels[valueDatum.valueIndex]
697
- },
698
- ],
699
- datum,
700
- category: data.CategoryDataMap.get(datum.categoryLabel)!,
701
- categoryIndex: datum.categoryIndex,
702
- categoryLabel: datum.categoryLabel,
703
- data: data.computedData,
704
- event,
705
- })
706
- })
707
-
708
- })
709
-
710
- combineLatest({
711
- graphicSelection: graphicSelection$,
712
- highlight: highlight$.pipe(
713
- map(data => data.map(d => d.id))
714
- ),
715
- fullChartParams: fullChartParams$
716
- }).pipe(
717
- takeUntil(destroy$),
718
- switchMap(async d => d)
719
- ).subscribe(data => {
720
- highlight({
721
- selection: data.graphicSelection,
722
- ids: data.highlight,
723
- fullChartParams: data.fullChartParams
724
- })
725
- })
726
-
727
- return () => {
728
- destroy$.next(undefined)
729
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ switchMap,
6
+ takeUntil,
7
+ distinctUntilChanged,
8
+ shareReplay,
9
+ Observable,
10
+ Subject } from 'rxjs'
11
+ import type { BasePluginFn } from './types'
12
+ import type {
13
+ ComputedDatumMultiValue,
14
+ ComputedDatumWithSumMultiValue,
15
+ ComputedDataMultiValue,
16
+ // ComputedLayoutDataGrid,
17
+ DataFormatterTypeMap,
18
+ ContainerPositionScaled,
19
+ ContainerSize,
20
+ EventMultiValue,
21
+ ChartParams,
22
+ Layout,
23
+ TransformData } from '../../lib/core-types'
24
+ import type { BaseOrdinalBubblesParams } from '../../lib/plugins-basic-types'
25
+ import { getD3TransitionEase } from '../utils/d3Utils'
26
+ import { getClassName, getUniID } from '../utils/orbchartsUtils'
27
+ import { multiValueContainerSelectionsObservable } from '../multiValue/multiValueObservables'
28
+
29
+ // export interface BaseBarsParams {
30
+ // // barType: BarType
31
+ // barWidth: number
32
+ // barPadding: number
33
+ // barGroupPadding: number // 群組和群組間的間隔
34
+ // barRadius: number | boolean
35
+ // }
36
+
37
+ interface BaseRacingBarsContext {
38
+ selection: d3.Selection<any, unknown, any, unknown>
39
+ computedData$: Observable<ComputedDataMultiValue>
40
+ visibleComputedRankingData$: Observable<ComputedDatumWithSumMultiValue[][]>
41
+ CategoryDataMap$: Observable<Map<string, ComputedDatumMultiValue[]>>
42
+ valueLabels$: Observable<string[]>
43
+ fullParams$: Observable<BaseOrdinalBubblesParams >
44
+ fullDataFormatter$: Observable<DataFormatterTypeMap<'multiValue'>>
45
+ fullChartParams$: Observable<ChartParams>
46
+ // xyValueIndex$: Observable<[number, number]>
47
+ highlight$: Observable<ComputedDatumMultiValue[]>
48
+ rankingItemHeight$: Observable<number>
49
+ rankingScaleList$: Observable<d3.ScalePoint<string>[]>
50
+ containerPosition$: Observable<ContainerPositionScaled[]>
51
+ containerSize$: Observable<ContainerSize>
52
+ ordinalScale$: Observable<d3.ScaleLinear<number, number>>
53
+ ordinalPadding$: Observable<number>
54
+ isCategorySeprate$: Observable<boolean>
55
+ event$: Subject<EventMultiValue>
56
+ }
57
+
58
+ interface RenderGraphicGParams {
59
+ containerSelection: d3.Selection<SVGGElement, ComputedDatumMultiValue[], any, any>
60
+ paddingGClassName: string
61
+ itemGClassName: string
62
+ bubbleData: BubblesDatum[][]
63
+ // rankingScaleList: d3.ScalePoint<string>[]
64
+ transitionDuration: number
65
+ transitionEase: string
66
+ ordinalPadding: number
67
+ }
68
+
69
+ // 對應到 value 裡的每個值
70
+ interface BubbleValueDatum {
71
+ valueIndex: number
72
+ x: number
73
+ y: number
74
+ r: number
75
+ opacity: number
76
+ // _originR: number // 紀錄變化前的r
77
+ _refDatum: ComputedDatumWithSumMultiValue // reference到資料本身
78
+ }
79
+
80
+ interface BubblesDatum extends ComputedDatumWithSumMultiValue {
81
+ graphicValue: Array<BubbleValueDatum>
82
+ // _visibleValue: number[]
83
+ }
84
+
85
+ type ClipPathDatum = {
86
+ id: string;
87
+ // x: number;
88
+ // y: number;
89
+ width: number;
90
+ height: number;
91
+ }
92
+
93
+
94
+ function renderGraphicG ({ containerSelection, paddingGClassName, itemGClassName, bubbleData, transitionDuration, transitionEase, ordinalPadding }: RenderGraphicGParams) {
95
+ containerSelection
96
+ .each((_, categoryIndex, g) => {
97
+ const container = d3.select(g[categoryIndex])
98
+ container.selectAll<SVGGElement, ComputedDatumMultiValue>(`g.${paddingGClassName}`)
99
+ .data([0])
100
+ .join(
101
+ enter => {
102
+ return enter
103
+ .append('g')
104
+ .attr('class', paddingGClassName)
105
+ },
106
+ update => update,
107
+ exit => exit.remove()
108
+ )
109
+ .attr('transform', `translate(${ordinalPadding}, 0)`)
110
+ .each((d, i, g) => {
111
+ const paddingG = d3.select(g[i])
112
+ paddingG.selectAll<SVGGElement, ComputedDatumMultiValue>(`g.${itemGClassName}`)
113
+ .data(bubbleData[categoryIndex] ?? [], d => d.id)
114
+ .join(
115
+ enter => {
116
+ return enter
117
+ .append('g')
118
+ .attr('class', itemGClassName)
119
+ .attr('cursor', 'pointer')
120
+ .attr('transform', d => {
121
+ return `translate(0, ${d.graphicValue[0] ? d.graphicValue[0].y : 0})`
122
+ })
123
+ },
124
+ update => {
125
+ return update
126
+ .transition()
127
+ .duration(transitionDuration)
128
+ // .ease(d3.easeLinear)
129
+ .ease(getD3TransitionEase(transitionEase))
130
+ .attr('transform', d => {
131
+ return `translate(0, ${d.graphicValue[0] ? d.graphicValue[0].y : 0})`
132
+ })
133
+ },
134
+ exit => exit.remove()
135
+ )
136
+ })
137
+ })
138
+
139
+ const graphicBarSelection: d3.Selection<SVGRectElement, BubblesDatum, SVGGElement, unknown> = containerSelection.selectAll(`g.${itemGClassName}`)
140
+
141
+ return graphicBarSelection
142
+ }
143
+
144
+ function renderBubbles ({ graphicGSelection, transitionDuration }: {
145
+ graphicGSelection: d3.Selection<SVGGElement, BubblesDatum, any, any>
146
+ // bubblesData: BubblesDatum[][]
147
+ // fullParams: BaseOrdinalBubblesParams
148
+ // fullChartParams: ChartParams
149
+ // sumSeries: boolean
150
+ transitionDuration: number
151
+ }) {
152
+
153
+ graphicGSelection
154
+ .each((datum, i, g) => {
155
+ const _graphicGSelection = d3.select(g[i])
156
+
157
+ _graphicGSelection.selectAll<SVGCircleElement, typeof datum>('circle')
158
+ .data(datum.graphicValue)
159
+ .join('circle')
160
+ .attr('fill', d => datum.color)
161
+ .style('opacity', d => d.opacity)
162
+ .transition()
163
+ .duration(100)
164
+ .ease(d3.easeLinear)
165
+ .attr('cx', d => d.x)
166
+ // .attr('cy', d => d.y)
167
+ .attr('r', d => d.r)
168
+
169
+ // const itemGSelection = _graphicGSelection.selectAll<SVGGElement, BubblesDatum>("g")
170
+ // .data(bubblesData[i], (d) => d.id)
171
+ // .join('g')
172
+ // .each((datum, datumIndex, _g) => {
173
+ // const _itemGSelection = d3.select(_g[datumIndex])
174
+
175
+ // _itemGSelection.selectAll<SVGCircleElement, typeof datum>('circle')
176
+ // .data(datum.graphicValue)
177
+ // .join('circle')
178
+ // .attr('fill', d => datum.color)
179
+ // .style('opacity', 0.8)
180
+ // .transition()
181
+ // .duration(200)
182
+ // .ease(d3.easeLinear)
183
+ // .attr('cx', d => d.x)
184
+ // .attr('cy', d => d.y)
185
+ // .attr('r', d => d.r)
186
+ // })
187
+ })
188
+
189
+ // const itemGSelection = selection.selectAll<SVGGElement, BubblesDatum[]>("g")
190
+ // .data(bubblesData)
191
+ // .join(
192
+ // enter => {
193
+ // return enter
194
+ // .append('g')
195
+ // .attr('cursor', 'pointer')
196
+ // },
197
+ // update => {
198
+ // return update
199
+ // },
200
+ // exit => {
201
+ // return exit
202
+ // .remove()
203
+ // }
204
+ // )
205
+ // .each((d, i, nodes) => {
206
+ // const g = d3.select(nodes[i])
207
+ // g.selectAll<SVGCircleElement, BubblesDatum>("circle")
208
+ // .data(d)
209
+ // })
210
+
211
+ const bubblesSelection: d3.Selection<SVGRectElement, BubbleValueDatum, SVGGElement, unknown> = graphicGSelection.selectAll(`circle`)
212
+
213
+ return bubblesSelection
214
+ }
215
+
216
+ function renderClipPath ({ defsSelection, clipPathData }: {
217
+ defsSelection: d3.Selection<SVGDefsElement, any, any, any>
218
+ clipPathData: ClipPathDatum[]
219
+ }) {
220
+ const clipPath = defsSelection
221
+ .selectAll<SVGClipPathElement, Layout>('clipPath')
222
+ .data(clipPathData)
223
+ .join(
224
+ enter => {
225
+ return enter
226
+ .append('clipPath')
227
+ },
228
+ update => update,
229
+ exit => exit.remove()
230
+ )
231
+ .attr('id', d => d.id)
232
+ .each((d, i, g) => {
233
+ const rect = d3.select(g[i])
234
+ .selectAll<SVGRectElement, typeof d>('rect')
235
+ .data([d])
236
+ .join(
237
+ enter => {
238
+ return enter
239
+ .append('rect')
240
+ },
241
+ update => update,
242
+ exit => exit.remove()
243
+ )
244
+ .attr('x', 0)
245
+ .attr('y', 0)
246
+ .attr('width', _d => _d.width)
247
+ .attr('height', _d => _d.height)
248
+ })
249
+ }
250
+
251
+ function highlight ({ selection, ids, fullChartParams }: {
252
+ selection: d3.Selection<any, BubbleValueDatum, any, any>
253
+ ids: string[]
254
+ fullChartParams: ChartParams
255
+ }) {
256
+ selection.interrupt('highlight')
257
+
258
+ if (!ids.length) {
259
+ // remove highlight
260
+ selection
261
+ .transition('highlight')
262
+ .duration(200)
263
+ .style('opacity', (d, i) => d.opacity)
264
+ return
265
+ }
266
+
267
+ selection
268
+ .each((d, i, n) => {
269
+ const datum = d._refDatum
270
+ if (ids.includes(datum.id)) {
271
+ d3.select(n[i])
272
+ .style('opacity', (d: BubbleValueDatum) => d.opacity)
273
+ } else {
274
+ d3.select(n[i])
275
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
276
+ }
277
+ })
278
+ }
279
+
280
+
281
+ export const createBaseOrdinalBubbles: BasePluginFn<BaseRacingBarsContext> = (pluginName: string, {
282
+ selection,
283
+ computedData$,
284
+ visibleComputedRankingData$,
285
+ // xyValueIndex$,
286
+ // categoryLabels$,
287
+ CategoryDataMap$,
288
+ valueLabels$,
289
+ fullParams$,
290
+ fullDataFormatter$,
291
+ fullChartParams$,
292
+ // layout$,
293
+ // graphicTransform$,
294
+ // graphicReverseScale$,
295
+ highlight$,
296
+ // computedRankingAmountList$,
297
+ rankingItemHeight$,
298
+ rankingScaleList$,
299
+ containerPosition$,
300
+ containerSize$,
301
+ // layout$,
302
+ ordinalScale$,
303
+ ordinalPadding$,
304
+ isCategorySeprate$,
305
+ event$
306
+ }) => {
307
+
308
+ const destroy$ = new Subject()
309
+
310
+ const clipPathID = getUniID(pluginName, 'clipPath-box')
311
+ const paddingGClassName = getClassName(pluginName, 'padding-g')
312
+ const itemGClassName = getClassName(pluginName, 'item-g')
313
+ const bubbleClassName = getClassName(pluginName, 'bubble')
314
+ // const containerClassName = getClassName(pluginName, 'container')
315
+
316
+
317
+ const containerSelection$ = multiValueContainerSelectionsObservable({
318
+ selection,
319
+ pluginName,
320
+ clipPathID,
321
+ computedData$,
322
+ containerPosition$,
323
+ isCategorySeprate$,
324
+ }).pipe(
325
+ takeUntil(destroy$),
326
+ )
327
+
328
+ containerSize$.subscribe(data => {
329
+ const defsSelection = selection.selectAll<SVGDefsElement, any>('defs')
330
+ .data([clipPathID])
331
+ .join('defs')
332
+ const clipPathData = [{
333
+ id: clipPathID,
334
+ width: data.width,
335
+ height: data.height
336
+ }]
337
+ renderClipPath({
338
+ defsSelection: defsSelection,
339
+ clipPathData,
340
+ // textReverseTransform: data.textReverseTransform
341
+ })
342
+ })
343
+
344
+ const maxRadius$ = combineLatest({
345
+ sizeAdjust: fullParams$.pipe(
346
+ map(p => p.bubble.sizeAdjust),
347
+ distinctUntilChanged()
348
+ ),
349
+ rankingItemHeight: rankingItemHeight$
350
+ }).pipe(
351
+ takeUntil(destroy$),
352
+ switchMap(async d => d),
353
+ map(d => (d.rankingItemHeight * d.sizeAdjust) / 2),
354
+ distinctUntilChanged(),
355
+ shareReplay(1)
356
+ )
357
+
358
+ const scaleDomain$ = combineLatest({
359
+ visibleComputedRankingData: visibleComputedRankingData$,
360
+ scaleDomain: fullDataFormatter$.pipe(
361
+ map(d => d.xAxis.scaleDomain),
362
+ ),
363
+ }).pipe(
364
+ takeUntil(destroy$),
365
+ switchMap(async d => d),
366
+ map(data => {
367
+ const firstValue = data.visibleComputedRankingData[0] && data.visibleComputedRankingData[0][0]
368
+ ? data.visibleComputedRankingData[0][0].value
369
+ : []
370
+ let startIndex = data.scaleDomain[0] === 'auto' || data.scaleDomain[0] === 'min'
371
+ ? 0
372
+ : data.scaleDomain[0]
373
+ let endIndex = data.scaleDomain[1] === 'auto' || data.scaleDomain[1] === 'max'
374
+ ? firstValue.length - 1 // 用第一筆資料判斷value長度
375
+ : data.scaleDomain[1]
376
+ return [startIndex, endIndex]
377
+ }),
378
+ distinctUntilChanged((a, b) => a[0] === b[0] && a[1] === b[1]),
379
+ shareReplay(1)
380
+ )
381
+
382
+ const minMaxValue$ = combineLatest({
383
+ visibleComputedRankingData: visibleComputedRankingData$,
384
+ scaleDomain: scaleDomain$,
385
+ }).pipe(
386
+ takeUntil(destroy$),
387
+ switchMap(async d => d),
388
+ map(data => {
389
+ let minValue = 0
390
+ let maxValue = 0
391
+ // console.log(data.scaleDomain[0], data.scaleDomain[1])
392
+ data.visibleComputedRankingData.forEach(categoryData => {
393
+ categoryData.forEach(datum => {
394
+ for (let i = data.scaleDomain[0]; i <= data.scaleDomain[1]; i++) {
395
+ const v = datum.value[i]
396
+ if (v == null) {
397
+ continue
398
+ }
399
+ if (v > maxValue) {
400
+ maxValue = v
401
+ } else if (v < minValue) {
402
+ minValue = v
403
+ }
404
+ }
405
+ })
406
+ })
407
+ // console.log([minValue, maxValue])
408
+ return [minValue, maxValue]
409
+ }),
410
+ distinctUntilChanged(),
411
+ shareReplay(1)
412
+ )
413
+
414
+
415
+ const opacityScale$ = combineLatest({
416
+ minMaxValue: minMaxValue$,
417
+ fullParams: fullParams$
418
+ }).pipe(
419
+ takeUntil(destroy$),
420
+ map(data => {
421
+ const opacityScale = d3.scaleLinear()
422
+ .domain(data.minMaxValue)
423
+ .range(data.fullParams.bubble.valueLinearOpacity)
424
+ return opacityScale
425
+ }),
426
+ distinctUntilChanged(),
427
+ shareReplay(1)
428
+ )
429
+
430
+ const radiusScale$ = combineLatest({
431
+ maxRadius: maxRadius$,
432
+ minMaxValue: minMaxValue$,
433
+ arcScaleType: fullParams$.pipe(
434
+ map(p => p.bubble.arcScaleType),
435
+ distinctUntilChanged()
436
+ )
437
+ }).pipe(
438
+ takeUntil(destroy$),
439
+ switchMap(async d => d),
440
+ map(data => {
441
+ // 半徑比例尺
442
+ const radiusScale = d3.scalePow()
443
+ .domain([0, data.minMaxValue[1]])
444
+ .range([2, data.maxRadius]) // 最小半徑為2
445
+ .exponent(data.arcScaleType === 'area'
446
+ ? 0.5 // 數值映射面積(0.5為取平方根)
447
+ : 1 // 數值映射半徑
448
+ )
449
+ return radiusScale
450
+ }),
451
+ distinctUntilChanged(),
452
+ shareReplay(1)
453
+ )
454
+
455
+ const bubbleData$: Observable<BubblesDatum[][]> = combineLatest({
456
+ visibleComputedRankingData: visibleComputedRankingData$,
457
+ // computedData: computedData$,
458
+ // fullParams: fullParams$,
459
+ // fullChartParams: fullChartParams$,
460
+ radiusScale: radiusScale$,
461
+ rankingScaleList: rankingScaleList$,
462
+ ordinalScale: ordinalScale$,
463
+ opacityScale: opacityScale$,
464
+ scaleDomain: scaleDomain$,
465
+ containerSize: containerSize$,
466
+ }).pipe(
467
+ takeUntil(destroy$),
468
+ switchMap(async d => d),
469
+ map(data => {
470
+ // console.log('data.visibleComputedRankingData', data.visibleComputedRankingData)
471
+ return data.visibleComputedRankingData.map((categoryData, categoryIndex) => {
472
+ const rankingScale = data.rankingScaleList[categoryIndex]
473
+
474
+ return categoryData.map((_d, i) => {
475
+ const d = _d as BubblesDatum
476
+ const graphicValue = d.value.map((v, vIndex) => {
477
+ // console.log('data.ordinalScale(vIndex)', data.ordinalScale(vIndex))
478
+ // const opacity = vIndex < data.scaleDomain[0] || vIndex > data.scaleDomain[1]
479
+ // ? 0
480
+ // : data.opacityScale(v)
481
+ let x = data.ordinalScale(vIndex)
482
+
483
+ if (data.scaleDomain[0] === data.scaleDomain[1]) {
484
+ if (vIndex > data.scaleDomain[0]) {
485
+ x += data.containerSize.width * 1.5
486
+ } else if (vIndex < data.scaleDomain[0]) {
487
+ x -= data.containerSize.width * 1.5
488
+ }
489
+ }
490
+
491
+ return {
492
+ valueIndex: vIndex,
493
+ x,
494
+ y: rankingScale(d.label),
495
+ r: data.radiusScale(v),
496
+ opacity: data.opacityScale(v),
497
+ // _originR: data.radiusScale(v)
498
+ _refDatum: d // reference到資料本身
499
+ }
500
+ })
501
+
502
+ // d._visibleValue = [] // highlight的時候才寫入
503
+ return {
504
+ ...d,
505
+ graphicValue
506
+ }
507
+ })
508
+ })
509
+ })
510
+ )
511
+
512
+ const transitionDuration$ = fullChartParams$.pipe(
513
+ takeUntil(destroy$),
514
+ map(d => d.transitionDuration),
515
+ distinctUntilChanged()
516
+ )
517
+
518
+ const transitionEase$ = fullChartParams$.pipe(
519
+ takeUntil(destroy$),
520
+ map(d => d.transitionEase),
521
+ distinctUntilChanged()
522
+ )
523
+
524
+ const graphicGSelection$ = combineLatest({
525
+ containerSelection: containerSelection$,
526
+ bubbleData: bubbleData$,
527
+ rankingScaleList: rankingScaleList$,
528
+ transitionDuration: transitionDuration$,
529
+ transitionEase: transitionEase$,
530
+ ordinalPadding: ordinalPadding$
531
+ }).pipe(
532
+ takeUntil(destroy$),
533
+ switchMap(async (d) => d),
534
+ map(data => {
535
+ // console.log('bubbleData', data.bubbleData)
536
+ return renderGraphicG({
537
+ containerSelection: data.containerSelection,
538
+ paddingGClassName,
539
+ itemGClassName,
540
+ bubbleData: data.bubbleData,
541
+ // rankingScaleList: data.rankingScaleList,
542
+ transitionDuration: data.transitionDuration,
543
+ transitionEase: data.transitionEase,
544
+ ordinalPadding: data.ordinalPadding
545
+ })
546
+ })
547
+ )
548
+
549
+ const graphicSelection$ = combineLatest({
550
+ graphicGSelection: graphicGSelection$,
551
+ // xyValueIndex: xyValueIndex$,
552
+ ordinalScale: ordinalScale$,
553
+ bubbleData: bubbleData$,
554
+ transitionDuration: transitionDuration$,
555
+ fullParams: fullParams$,
556
+ }).pipe(
557
+ takeUntil(destroy$),
558
+ switchMap(async (d) => d),
559
+ map(data => {
560
+
561
+ return renderBubbles({
562
+ graphicGSelection: data.graphicGSelection,
563
+ // bubblesData: data.bubbleData
564
+ transitionDuration: data.transitionDuration
565
+ })
566
+ }),
567
+ shareReplay(1)
568
+ )
569
+
570
+ const highlightTarget$ = fullChartParams$.pipe(
571
+ takeUntil(destroy$),
572
+ map(d => d.highlightTarget),
573
+ distinctUntilChanged()
574
+ )
575
+
576
+ combineLatest({
577
+ graphicSelection: graphicSelection$,
578
+ computedData: computedData$,
579
+ CategoryDataMap: CategoryDataMap$,
580
+ valueLabels: valueLabels$,
581
+ highlightTarget: highlightTarget$,
582
+ fullDataFormatter: fullDataFormatter$,
583
+ }).pipe(
584
+ takeUntil(destroy$),
585
+ switchMap(async (d) => d),
586
+ ).subscribe(data => {
587
+
588
+ data.graphicSelection
589
+ .on('mouseover', (event, valueDatum) => {
590
+ // event.stopPropagation()
591
+
592
+ // reference 到資料本身
593
+ const datum = valueDatum._refDatum
594
+
595
+ // 只顯示目前的值
596
+ // datum._visibleValue = [datum.value[valueDatum.index]]
597
+ // 只顯示總數
598
+ // datum._visibleValue = [datum.sum]
599
+
600
+ event$.next({
601
+ type: 'multiValue',
602
+ eventName: 'mouseover',
603
+ pluginName,
604
+ highlightTarget: data.highlightTarget,
605
+ valueDetail: [
606
+ {
607
+ value: datum.value[valueDatum.valueIndex],
608
+ valueIndex: valueDatum.valueIndex,
609
+ valueLabel: data.valueLabels[valueDatum.valueIndex]
610
+ },
611
+ ],
612
+ datum: datum,
613
+ category: data.CategoryDataMap.get(datum.categoryLabel)!,
614
+ categoryIndex: datum.categoryIndex,
615
+ categoryLabel: datum.categoryLabel,
616
+ data: data.computedData,
617
+ event,
618
+ })
619
+ })
620
+ .on('mousemove', (event, valueDatum) => {
621
+ // event.stopPropagation()
622
+
623
+ // reference 到資料本身
624
+ const datum = valueDatum._refDatum
625
+
626
+ // 只顯示目前的值
627
+ // datum._visibleValue = [datum.value[valueDatum.index]]
628
+ // 只顯示總數
629
+ // datum._visibleValue = [datum.sum]
630
+ event$.next({
631
+ type: 'multiValue',
632
+ eventName: 'mousemove',
633
+ pluginName,
634
+ highlightTarget: data.highlightTarget,
635
+ valueDetail: [
636
+ {
637
+ value: datum.value[valueDatum.valueIndex],
638
+ valueIndex: valueDatum.valueIndex,
639
+ valueLabel: data.valueLabels[valueDatum.valueIndex]
640
+ },
641
+ ],
642
+ datum,
643
+ category: data.CategoryDataMap.get(datum.categoryLabel)!,
644
+ categoryIndex: datum.categoryIndex,
645
+ categoryLabel: datum.categoryLabel,
646
+ data: data.computedData,
647
+ event,
648
+ })
649
+ })
650
+ .on('mouseout', (event, valueDatum) => {
651
+ // event.stopPropagation()
652
+
653
+ // reference 到資料本身
654
+ const datum = valueDatum._refDatum
655
+
656
+ event$.next({
657
+ type: 'multiValue',
658
+ eventName: 'mouseout',
659
+ pluginName,
660
+ highlightTarget: data.highlightTarget,
661
+ valueDetail: [
662
+ {
663
+ value: datum.value[valueDatum.valueIndex],
664
+ valueIndex: valueDatum.valueIndex,
665
+ valueLabel: data.valueLabels[valueDatum.valueIndex]
666
+ },
667
+ ],
668
+ datum,
669
+ category: data.CategoryDataMap.get(datum.categoryLabel)!,
670
+ categoryIndex: datum.categoryIndex,
671
+ categoryLabel: datum.categoryLabel,
672
+ data: data.computedData,
673
+ event,
674
+ })
675
+ })
676
+ .on('click', (event, valueDatum) => {
677
+ // event.stopPropagation()
678
+
679
+ // reference 到資料本身
680
+ const datum = valueDatum._refDatum
681
+
682
+ // 只顯示目前的值
683
+ // datum._visibleValue = [datum.value[valueDatum.index]]
684
+ // 只顯示總數
685
+ // datum._visibleValue = [datum.sum]
686
+
687
+ event$.next({
688
+ type: 'multiValue',
689
+ eventName: 'click',
690
+ pluginName,
691
+ highlightTarget: data.highlightTarget,
692
+ valueDetail: [
693
+ {
694
+ value: datum.value[valueDatum.valueIndex],
695
+ valueIndex: valueDatum.valueIndex,
696
+ valueLabel: data.valueLabels[valueDatum.valueIndex]
697
+ },
698
+ ],
699
+ datum,
700
+ category: data.CategoryDataMap.get(datum.categoryLabel)!,
701
+ categoryIndex: datum.categoryIndex,
702
+ categoryLabel: datum.categoryLabel,
703
+ data: data.computedData,
704
+ event,
705
+ })
706
+ })
707
+
708
+ })
709
+
710
+ combineLatest({
711
+ graphicSelection: graphicSelection$,
712
+ highlight: highlight$.pipe(
713
+ map(data => data.map(d => d.id))
714
+ ),
715
+ fullChartParams: fullChartParams$
716
+ }).pipe(
717
+ takeUntil(destroy$),
718
+ switchMap(async d => d)
719
+ ).subscribe(data => {
720
+ highlight({
721
+ selection: data.graphicSelection,
722
+ ids: data.highlight,
723
+ fullChartParams: data.fullChartParams
724
+ })
725
+ })
726
+
727
+ return () => {
728
+ destroy$.next(undefined)
729
+ }
730
730
  }