@orbcharts/plugins-basic 3.0.3 → 3.0.5

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 (117) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +2486 -2453
  3. package/dist/orbcharts-plugins-basic.umd.js +44 -44
  4. package/dist/src/series/seriesObservables.d.ts +5 -7
  5. package/lib/core-types.ts +7 -7
  6. package/lib/core.ts +6 -6
  7. package/lib/gridObservables.ts +6 -6
  8. package/lib/plugins-basic-types.ts +6 -6
  9. package/package.json +48 -48
  10. package/src/base/BaseBars.ts +765 -765
  11. package/src/base/BaseBarsTriangle.ts +676 -676
  12. package/src/base/BaseDots.ts +464 -464
  13. package/src/base/BaseGroupAxis.ts +691 -691
  14. package/src/base/BaseLegend.ts +684 -684
  15. package/src/base/BaseLineAreas.ts +629 -629
  16. package/src/base/BaseLines.ts +706 -706
  17. package/src/base/BaseOrdinalBubbles.ts +729 -727
  18. package/src/base/BaseRacingBars.ts +582 -582
  19. package/src/base/BaseRacingLabels.ts +404 -404
  20. package/src/base/BaseRacingValueLabels.ts +403 -403
  21. package/src/base/BaseStackedBars.ts +782 -782
  22. package/src/base/BaseTooltip.ts +386 -386
  23. package/src/base/BaseValueAxis.ts +600 -600
  24. package/src/base/BaseXAxis.ts +427 -427
  25. package/src/base/BaseXZoom.ts +241 -241
  26. package/src/base/BaseYAxis.ts +389 -389
  27. package/src/base/types.ts +2 -2
  28. package/src/const.ts +30 -30
  29. package/src/grid/defaults.ts +213 -213
  30. package/src/grid/gridObservables.ts +635 -635
  31. package/src/grid/index.ts +16 -16
  32. package/src/grid/plugins/Bars.ts +69 -69
  33. package/src/grid/plugins/BarsPN.ts +66 -66
  34. package/src/grid/plugins/BarsTriangle.ts +73 -73
  35. package/src/grid/plugins/Dots.ts +68 -68
  36. package/src/grid/plugins/GridLegend.ts +107 -107
  37. package/src/grid/plugins/GridTooltip.ts +66 -66
  38. package/src/grid/plugins/GroupAux.ts +1095 -1095
  39. package/src/grid/plugins/GroupAxis.ts +73 -73
  40. package/src/grid/plugins/GroupZoom.ts +218 -218
  41. package/src/grid/plugins/LineAreas.ts +65 -65
  42. package/src/grid/plugins/Lines.ts +59 -59
  43. package/src/grid/plugins/StackedBars.ts +64 -64
  44. package/src/grid/plugins/StackedValueAxis.ts +96 -96
  45. package/src/grid/plugins/ValueAxis.ts +94 -94
  46. package/src/index.ts +6 -6
  47. package/src/multiGrid/defaults.ts +244 -244
  48. package/src/multiGrid/index.ts +14 -14
  49. package/src/multiGrid/multiGridObservables.ts +50 -50
  50. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  51. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  52. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  53. package/src/multiGrid/plugins/MultiGridLegend.ts +169 -169
  54. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  55. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  56. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  57. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  58. package/src/multiGrid/plugins/MultiStackedBars.ts +106 -106
  59. package/src/multiGrid/plugins/MultiStackedValueAxis.ts +134 -134
  60. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  61. package/src/multiGrid/plugins/OverlappingStackedValueAxes.ts +300 -300
  62. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  63. package/src/multiValue/defaults.ts +523 -523
  64. package/src/multiValue/index.ts +16 -16
  65. package/src/multiValue/multiValueObservables.ts +781 -781
  66. package/src/multiValue/plugins/MultiValueLegend.ts +107 -107
  67. package/src/multiValue/plugins/MultiValueTooltip.ts +66 -66
  68. package/src/multiValue/plugins/OrdinalAux.ts +660 -660
  69. package/src/multiValue/plugins/OrdinalAxis.ts +524 -524
  70. package/src/multiValue/plugins/OrdinalBubbles.ts +226 -226
  71. package/src/multiValue/plugins/OrdinalZoom.ts +57 -57
  72. package/src/multiValue/plugins/RacingBars.ts +375 -375
  73. package/src/multiValue/plugins/RacingCounterTexts.ts +300 -300
  74. package/src/multiValue/plugins/RacingValueAxis.ts +114 -114
  75. package/src/multiValue/plugins/Scatter.ts +486 -486
  76. package/src/multiValue/plugins/ScatterBubbles.ts +635 -635
  77. package/src/multiValue/plugins/XAxis.ts +107 -107
  78. package/src/multiValue/plugins/XYAux.ts +683 -683
  79. package/src/multiValue/plugins/XYAxes.ts +194 -194
  80. package/src/multiValue/plugins/XYAxes_legacy.ts +683 -683
  81. package/src/multiValue/plugins/XZoom.ts +40 -40
  82. package/src/noneData/defaults.ts +102 -102
  83. package/src/noneData/index.ts +3 -3
  84. package/src/noneData/plugins/Container.ts +27 -27
  85. package/src/noneData/plugins/Tooltip.ts +373 -373
  86. package/src/relationship/defaults.ts +221 -221
  87. package/src/relationship/index.ts +5 -5
  88. package/src/relationship/plugins/ForceDirected.ts +1056 -1173
  89. package/src/relationship/plugins/ForceDirectedBubbles.ts +1294 -1411
  90. package/src/relationship/plugins/RelationshipLegend.ts +100 -100
  91. package/src/relationship/plugins/RelationshipTooltip.ts +66 -66
  92. package/src/relationship/relationshipObservables.ts +49 -49
  93. package/src/series/defaults.ts +222 -221
  94. package/src/series/index.ts +9 -9
  95. package/src/series/plugins/Bubbles.ts +766 -636
  96. package/src/series/plugins/Pie.ts +622 -623
  97. package/src/series/plugins/PieEventTexts.ts +283 -284
  98. package/src/series/plugins/PieLabels.ts +639 -640
  99. package/src/series/plugins/Rose.ts +515 -516
  100. package/src/series/plugins/RoseLabels.ts +599 -600
  101. package/src/series/plugins/SeriesLegend.ts +107 -107
  102. package/src/series/plugins/SeriesTooltip.ts +66 -66
  103. package/src/series/seriesObservables.ts +168 -145
  104. package/src/series/seriesUtils.ts +51 -51
  105. package/src/tree/defaults.ts +102 -102
  106. package/src/tree/index.ts +4 -4
  107. package/src/tree/plugins/TreeLegend.ts +100 -100
  108. package/src/tree/plugins/TreeMap.ts +341 -341
  109. package/src/tree/plugins/TreeTooltip.ts +66 -66
  110. package/src/utils/commonUtils.ts +31 -31
  111. package/src/utils/d3Graphics.ts +176 -176
  112. package/src/utils/d3Utils.ts +92 -92
  113. package/src/utils/observables.ts +14 -14
  114. package/src/utils/orbchartsUtils.ts +129 -129
  115. package/tsconfig.base.json +13 -13
  116. package/tsconfig.json +2 -2
  117. package/vite.config.js +22 -22
@@ -1,728 +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
- d.graphicValue = graphicValue
502
- // d._visibleValue = [] // highlight的時候才寫入
503
- return d
504
- })
505
- })
506
- })
507
- )
508
-
509
- const transitionDuration$ = fullChartParams$.pipe(
510
- takeUntil(destroy$),
511
- map(d => d.transitionDuration),
512
- distinctUntilChanged()
513
- )
514
-
515
- const transitionEase$ = fullChartParams$.pipe(
516
- takeUntil(destroy$),
517
- map(d => d.transitionEase),
518
- distinctUntilChanged()
519
- )
520
-
521
- const graphicGSelection$ = combineLatest({
522
- containerSelection: containerSelection$,
523
- bubbleData: bubbleData$,
524
- rankingScaleList: rankingScaleList$,
525
- transitionDuration: transitionDuration$,
526
- transitionEase: transitionEase$,
527
- ordinalPadding: ordinalPadding$
528
- }).pipe(
529
- takeUntil(destroy$),
530
- switchMap(async (d) => d),
531
- map(data => {
532
- // console.log('bubbleData', data.bubbleData)
533
- return renderGraphicG({
534
- containerSelection: data.containerSelection,
535
- paddingGClassName,
536
- itemGClassName,
537
- bubbleData: data.bubbleData,
538
- // rankingScaleList: data.rankingScaleList,
539
- transitionDuration: data.transitionDuration,
540
- transitionEase: data.transitionEase,
541
- ordinalPadding: data.ordinalPadding
542
- })
543
- })
544
- )
545
-
546
- const graphicSelection$ = combineLatest({
547
- graphicGSelection: graphicGSelection$,
548
- // xyValueIndex: xyValueIndex$,
549
- ordinalScale: ordinalScale$,
550
- bubbleData: bubbleData$,
551
- transitionDuration: transitionDuration$,
552
- fullParams: fullParams$,
553
- }).pipe(
554
- takeUntil(destroy$),
555
- switchMap(async (d) => d),
556
- map(data => {
557
-
558
- return renderBubbles({
559
- graphicGSelection: data.graphicGSelection,
560
- // bubblesData: data.bubbleData
561
- transitionDuration: data.transitionDuration
562
- })
563
- }),
564
- shareReplay(1)
565
- )
566
-
567
- const highlightTarget$ = fullChartParams$.pipe(
568
- takeUntil(destroy$),
569
- map(d => d.highlightTarget),
570
- distinctUntilChanged()
571
- )
572
-
573
- combineLatest({
574
- graphicSelection: graphicSelection$,
575
- computedData: computedData$,
576
- CategoryDataMap: CategoryDataMap$,
577
- valueLabels: valueLabels$,
578
- highlightTarget: highlightTarget$,
579
- fullDataFormatter: fullDataFormatter$,
580
- }).pipe(
581
- takeUntil(destroy$),
582
- switchMap(async (d) => d),
583
- ).subscribe(data => {
584
-
585
- data.graphicSelection
586
- .on('mouseover', (event, valueDatum) => {
587
- // event.stopPropagation()
588
-
589
- // reference 到資料本身
590
- const datum = valueDatum._refDatum
591
-
592
- // 只顯示目前的值
593
- // datum._visibleValue = [datum.value[valueDatum.index]]
594
- // 只顯示總數
595
- // datum._visibleValue = [datum.sum]
596
-
597
- event$.next({
598
- type: 'multiValue',
599
- eventName: 'mouseover',
600
- pluginName,
601
- highlightTarget: data.highlightTarget,
602
- valueDetail: [
603
- {
604
- value: datum.value[valueDatum.valueIndex],
605
- valueIndex: valueDatum.valueIndex,
606
- valueLabel: data.valueLabels[valueDatum.valueIndex]
607
- },
608
- ],
609
- datum: datum,
610
- category: data.CategoryDataMap.get(datum.categoryLabel)!,
611
- categoryIndex: datum.categoryIndex,
612
- categoryLabel: datum.categoryLabel,
613
- data: data.computedData,
614
- event,
615
- })
616
- })
617
- .on('mousemove', (event, valueDatum) => {
618
- // event.stopPropagation()
619
-
620
- // reference 到資料本身
621
- const datum = valueDatum._refDatum
622
-
623
- // 只顯示目前的值
624
- // datum._visibleValue = [datum.value[valueDatum.index]]
625
- // 只顯示總數
626
- // datum._visibleValue = [datum.sum]
627
-
628
- event$.next({
629
- type: 'multiValue',
630
- eventName: 'mousemove',
631
- pluginName,
632
- highlightTarget: data.highlightTarget,
633
- valueDetail: [
634
- {
635
- value: datum.value[valueDatum.valueIndex],
636
- valueIndex: valueDatum.valueIndex,
637
- valueLabel: data.valueLabels[valueDatum.valueIndex]
638
- },
639
- ],
640
- datum,
641
- category: data.CategoryDataMap.get(datum.categoryLabel)!,
642
- categoryIndex: datum.categoryIndex,
643
- categoryLabel: datum.categoryLabel,
644
- data: data.computedData,
645
- event,
646
- })
647
- })
648
- .on('mouseout', (event, valueDatum) => {
649
- // event.stopPropagation()
650
-
651
- // reference 到資料本身
652
- const datum = valueDatum._refDatum
653
-
654
- event$.next({
655
- type: 'multiValue',
656
- eventName: 'mouseout',
657
- pluginName,
658
- highlightTarget: data.highlightTarget,
659
- valueDetail: [
660
- {
661
- value: datum.value[valueDatum.valueIndex],
662
- valueIndex: valueDatum.valueIndex,
663
- valueLabel: data.valueLabels[valueDatum.valueIndex]
664
- },
665
- ],
666
- datum,
667
- category: data.CategoryDataMap.get(datum.categoryLabel)!,
668
- categoryIndex: datum.categoryIndex,
669
- categoryLabel: datum.categoryLabel,
670
- data: data.computedData,
671
- event,
672
- })
673
- })
674
- .on('click', (event, valueDatum) => {
675
- // event.stopPropagation()
676
-
677
- // reference 到資料本身
678
- const datum = valueDatum._refDatum
679
-
680
- // 只顯示目前的值
681
- // datum._visibleValue = [datum.value[valueDatum.index]]
682
- // 只顯示總數
683
- // datum._visibleValue = [datum.sum]
684
-
685
- event$.next({
686
- type: 'multiValue',
687
- eventName: 'click',
688
- pluginName,
689
- highlightTarget: data.highlightTarget,
690
- valueDetail: [
691
- {
692
- value: datum.value[valueDatum.valueIndex],
693
- valueIndex: valueDatum.valueIndex,
694
- valueLabel: data.valueLabels[valueDatum.valueIndex]
695
- },
696
- ],
697
- datum,
698
- category: data.CategoryDataMap.get(datum.categoryLabel)!,
699
- categoryIndex: datum.categoryIndex,
700
- categoryLabel: datum.categoryLabel,
701
- data: data.computedData,
702
- event,
703
- })
704
- })
705
-
706
- })
707
-
708
- combineLatest({
709
- graphicSelection: graphicSelection$,
710
- highlight: highlight$.pipe(
711
- map(data => data.map(d => d.id))
712
- ),
713
- fullChartParams: fullChartParams$
714
- }).pipe(
715
- takeUntil(destroy$),
716
- switchMap(async d => d)
717
- ).subscribe(data => {
718
- highlight({
719
- selection: data.graphicSelection,
720
- ids: data.highlight,
721
- fullChartParams: data.fullChartParams
722
- })
723
- })
724
-
725
- return () => {
726
- destroy$.next(undefined)
727
- }
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
+ }
728
730
  }