@orbcharts/plugins-basic 3.0.0-alpha.45 → 3.0.0-alpha.46

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +182 -159
  3. package/dist/orbcharts-plugins-basic.umd.js +7 -7
  4. package/dist/src/index.d.ts +5 -1
  5. package/dist/{orbcharts-plugins-basic/src → src}/series/seriesObservables.d.ts +4 -4
  6. package/package.json +42 -42
  7. package/src/base/BaseBarStack.ts +778 -778
  8. package/src/base/BaseBars.ts +764 -764
  9. package/src/base/BaseBarsTriangle.ts +672 -672
  10. package/src/base/BaseDots.ts +502 -502
  11. package/src/base/BaseGroupAxis.ts +496 -496
  12. package/src/base/BaseLegend.ts +641 -641
  13. package/src/base/BaseLineAreas.ts +625 -625
  14. package/src/base/BaseLines.ts +699 -699
  15. package/src/base/BaseValueAxis.ts +478 -478
  16. package/src/base/types.ts +2 -2
  17. package/src/grid/defaults.ts +121 -121
  18. package/src/grid/gridObservables.ts +247 -247
  19. package/src/grid/index.ts +15 -15
  20. package/src/grid/plugins/BarStack.ts +50 -50
  21. package/src/grid/plugins/Bars.ts +51 -51
  22. package/src/grid/plugins/BarsDiverging.ts +41 -41
  23. package/src/grid/plugins/BarsTriangle.ts +50 -50
  24. package/src/grid/plugins/Dots.ts +37 -37
  25. package/src/grid/plugins/GridLegend.ts +59 -59
  26. package/src/grid/plugins/GroupAux.ts +645 -645
  27. package/src/grid/plugins/GroupAxis.ts +42 -42
  28. package/src/grid/plugins/LineAreas.ts +39 -39
  29. package/src/grid/plugins/Lines.ts +38 -38
  30. package/src/grid/plugins/ScalingArea.ts +173 -173
  31. package/src/grid/plugins/ValueAxis.ts +43 -43
  32. package/src/grid/plugins/ValueStackAxis.ts +79 -79
  33. package/src/grid/types.ts +120 -120
  34. package/src/index.ts +9 -9
  35. package/src/multiGrid/defaults.ts +147 -147
  36. package/src/multiGrid/index.ts +11 -11
  37. package/src/multiGrid/multiGridObservables.ts +42 -42
  38. package/src/multiGrid/plugins/MultiBarStack.ts +74 -74
  39. package/src/multiGrid/plugins/MultiBars.ts +73 -73
  40. package/src/multiGrid/plugins/MultiBarsTriangle.ts +73 -73
  41. package/src/multiGrid/plugins/MultiDots.ts +60 -60
  42. package/src/multiGrid/plugins/MultiGridLegend.ts +89 -89
  43. package/src/multiGrid/plugins/MultiGroupAxis.ts +65 -65
  44. package/src/multiGrid/plugins/MultiLineAreas.ts +62 -62
  45. package/src/multiGrid/plugins/MultiLines.ts +61 -61
  46. package/src/multiGrid/plugins/MultiValueAxis.ts +65 -65
  47. package/src/multiGrid/plugins/OverlappingValueAxes.ts +169 -169
  48. package/src/multiGrid/types.ts +67 -67
  49. package/src/noneData/defaults.ts +64 -64
  50. package/src/noneData/index.ts +3 -3
  51. package/src/noneData/plugins/Container.ts +10 -10
  52. package/src/noneData/plugins/Tooltip.ts +310 -310
  53. package/src/noneData/types.ts +26 -26
  54. package/src/series/defaults.ts +109 -109
  55. package/src/series/index.ts +6 -6
  56. package/src/series/plugins/Bubbles.ts +602 -571
  57. package/src/series/plugins/Pie.ts +555 -548
  58. package/src/series/plugins/PieEventTexts.ts +258 -258
  59. package/src/series/plugins/PieLabels.ts +335 -335
  60. package/src/series/plugins/SeriesLegend.ts +59 -59
  61. package/src/series/seriesObservables.ts +145 -145
  62. package/src/series/seriesUtils.ts +50 -50
  63. package/src/series/types.ts +67 -67
  64. package/src/tree/defaults.ts +22 -22
  65. package/src/tree/index.ts +3 -3
  66. package/src/tree/plugins/TreeLegend.ts +59 -59
  67. package/src/tree/plugins/TreeMap.ts +305 -305
  68. package/src/tree/types.ts +23 -23
  69. package/src/utils/commonUtils.ts +21 -21
  70. package/src/utils/d3Graphics.ts +124 -124
  71. package/src/utils/d3Utils.ts +73 -73
  72. package/src/utils/observables.ts +14 -14
  73. package/src/utils/orbchartsUtils.ts +100 -100
  74. package/tsconfig.dev.json +16 -16
  75. package/tsconfig.json +13 -16
  76. package/tsconfig.prod.json +13 -13
  77. package/vite.config.js +49 -49
  78. package/dist/orbcharts-plugins-basic/src/index.d.ts +0 -5
  79. /package/dist/{orbcharts-plugins-basic/src → src}/base/BaseBarStack.d.ts +0 -0
  80. /package/dist/{orbcharts-plugins-basic/src → src}/base/BaseBars.d.ts +0 -0
  81. /package/dist/{orbcharts-plugins-basic/src → src}/base/BaseBarsTriangle.d.ts +0 -0
  82. /package/dist/{orbcharts-plugins-basic/src → src}/base/BaseDots.d.ts +0 -0
  83. /package/dist/{orbcharts-plugins-basic/src → src}/base/BaseGroupArea.d.ts +0 -0
  84. /package/dist/{orbcharts-plugins-basic/src → src}/base/BaseGroupAxis.d.ts +0 -0
  85. /package/dist/{orbcharts-plugins-basic/src → src}/base/BaseLegend.d.ts +0 -0
  86. /package/dist/{orbcharts-plugins-basic/src → src}/base/BaseLineAreas.d.ts +0 -0
  87. /package/dist/{orbcharts-plugins-basic/src → src}/base/BaseLines.d.ts +0 -0
  88. /package/dist/{orbcharts-plugins-basic/src → src}/base/BaseValueAxis.d.ts +0 -0
  89. /package/dist/{orbcharts-plugins-basic/src → src}/base/types.d.ts +0 -0
  90. /package/dist/{orbcharts-plugins-basic/src → src}/grid/defaults.d.ts +0 -0
  91. /package/dist/{orbcharts-plugins-basic/src → src}/grid/gridObservables.d.ts +0 -0
  92. /package/dist/{orbcharts-plugins-basic/src → src}/grid/index.d.ts +0 -0
  93. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/BarStack.d.ts +0 -0
  94. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/Bars.d.ts +0 -0
  95. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/BarsDiverging.d.ts +0 -0
  96. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/BarsTriangle.d.ts +0 -0
  97. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/Dots.d.ts +0 -0
  98. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/GridLegend.d.ts +0 -0
  99. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/GroupAux.d.ts +0 -0
  100. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/GroupAxis.d.ts +0 -0
  101. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/LineAreas.d.ts +0 -0
  102. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/Lines.d.ts +0 -0
  103. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/Ranking.d.ts +0 -0
  104. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/RankingAxis.d.ts +0 -0
  105. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/ScalingArea.d.ts +0 -0
  106. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/ValueAxis.d.ts +0 -0
  107. /package/dist/{orbcharts-plugins-basic/src → src}/grid/plugins/ValueStackAxis.d.ts +0 -0
  108. /package/dist/{orbcharts-plugins-basic/src → src}/grid/types.d.ts +0 -0
  109. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/defaults.d.ts +0 -0
  110. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/index.d.ts +0 -0
  111. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/multiGridObservables.d.ts +0 -0
  112. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/plugins/MultiBarStack.d.ts +0 -0
  113. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/plugins/MultiBars.d.ts +0 -0
  114. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/plugins/MultiBarsTriangle.d.ts +0 -0
  115. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/plugins/MultiDots.d.ts +0 -0
  116. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/plugins/MultiGridLegend.d.ts +0 -0
  117. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/plugins/MultiGroupAxis.d.ts +0 -0
  118. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/plugins/MultiLineAreas.d.ts +0 -0
  119. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/plugins/MultiLines.d.ts +0 -0
  120. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/plugins/MultiValueAxis.d.ts +0 -0
  121. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/plugins/OverlappingValueAxes.d.ts +0 -0
  122. /package/dist/{orbcharts-plugins-basic/src → src}/multiGrid/types.d.ts +0 -0
  123. /package/dist/{orbcharts-plugins-basic/src → src}/multiValue/index.d.ts +0 -0
  124. /package/dist/{orbcharts-plugins-basic/src → src}/multiValue/plugins/Scatter.d.ts +0 -0
  125. /package/dist/{orbcharts-plugins-basic/src → src}/multiValue/plugins/ScatterAxes.d.ts +0 -0
  126. /package/dist/{orbcharts-plugins-basic/src → src}/noneData/defaults.d.ts +0 -0
  127. /package/dist/{orbcharts-plugins-basic/src → src}/noneData/index.d.ts +0 -0
  128. /package/dist/{orbcharts-plugins-basic/src → src}/noneData/plugins/Container.d.ts +0 -0
  129. /package/dist/{orbcharts-plugins-basic/src → src}/noneData/plugins/Tooltip.d.ts +0 -0
  130. /package/dist/{orbcharts-plugins-basic/src → src}/noneData/types.d.ts +0 -0
  131. /package/dist/{orbcharts-plugins-basic/src → src}/relationship/index.d.ts +0 -0
  132. /package/dist/{orbcharts-plugins-basic/src → src}/relationship/plugins/Relationship.d.ts +0 -0
  133. /package/dist/{orbcharts-plugins-basic/src → src}/series/defaults.d.ts +0 -0
  134. /package/dist/{orbcharts-plugins-basic/src → src}/series/index.d.ts +0 -0
  135. /package/dist/{orbcharts-plugins-basic/src → src}/series/plugins/Bubbles.d.ts +0 -0
  136. /package/dist/{orbcharts-plugins-basic/src → src}/series/plugins/Pie.d.ts +0 -0
  137. /package/dist/{orbcharts-plugins-basic/src → src}/series/plugins/PieEventTexts.d.ts +0 -0
  138. /package/dist/{orbcharts-plugins-basic/src → src}/series/plugins/PieLabels.d.ts +0 -0
  139. /package/dist/{orbcharts-plugins-basic/src → src}/series/plugins/SeriesLegend.d.ts +0 -0
  140. /package/dist/{orbcharts-plugins-basic/src → src}/series/plugins/Waffle.d.ts +0 -0
  141. /package/dist/{orbcharts-plugins-basic/src → src}/series/seriesUtils.d.ts +0 -0
  142. /package/dist/{orbcharts-plugins-basic/src → src}/series/types.d.ts +0 -0
  143. /package/dist/{orbcharts-plugins-basic/src → src}/tree/defaults.d.ts +0 -0
  144. /package/dist/{orbcharts-plugins-basic/src → src}/tree/index.d.ts +0 -0
  145. /package/dist/{orbcharts-plugins-basic/src → src}/tree/plugins/TreeLegend.d.ts +0 -0
  146. /package/dist/{orbcharts-plugins-basic/src → src}/tree/plugins/TreeMap.d.ts +0 -0
  147. /package/dist/{orbcharts-plugins-basic/src → src}/tree/types.d.ts +0 -0
  148. /package/dist/{orbcharts-plugins-basic/src → src}/utils/commonUtils.d.ts +0 -0
  149. /package/dist/{orbcharts-plugins-basic/src → src}/utils/d3Graphics.d.ts +0 -0
  150. /package/dist/{orbcharts-plugins-basic/src → src}/utils/d3Utils.d.ts +0 -0
  151. /package/dist/{orbcharts-plugins-basic/src → src}/utils/observables.d.ts +0 -0
  152. /package/dist/{orbcharts-plugins-basic/src → src}/utils/orbchartsUtils.d.ts +0 -0
  153. /package/dist/{orbcharts-plugins-basic/vite.config.d.ts → vite.config.d.ts} +0 -0
@@ -1,572 +1,603 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- map,
5
- switchMap,
6
- first,
7
- takeUntil,
8
- Subject,
9
- Observable,
10
- distinctUntilChanged} from 'rxjs'
11
- import type {
12
- ChartParams,
13
- DatumValue,
14
- DataSeries,
15
- EventName,
16
- ComputedDataSeries,
17
- ComputedDatumSeries,
18
- SeriesContainerPosition } from '@orbcharts/core'
19
- import {
20
- defineSeriesPlugin } from '@orbcharts/core'
21
- import type { BubblesParams, BubbleScaleType } from '../types'
22
- import { DEFAULT_BUBBLES_PARAMS } from '../defaults'
23
- import { renderCircleText } from '../../utils/d3Graphics'
24
-
25
- interface BubblesDatum extends ComputedDatumSeries {
26
- x: number
27
- y: number
28
- r: number
29
- _originR: number // 紀錄變化前的r
30
- }
31
-
32
- type BubblesSimulationDatum = BubblesDatum & d3.SimulationNodeDatum
33
-
34
- let force: d3.Simulation<d3.SimulationNodeDatum, undefined> | undefined
35
-
36
- function makeForce (bubblesSelection: d3.Selection<SVGGElement, any, any, any>, fullParams: BubblesParams) {
37
- return d3.forceSimulation()
38
- .velocityDecay(fullParams.force!.velocityDecay!)
39
- // .alphaDecay(0.2)
40
- .force(
41
- "collision",
42
- d3.forceCollide()
43
- .radius(d => {
44
- // @ts-ignore
45
- return d.r + fullParams.force!.collisionSpacing
46
- })
47
- // .strength(0.01)
48
- )
49
- .force("charge", d3.forceManyBody().strength((d) => {
50
- // @ts-ignore
51
- return - Math.pow(d.r, 2.0) * fullParams.force!.strength
52
- }))
53
- // .force("x", d3.forceX().strength(forceStrength).x(this.graphicWidth / 2))
54
- // .force("y", d3.forceY().strength(forceStrength).y(this.graphicHeight / 2))
55
- .on("tick", () => {
56
- // if (!bubblesSelection) {
57
- // return
58
- // }
59
- bubblesSelection
60
- .attr("transform", (d) => {
61
- return `translate(${d.x},${d.y})`
62
- })
63
- // .attr("cx", (d) => d.x)
64
- // .attr("cy", (d) => d.y)
65
- })
66
- }
67
-
68
-
69
- // 計算最大泡泡的半徑
70
- function getMaxR ({ data, bubbleGroupR, maxValue, avgValue }: {
71
- data: DatumValue[]
72
- bubbleGroupR: number
73
- maxValue: number
74
- avgValue: number
75
- }) {
76
- // 平均r(假想是正方型來計算的,比如說大正方型裡有4個正方型,則 r = width/Math.sqrt(4)/2)
77
- const avgR = bubbleGroupR / Math.sqrt(data.length)
78
- const avgSize = avgR * avgR * Math.PI
79
- const sizeRate = avgSize / avgValue
80
- const maxSize = maxValue * sizeRate
81
- const maxR = Math.pow(maxSize / Math.PI, 0.5)
82
-
83
- const modifier = 0.75 // @Q@ 因為以下公式是假設泡泡是正方型來計算,所以畫出來的圖會偏大一些,這個數值是用來修正用的
84
- return maxR * modifier
85
- }
86
-
87
- function createBubblesData ({ data, LastBubbleDataMap, graphicWidth, graphicHeight, SeriesContainerPositionMap, scaleType }: {
88
- data: ComputedDataSeries
89
- LastBubbleDataMap: Map<string, BubblesDatum>
90
- graphicWidth: number
91
- graphicHeight: number
92
- SeriesContainerPositionMap: Map<string, SeriesContainerPosition>
93
- scaleType: BubbleScaleType
94
- // highlightIds: string[]
95
- }) {
96
- const bubbleGroupR = Math.min(...[graphicWidth, graphicHeight]) / 2
97
-
98
- const filteredData = data
99
- .flat()
100
- .filter(_d => _d.value != null && _d.visible != false)
101
-
102
- const maxValue = Math.max(
103
- ...filteredData.map(_d => _d.value!)
104
- )
105
-
106
- const avgValue = (
107
- filteredData.reduce((prev, current) => prev + (current.value ?? 0), 0)
108
- ) / filteredData.length
109
-
110
- const maxR = getMaxR({ data: filteredData, bubbleGroupR, maxValue, avgValue })
111
-
112
- const exponent = scaleType === 'area'
113
- ? 0.5 // 比例映射面積(0.5為取平方根)
114
- : 1 // 比例映射半徑
115
-
116
- const scaleBubbleR = d3.scalePow()
117
- .domain([0, maxValue])
118
- .range([0, maxR])
119
- .exponent(exponent)
120
-
121
- const bubbleData: BubblesDatum[] = filteredData.map((_d) => {
122
- const d: BubblesDatum = _d as BubblesDatum
123
-
124
- const existDatum = LastBubbleDataMap.get(_d.id)
125
-
126
- if (existDatum) {
127
- // 使用現有的座標
128
- d.x = existDatum.x
129
- d.y = existDatum.y
130
- } else {
131
- const seriesContainerPosition = SeriesContainerPositionMap.get(d.seriesLabel)!
132
- d.x = Math.random() * seriesContainerPosition.width
133
- d.y = Math.random() * seriesContainerPosition.height
134
- }
135
- const r = scaleBubbleR!(d.value ?? 0)!
136
- d.r = r
137
- d._originR = r
138
-
139
- return d
140
- })
141
-
142
- return bubbleData
143
- }
144
-
145
- function renderBubbles ({ graphicSelection, bubblesData, fullParams }: {
146
- graphicSelection: d3.Selection<SVGGElement, any, any, any>
147
- bubblesData: BubblesDatum[]
148
- fullParams: BubblesParams
149
- }) {
150
- let update = graphicSelection.selectAll<SVGGElement, BubblesDatum>("g")
151
- .data(bubblesData, (d) => d.id)
152
- let enter = update.enter()
153
- .append<SVGGElement>("g")
154
- .attr('cursor', 'pointer')
155
- enter
156
- .attr('font-size', 12)
157
- .style('fill', '#ffffff')
158
- .attr("text-anchor", "middle")
159
- .attr("transform", (d) => {
160
- return `translate(${d.x},${d.y})`
161
- })
162
- // .attr("cx", (d) => d.x)
163
- // .attr("cy", (d) => d.y)
164
-
165
- enter
166
- .append("circle")
167
- .attr("class", "node")
168
- // update.merge(enter)
169
- .attr("cx", 0)
170
- .attr("cy", 0)
171
- // .attr("r", 1e-6)
172
- .attr('fill', (d) => d.color)
173
- // .transition()
174
- // .duration(500)
175
-
176
- enter
177
- .append('text')
178
- .style('opacity', 0.8)
179
- .attr('pointer-events', 'none')
180
-
181
- update.exit().remove()
182
-
183
- const bubblesSelection = update.merge(enter)
184
-
185
- bubblesSelection.select('circle')
186
- .transition()
187
- .duration(200)
188
- .attr("r", (d) => d.r)
189
- .attr('fill', (d) => d.color)
190
- bubblesSelection
191
- .each((d,i,g) => {
192
- const gSelection = d3.select(g[i])
193
- let breakAll = true
194
- if (d.label.length <= fullParams.bubbleText.lineLengthMin) {
195
- breakAll = false
196
- }
197
- gSelection.call(renderCircleText, {
198
- text: d.label,
199
- radius: d.r * fullParams.bubbleText.fillRate,
200
- lineHeight: fullParams.bubbleText.lineHeight,
201
- isBreakAll: breakAll
202
- })
203
-
204
- })
205
-
206
- return bubblesSelection
207
- }
208
-
209
- function setHighlightData ({ data, highlightRIncrease, highlightIds }: {
210
- data: BubblesDatum[]
211
- // fullParams: BubblesParams
212
- highlightRIncrease: number
213
- highlightIds: string[]
214
- }) {
215
- if (highlightRIncrease == 0) {
216
- return
217
- }
218
- if (!highlightIds.length) {
219
- data.forEach(d => d.r = d._originR)
220
- return
221
- }
222
- data.forEach(d => {
223
- if (highlightIds.includes(d.id)) {
224
- d.r = d._originR + highlightRIncrease
225
- } else {
226
- d.r = d._originR
227
- }
228
- })
229
- }
230
-
231
- function drag (): d3.DragBehavior<Element, unknown, unknown> {
232
- return d3.drag()
233
- .on("start", (event, d: any) => {
234
- if (!event.active) {
235
- force!.alpha(1).restart();
236
- }
237
- d.fx = d.x
238
- d.fy = d.y
239
- })
240
- .on("drag", (event, d: any) => {
241
- if (!event.active) {
242
- force!.alphaTarget(0)
243
- }
244
- d.fx = event.x
245
- d.fy = event.y
246
- })
247
- .on("end", (event, d: any) => {
248
- d.fx = null
249
- d.fy = null
250
- })
251
- }
252
-
253
-
254
- // private nodeTypePos (d: any) {
255
- // console.log(d)
256
- // console.log(this.TypeCenters.get(d.type)!)
257
- // const typeCenter = this.TypeCenters.get(d.type)!
258
- // return typeCenter ? typeCenter.x : 0
259
- // }
260
-
261
- function groupBubbles ({ fullParams, SeriesContainerPositionMap }: {
262
- fullParams: BubblesParams
263
- // graphicWidth: number
264
- // graphicHeight: number
265
- SeriesContainerPositionMap: Map<string, SeriesContainerPosition>
266
- }) {
267
- // console.log('groupBubbles')
268
- force!
269
- // .force('x', d3.forceX().strength(fullParams.force.strength).x(graphicWidth / 2))
270
- // .force('y', d3.forceY().strength(fullParams.force.strength).y(graphicHeight / 2))
271
- .force('x', d3.forceX().strength(fullParams.force.strength).x((data: BubblesSimulationDatum) => {
272
- return SeriesContainerPositionMap.get(data.seriesLabel)!.centerX
273
- }))
274
- .force('y', d3.forceY().strength(fullParams.force.strength).y((data: BubblesSimulationDatum) => {
275
- return SeriesContainerPositionMap.get(data.seriesLabel)!.centerY
276
- }))
277
-
278
- force!.alpha(1).restart();
279
- }
280
-
281
- function highlight ({ bubblesSelection, highlightIds, fullChartParams }: {
282
- bubblesSelection: d3.Selection<SVGGElement, BubblesDatum, any, any>
283
- fullChartParams: ChartParams
284
- highlightIds: string[]
285
- }) {
286
- bubblesSelection.interrupt('highlight')
287
-
288
- if (!highlightIds.length) {
289
- bubblesSelection
290
- .transition('highlight')
291
- .style('opacity', 1)
292
- return
293
- }
294
-
295
- bubblesSelection.each((d, i, n) => {
296
- const segment = d3.select(n[i])
297
-
298
- if (highlightIds.includes(d.id)) {
299
- segment
300
- .style('opacity', 1)
301
- .transition('highlight')
302
- .ease(d3.easeElastic)
303
- .duration(500)
304
- } else {
305
- // 取消放大
306
- segment
307
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
308
- }
309
- })
310
- }
311
-
312
- export const Bubbles = defineSeriesPlugin('Bubbles', DEFAULT_BUBBLES_PARAMS)(({ selection, name, observer, subject }) => {
313
-
314
- const destroy$ = new Subject()
315
-
316
- const graphicSelection: d3.Selection<SVGGElement, any, any, any> = selection.append('g')
317
- const bubblesSelection$: Subject<d3.Selection<SVGGElement, BubblesDatum, any, any>> = new Subject()
318
- // 紀錄前一次bubble data
319
- let LastBubbleDataMap: Map<string, BubblesDatum> = new Map()
320
-
321
-
322
- // fullParams$.subscribe(d => {
323
- // force = makeForce(bubblesSelection, d)
324
- // })
325
-
326
- // observer.seriesContainerPosition$.subscribe(d => {
327
- // console.log(d)
328
- // })
329
-
330
- // observer.layout$
331
- // .pipe(
332
- // first()
333
- // )
334
- // .subscribe(size => {
335
- // selection
336
- // .attr('transform', `translate(${size.width / 2}, ${size.height / 2})`)
337
- // observer.layout$
338
- // .pipe(
339
- // takeUntil(destroy$)
340
- // )
341
- // .subscribe(size => {
342
- // selection
343
- // .transition()
344
- // .attr('transform', `translate(${size.width / 2}, ${size.height / 2})`)
345
- // })
346
- // })
347
-
348
- // const bubbleGroupR$ = layout$.pipe(
349
- // map(d => {
350
- // const minWidth = Math.min(...[d.width, d.height])
351
- // return minWidth / 2
352
- // })
353
- // )
354
-
355
- // const maxValue$ = computedData$.pipe(
356
- // map(d => Math.max(
357
- // ...d
358
- // .flat()
359
- // .filter(_d => _d.value != null)
360
- // .map(_d => _d.value!)
361
- // )
362
- // )
363
- // )
364
-
365
- // const avgValue$ = computedData$.pipe(
366
- // map(d => {
367
- // const total = d
368
- // .flat()
369
- // .reduce((prev, current) => prev + (current.value ?? 0), 0)
370
- // return total / d.length
371
- // })
372
- // )
373
-
374
- // const SeriesDataMap$ = observer.computedData$.pipe(
375
- // takeUntil(destroy$),
376
- // map(d => makeSeriesDataMap(d))
377
- // )
378
-
379
- const scaleType$ = observer.fullParams$.pipe(
380
- takeUntil(destroy$),
381
- map(d => d.bubbleScaleType),
382
- distinctUntilChanged()
383
- )
384
-
385
- const bubblesData$ = new Observable<BubblesDatum[]>(subscriber => {
386
- combineLatest({
387
- layout: observer.layout$,
388
- SeriesContainerPositionMap: observer.SeriesContainerPositionMap$,
389
- visibleComputedLayoutData: observer.visibleComputedLayoutData$,
390
- scaleType: scaleType$
391
- }).pipe(
392
- takeUntil(destroy$),
393
- switchMap(async (d) => d),
394
- ).subscribe(data => {
395
- // console.log(data.visibleComputedLayoutData)
396
- const bubblesData = createBubblesData({
397
- data: data.visibleComputedLayoutData,
398
- LastBubbleDataMap,
399
- graphicWidth: data.layout.width,
400
- graphicHeight: data.layout.height,
401
- SeriesContainerPositionMap: data.SeriesContainerPositionMap,
402
- scaleType: data.scaleType
403
- })
404
- subscriber.next(bubblesData)
405
- })
406
- })
407
-
408
- // 紀錄前一次bubble data
409
- bubblesData$.subscribe(d => {
410
- LastBubbleDataMap = new Map(d.map(_d => [_d.id, _d])) // key: id, value: datum
411
- })
412
-
413
- const highlightTarget$ = observer.fullChartParams$.pipe(
414
- takeUntil(destroy$),
415
- map(d => d.highlightTarget),
416
- distinctUntilChanged()
417
- )
418
-
419
- combineLatest({
420
- layout: observer.layout$,
421
- computedData: observer.computedData$,
422
- bubblesData: bubblesData$,
423
- SeriesDataMap: observer.SeriesDataMap$,
424
- fullParams: observer.fullParams$,
425
- highlightTarget: highlightTarget$,
426
- SeriesContainerPositionMap: observer.SeriesContainerPositionMap$,
427
- // fullChartParams: fullChartParams$
428
- // highlight: highlight$
429
- }).pipe(
430
- takeUntil(destroy$),
431
- switchMap(async (d) => d),
432
- ).subscribe(data => {
433
-
434
- const bubblesSelection = renderBubbles({
435
- graphicSelection,
436
- bubblesData: data.bubblesData,
437
- fullParams: data.fullParams
438
- })
439
-
440
- force = makeForce(bubblesSelection, data.fullParams)
441
-
442
- bubblesSelection
443
- .on('mouseover', (event, datum) => {
444
- // this.tooltip!.setDatum({
445
- // data: d,
446
- // x: d3.event.clientX,
447
- // y: d3.event.clientY
448
- // })
449
-
450
- subject.event$.next({
451
- type: 'series',
452
- eventName: 'mouseover',
453
- pluginName: name,
454
- highlightTarget: data.highlightTarget,
455
- datum,
456
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
457
- seriesIndex: datum.seriesIndex,
458
- seriesLabel: datum.seriesLabel,
459
- event,
460
- data: data.computedData
461
- })
462
- })
463
- .on('mousemove', (event, datum) => {
464
- // this.tooltip!.setDatum({
465
- // x: d3.event.clientX,
466
- // y: d3.event.clientY
467
- // })
468
-
469
- subject.event$.next({
470
- type: 'series',
471
- eventName: 'mousemove',
472
- pluginName: name,
473
- highlightTarget: data.highlightTarget,
474
- datum,
475
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
476
- seriesIndex: datum.seriesIndex,
477
- seriesLabel: datum.seriesLabel,
478
- event,
479
- data: data.computedData
480
- })
481
- })
482
- .on('mouseout', (event, datum) => {
483
- // this.tooltip!.remove()
484
-
485
- subject.event$.next({
486
- type: 'series',
487
- eventName: 'mouseout',
488
- pluginName: name,
489
- highlightTarget: data.highlightTarget,
490
- datum,
491
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
492
- seriesIndex: datum.seriesIndex,
493
- seriesLabel: datum.seriesLabel,
494
- event,
495
- data: data.computedData
496
- })
497
- })
498
- .on('click', (event, datum) => {
499
-
500
- subject.event$.next({
501
- type: 'series',
502
- eventName: 'click',
503
- pluginName: name,
504
- highlightTarget: data.highlightTarget,
505
- datum,
506
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
507
- seriesIndex: datum.seriesIndex,
508
- seriesLabel: datum.seriesLabel,
509
- event,
510
- data: data.computedData
511
- })
512
- })
513
- .call(drag() as any)
514
-
515
- force!.nodes(data.bubblesData);
516
-
517
- groupBubbles({
518
- fullParams: data.fullParams,
519
- SeriesContainerPositionMap: data.SeriesContainerPositionMap
520
- // graphicWidth: data.layout.width,
521
- // graphicHeight: data.layout.height
522
- })
523
-
524
- bubblesSelection$.next(bubblesSelection)
525
- })
526
-
527
- combineLatest({
528
- bubblesSelection: bubblesSelection$,
529
- bubblesData: bubblesData$,
530
- highlight: observer.seriesHighlight$.pipe(
531
- map(data => data.map(d => d.id))
532
- ),
533
- fullChartParams: observer.fullChartParams$,
534
- fullParams: observer.fullParams$,
535
- // layout: observer.layout$,
536
- SeriesContainerPositionMap: observer.SeriesContainerPositionMap$,
537
- }).pipe(
538
- takeUntil(destroy$),
539
- switchMap(async d => d)
540
- ).subscribe(data => {
541
- highlight({
542
- bubblesSelection: data.bubblesSelection,
543
- highlightIds: data.highlight,
544
- fullChartParams: data.fullChartParams
545
- })
546
-
547
- if (data.fullParams.highlightRIncrease) {
548
- setHighlightData ({
549
- data: data.bubblesData,
550
- highlightRIncrease: data.fullParams.highlightRIncrease,
551
- highlightIds: data.highlight
552
- })
553
- renderBubbles({
554
- graphicSelection,
555
- bubblesData: data.bubblesData,
556
- fullParams: data.fullParams
557
- })
558
- }
559
-
560
- groupBubbles({
561
- fullParams: data.fullParams,
562
- SeriesContainerPositionMap: data.SeriesContainerPositionMap
563
- // graphicWidth: data.layout.width,
564
- // graphicHeight: data.layout.height
565
- })
566
- force!.nodes(data.bubblesData);
567
- })
568
-
569
- return () => {
570
- destroy$.next(undefined)
571
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ switchMap,
6
+ first,
7
+ takeUntil,
8
+ Subject,
9
+ Observable,
10
+ distinctUntilChanged} from 'rxjs'
11
+ import type {
12
+ ChartParams,
13
+ DatumValue,
14
+ DataSeries,
15
+ EventName,
16
+ ComputedDataSeries,
17
+ ComputedDatumSeries,
18
+ SeriesContainerPosition } from '@orbcharts/core'
19
+ import {
20
+ defineSeriesPlugin } from '@orbcharts/core'
21
+ import type { BubblesParams, BubbleScaleType } from '../types'
22
+ import { DEFAULT_BUBBLES_PARAMS } from '../defaults'
23
+ import { renderCircleText } from '../../utils/d3Graphics'
24
+
25
+ interface BubblesDatum extends ComputedDatumSeries {
26
+ x: number
27
+ y: number
28
+ r: number
29
+ _originR: number // 紀錄變化前的r
30
+ }
31
+
32
+ type BubblesSimulationDatum = BubblesDatum & d3.SimulationNodeDatum
33
+
34
+ let force: d3.Simulation<d3.SimulationNodeDatum, undefined> | undefined
35
+
36
+ function makeForce (bubblesSelection: d3.Selection<SVGGElement, any, any, any>, fullParams: BubblesParams) {
37
+ return d3.forceSimulation()
38
+ .velocityDecay(fullParams.force!.velocityDecay!)
39
+ // .alphaDecay(0.2)
40
+ .force(
41
+ "collision",
42
+ d3.forceCollide()
43
+ .radius(d => {
44
+ // @ts-ignore
45
+ return d.r + fullParams.force!.collisionSpacing
46
+ })
47
+ // .strength(0.01)
48
+ )
49
+ .force("charge", d3.forceManyBody().strength((d) => {
50
+ // @ts-ignore
51
+ return - Math.pow(d.r, 2.0) * fullParams.force!.strength
52
+ }))
53
+ // .force("x", d3.forceX().strength(forceStrength).x(this.graphicWidth / 2))
54
+ // .force("y", d3.forceY().strength(forceStrength).y(this.graphicHeight / 2))
55
+ .on("tick", () => {
56
+ // if (!bubblesSelection) {
57
+ // return
58
+ // }
59
+ bubblesSelection
60
+ .attr("transform", (d) => {
61
+ return `translate(${d.x},${d.y})`
62
+ })
63
+ // .attr("cx", (d) => d.x)
64
+ // .attr("cy", (d) => d.y)
65
+ })
66
+ }
67
+
68
+
69
+ // 計算最大泡泡的半徑
70
+ function getMaxR ({ data, bubbleGroupR, maxValue, avgValue }: {
71
+ data: DatumValue[]
72
+ bubbleGroupR: number
73
+ maxValue: number
74
+ avgValue: number
75
+ }) {
76
+ // 平均r(假想是正方型來計算的,比如說大正方型裡有4個正方型,則 r = width/Math.sqrt(4)/2)
77
+ const avgR = bubbleGroupR / Math.sqrt(data.length)
78
+ const avgSize = avgR * avgR * Math.PI
79
+ const sizeRate = avgSize / avgValue
80
+ const maxSize = maxValue * sizeRate
81
+ const maxR = Math.pow(maxSize / Math.PI, 0.5)
82
+
83
+ const modifier = 0.75 // @Q@ 因為以下公式是假設泡泡是正方型來計算,所以畫出來的圖會偏大一些,這個數值是用來修正用的
84
+ return maxR * modifier
85
+ }
86
+
87
+ function createBubblesData ({ data, LastBubbleDataMap, graphicWidth, graphicHeight, SeriesContainerPositionMap, scaleType }: {
88
+ data: ComputedDataSeries
89
+ LastBubbleDataMap: Map<string, BubblesDatum>
90
+ graphicWidth: number
91
+ graphicHeight: number
92
+ SeriesContainerPositionMap: Map<string, SeriesContainerPosition>
93
+ scaleType: BubbleScaleType
94
+ // highlightIds: string[]
95
+ }) {
96
+ const bubbleGroupR = Math.min(...[graphicWidth, graphicHeight]) / 2
97
+
98
+ const filteredData = data
99
+ .flat()
100
+ .filter(_d => _d.value != null && _d.visible != false)
101
+
102
+ const maxValue = Math.max(
103
+ ...filteredData.map(_d => _d.value!)
104
+ )
105
+
106
+ const avgValue = (
107
+ filteredData.reduce((prev, current) => prev + (current.value ?? 0), 0)
108
+ ) / filteredData.length
109
+
110
+ const maxR = getMaxR({ data: filteredData, bubbleGroupR, maxValue, avgValue })
111
+
112
+ const exponent = scaleType === 'area'
113
+ ? 0.5 // 比例映射面積(0.5為取平方根)
114
+ : 1 // 比例映射半徑
115
+
116
+ const scaleBubbleR = d3.scalePow()
117
+ .domain([0, maxValue])
118
+ .range([0, maxR])
119
+ .exponent(exponent)
120
+
121
+ const bubbleData: BubblesDatum[] = filteredData.map((_d) => {
122
+ const d: BubblesDatum = _d as BubblesDatum
123
+
124
+ const existDatum = LastBubbleDataMap.get(_d.id)
125
+
126
+ if (existDatum) {
127
+ // 使用現有的座標
128
+ d.x = existDatum.x
129
+ d.y = existDatum.y
130
+ } else {
131
+ const seriesContainerPosition = SeriesContainerPositionMap.get(d.seriesLabel)!
132
+ d.x = Math.random() * seriesContainerPosition.width
133
+ d.y = Math.random() * seriesContainerPosition.height
134
+ }
135
+ const r = scaleBubbleR!(d.value ?? 0)!
136
+ d.r = r
137
+ d._originR = r
138
+
139
+ return d
140
+ })
141
+
142
+ return bubbleData
143
+ }
144
+
145
+ function renderBubbles ({ graphicSelection, bubblesData, fullParams, sumSeries }: {
146
+ graphicSelection: d3.Selection<SVGGElement, any, any, any>
147
+ bubblesData: BubblesDatum[]
148
+ fullParams: BubblesParams
149
+ sumSeries: boolean
150
+ }) {
151
+ const bubblesSelection = graphicSelection.selectAll<SVGGElement, BubblesDatum>("g")
152
+ .data(bubblesData, (d) => d.id)
153
+ .join(
154
+ enter => {
155
+ const enterSelection = enter
156
+ .append('g')
157
+ .attr('cursor', 'pointer')
158
+ .attr('font-size', 12)
159
+ .style('fill', '#ffffff')
160
+ .attr("text-anchor", "middle")
161
+
162
+ enterSelection
163
+ .append("circle")
164
+ .attr("class", "node")
165
+ .attr("cx", 0)
166
+ .attr("cy", 0)
167
+ // .attr("r", 1e-6)
168
+ .attr('fill', (d) => d.color)
169
+ // .transition()
170
+ // .duration(500)
171
+
172
+ enterSelection
173
+ .append('text')
174
+ .style('opacity', 0.8)
175
+ .attr('pointer-events', 'none')
176
+
177
+ return enterSelection
178
+ },
179
+ update => {
180
+ return update
181
+ },
182
+ exit => {
183
+ return exit
184
+ .remove()
185
+ }
186
+ )
187
+ .attr("transform", (d) => {
188
+ return `translate(${d.x},${d.y})`
189
+ })
190
+
191
+ // 泡泡文字要使用的的資料欄位
192
+ const textDataColumn = sumSeries ? 'seriesLabel' : 'label'// 如果有合併series則使用seriesLabel
193
+
194
+ bubblesSelection.select('circle')
195
+ .transition()
196
+ .duration(200)
197
+ .attr("r", (d) => d.r)
198
+ .attr('fill', (d) => d.color)
199
+ bubblesSelection
200
+ .each((d,i,g) => {
201
+ const gSelection = d3.select(g[i])
202
+ let breakAll = true
203
+ if (d[textDataColumn].length <= fullParams.bubbleText.lineLengthMin) {
204
+ breakAll = false
205
+ }
206
+ gSelection.call(renderCircleText, {
207
+ text: d[textDataColumn],
208
+ radius: d.r * fullParams.bubbleText.fillRate,
209
+ lineHeight: fullParams.bubbleText.lineHeight,
210
+ isBreakAll: breakAll
211
+ })
212
+
213
+ })
214
+
215
+ return bubblesSelection
216
+ }
217
+
218
+ function setHighlightData ({ data, highlightRIncrease, highlightIds }: {
219
+ data: BubblesDatum[]
220
+ // fullParams: BubblesParams
221
+ highlightRIncrease: number
222
+ highlightIds: string[]
223
+ }) {
224
+ if (highlightRIncrease == 0) {
225
+ return
226
+ }
227
+ if (!highlightIds.length) {
228
+ data.forEach(d => d.r = d._originR)
229
+ return
230
+ }
231
+ data.forEach(d => {
232
+ if (highlightIds.includes(d.id)) {
233
+ d.r = d._originR + highlightRIncrease
234
+ } else {
235
+ d.r = d._originR
236
+ }
237
+ })
238
+ }
239
+
240
+ function drag (): d3.DragBehavior<Element, unknown, unknown> {
241
+ return d3.drag()
242
+ .on("start", (event, d: any) => {
243
+ if (!event.active) {
244
+ force!.alpha(1).restart();
245
+ }
246
+ d.fx = d.x
247
+ d.fy = d.y
248
+ })
249
+ .on("drag", (event, d: any) => {
250
+ if (!event.active) {
251
+ force!.alphaTarget(0)
252
+ }
253
+ d.fx = event.x
254
+ d.fy = event.y
255
+ })
256
+ .on("end", (event, d: any) => {
257
+ d.fx = null
258
+ d.fy = null
259
+ })
260
+ }
261
+
262
+
263
+ // private nodeTypePos (d: any) {
264
+ // console.log(d)
265
+ // console.log(this.TypeCenters.get(d.type)!)
266
+ // const typeCenter = this.TypeCenters.get(d.type)!
267
+ // return typeCenter ? typeCenter.x : 0
268
+ // }
269
+
270
+ function groupBubbles ({ fullParams, SeriesContainerPositionMap }: {
271
+ fullParams: BubblesParams
272
+ // graphicWidth: number
273
+ // graphicHeight: number
274
+ SeriesContainerPositionMap: Map<string, SeriesContainerPosition>
275
+ }) {
276
+ // console.log('groupBubbles')
277
+ force!
278
+ // .force('x', d3.forceX().strength(fullParams.force.strength).x(graphicWidth / 2))
279
+ // .force('y', d3.forceY().strength(fullParams.force.strength).y(graphicHeight / 2))
280
+ .force('x', d3.forceX().strength(fullParams.force.strength).x((data: BubblesSimulationDatum) => {
281
+ return SeriesContainerPositionMap.get(data.seriesLabel)!.centerX
282
+ }))
283
+ .force('y', d3.forceY().strength(fullParams.force.strength).y((data: BubblesSimulationDatum) => {
284
+ return SeriesContainerPositionMap.get(data.seriesLabel)!.centerY
285
+ }))
286
+
287
+ force!.alpha(1).restart();
288
+ }
289
+
290
+ function highlight ({ bubblesSelection, highlightIds, fullChartParams }: {
291
+ bubblesSelection: d3.Selection<SVGGElement, BubblesDatum, any, any>
292
+ fullChartParams: ChartParams
293
+ highlightIds: string[]
294
+ }) {
295
+ bubblesSelection.interrupt('highlight')
296
+
297
+ if (!highlightIds.length) {
298
+ bubblesSelection
299
+ .transition('highlight')
300
+ .style('opacity', 1)
301
+ return
302
+ }
303
+
304
+ bubblesSelection.each((d, i, n) => {
305
+ const segment = d3.select(n[i])
306
+
307
+ if (highlightIds.includes(d.id)) {
308
+ segment
309
+ .style('opacity', 1)
310
+ .transition('highlight')
311
+ .ease(d3.easeElastic)
312
+ .duration(500)
313
+ } else {
314
+ // 取消放大
315
+ segment
316
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
317
+ }
318
+ })
319
+ }
320
+
321
+ export const Bubbles = defineSeriesPlugin('Bubbles', DEFAULT_BUBBLES_PARAMS)(({ selection, name, observer, subject }) => {
322
+
323
+ const destroy$ = new Subject()
324
+
325
+ const graphicSelection: d3.Selection<SVGGElement, any, any, any> = selection.append('g')
326
+ // const bubblesSelection$: Subject<d3.Selection<SVGGElement, BubblesDatum, any, any>> = new Subject()
327
+ // 紀錄前一次bubble data
328
+ let LastBubbleDataMap: Map<string, BubblesDatum> = new Map()
329
+
330
+
331
+ // fullParams$.subscribe(d => {
332
+ // force = makeForce(bubblesSelection, d)
333
+ // })
334
+
335
+ // observer.seriesContainerPosition$.subscribe(d => {
336
+ // console.log(d)
337
+ // })
338
+
339
+ // observer.layout$
340
+ // .pipe(
341
+ // first()
342
+ // )
343
+ // .subscribe(size => {
344
+ // selection
345
+ // .attr('transform', `translate(${size.width / 2}, ${size.height / 2})`)
346
+ // observer.layout$
347
+ // .pipe(
348
+ // takeUntil(destroy$)
349
+ // )
350
+ // .subscribe(size => {
351
+ // selection
352
+ // .transition()
353
+ // .attr('transform', `translate(${size.width / 2}, ${size.height / 2})`)
354
+ // })
355
+ // })
356
+
357
+ // const bubbleGroupR$ = layout$.pipe(
358
+ // map(d => {
359
+ // const minWidth = Math.min(...[d.width, d.height])
360
+ // return minWidth / 2
361
+ // })
362
+ // )
363
+
364
+ // const maxValue$ = computedData$.pipe(
365
+ // map(d => Math.max(
366
+ // ...d
367
+ // .flat()
368
+ // .filter(_d => _d.value != null)
369
+ // .map(_d => _d.value!)
370
+ // )
371
+ // )
372
+ // )
373
+
374
+ // const avgValue$ = computedData$.pipe(
375
+ // map(d => {
376
+ // const total = d
377
+ // .flat()
378
+ // .reduce((prev, current) => prev + (current.value ?? 0), 0)
379
+ // return total / d.length
380
+ // })
381
+ // )
382
+
383
+ // const SeriesDataMap$ = observer.computedData$.pipe(
384
+ // takeUntil(destroy$),
385
+ // map(d => makeSeriesDataMap(d))
386
+ // )
387
+
388
+ const sumSeries$ = observer.fullDataFormatter$.pipe(
389
+ map(d => d.sumSeries),
390
+ distinctUntilChanged()
391
+ )
392
+
393
+ const scaleType$ = observer.fullParams$.pipe(
394
+ takeUntil(destroy$),
395
+ map(d => d.bubbleScaleType),
396
+ distinctUntilChanged()
397
+ )
398
+
399
+ const bubblesData$ = new Observable<BubblesDatum[]>(subscriber => {
400
+ combineLatest({
401
+ layout: observer.layout$,
402
+ SeriesContainerPositionMap: observer.SeriesContainerPositionMap$,
403
+ visibleComputedLayoutData: observer.visibleComputedLayoutData$,
404
+ scaleType: scaleType$
405
+ }).pipe(
406
+ takeUntil(destroy$),
407
+ switchMap(async (d) => d),
408
+ ).subscribe(data => {
409
+ // console.log(data.visibleComputedLayoutData)
410
+ const bubblesData = createBubblesData({
411
+ data: data.visibleComputedLayoutData,
412
+ LastBubbleDataMap,
413
+ graphicWidth: data.layout.width,
414
+ graphicHeight: data.layout.height,
415
+ SeriesContainerPositionMap: data.SeriesContainerPositionMap,
416
+ scaleType: data.scaleType
417
+ })
418
+ subscriber.next(bubblesData)
419
+ })
420
+ })
421
+
422
+ // 紀錄前一次bubble data
423
+ bubblesData$.subscribe(d => {
424
+ LastBubbleDataMap = new Map(d.map(_d => [_d.id, _d])) // key: id, value: datum
425
+ })
426
+
427
+ const highlightTarget$ = observer.fullChartParams$.pipe(
428
+ takeUntil(destroy$),
429
+ map(d => d.highlightTarget),
430
+ distinctUntilChanged()
431
+ )
432
+
433
+ const bubblesSelection$ = combineLatest({
434
+ bubblesData: bubblesData$,
435
+ fullParams: observer.fullParams$,
436
+ SeriesContainerPositionMap: observer.SeriesContainerPositionMap$,
437
+ sumSeries: sumSeries$
438
+ }).pipe(
439
+ takeUntil(destroy$),
440
+ switchMap(async (d) => d),
441
+ map(data => {
442
+ const bubblesSelection = renderBubbles({
443
+ graphicSelection,
444
+ bubblesData: data.bubblesData,
445
+ fullParams: data.fullParams,
446
+ sumSeries: data.sumSeries
447
+ })
448
+
449
+ force = makeForce(bubblesSelection, data.fullParams)
450
+
451
+ force.nodes(data.bubblesData)
452
+
453
+ groupBubbles({
454
+ fullParams: data.fullParams,
455
+ SeriesContainerPositionMap: data.SeriesContainerPositionMap
456
+ // graphicWidth: data.layout.width,
457
+ // graphicHeight: data.layout.height
458
+ })
459
+
460
+ return bubblesSelection
461
+ })
462
+ )
463
+
464
+ combineLatest({
465
+ bubblesSelection: bubblesSelection$,
466
+ layout: observer.layout$,
467
+ computedData: observer.computedData$,
468
+ bubblesData: bubblesData$,
469
+ SeriesDataMap: observer.SeriesDataMap$,
470
+ fullParams: observer.fullParams$,
471
+ highlightTarget: highlightTarget$,
472
+ SeriesContainerPositionMap: observer.SeriesContainerPositionMap$,
473
+ // fullChartParams: fullChartParams$
474
+ // highlight: highlight$
475
+ }).pipe(
476
+ takeUntil(destroy$),
477
+ switchMap(async (d) => d)
478
+ ).subscribe(data => {
479
+
480
+ data.bubblesSelection
481
+ .on('mouseover', (event, datum) => {
482
+ // this.tooltip!.setDatum({
483
+ // data: d,
484
+ // x: d3.event.clientX,
485
+ // y: d3.event.clientY
486
+ // })
487
+
488
+ subject.event$.next({
489
+ type: 'series',
490
+ eventName: 'mouseover',
491
+ pluginName: name,
492
+ highlightTarget: data.highlightTarget,
493
+ datum,
494
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
495
+ seriesIndex: datum.seriesIndex,
496
+ seriesLabel: datum.seriesLabel,
497
+ event,
498
+ data: data.computedData
499
+ })
500
+ })
501
+ .on('mousemove', (event, datum) => {
502
+ // this.tooltip!.setDatum({
503
+ // x: d3.event.clientX,
504
+ // y: d3.event.clientY
505
+ // })
506
+
507
+ subject.event$.next({
508
+ type: 'series',
509
+ eventName: 'mousemove',
510
+ pluginName: name,
511
+ highlightTarget: data.highlightTarget,
512
+ datum,
513
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
514
+ seriesIndex: datum.seriesIndex,
515
+ seriesLabel: datum.seriesLabel,
516
+ event,
517
+ data: data.computedData
518
+ })
519
+ })
520
+ .on('mouseout', (event, datum) => {
521
+ // this.tooltip!.remove()
522
+
523
+ subject.event$.next({
524
+ type: 'series',
525
+ eventName: 'mouseout',
526
+ pluginName: name,
527
+ highlightTarget: data.highlightTarget,
528
+ datum,
529
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
530
+ seriesIndex: datum.seriesIndex,
531
+ seriesLabel: datum.seriesLabel,
532
+ event,
533
+ data: data.computedData
534
+ })
535
+ })
536
+ .on('click', (event, datum) => {
537
+
538
+ subject.event$.next({
539
+ type: 'series',
540
+ eventName: 'click',
541
+ pluginName: name,
542
+ highlightTarget: data.highlightTarget,
543
+ datum,
544
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
545
+ seriesIndex: datum.seriesIndex,
546
+ seriesLabel: datum.seriesLabel,
547
+ event,
548
+ data: data.computedData
549
+ })
550
+ })
551
+ .call(drag() as any)
552
+
553
+
554
+ })
555
+
556
+ combineLatest({
557
+ bubblesSelection: bubblesSelection$,
558
+ bubblesData: bubblesData$,
559
+ highlight: observer.seriesHighlight$.pipe(
560
+ map(data => data.map(d => d.id))
561
+ ),
562
+ fullChartParams: observer.fullChartParams$,
563
+ fullParams: observer.fullParams$,
564
+ sumSeries: sumSeries$,
565
+ // layout: observer.layout$,
566
+ SeriesContainerPositionMap: observer.SeriesContainerPositionMap$,
567
+ }).pipe(
568
+ takeUntil(destroy$),
569
+ switchMap(async d => d)
570
+ ).subscribe(data => {
571
+ highlight({
572
+ bubblesSelection: data.bubblesSelection,
573
+ highlightIds: data.highlight,
574
+ fullChartParams: data.fullChartParams
575
+ })
576
+
577
+ if (data.fullParams.highlightRIncrease) {
578
+ setHighlightData ({
579
+ data: data.bubblesData,
580
+ highlightRIncrease: data.fullParams.highlightRIncrease,
581
+ highlightIds: data.highlight
582
+ })
583
+ renderBubbles({
584
+ graphicSelection,
585
+ bubblesData: data.bubblesData,
586
+ fullParams: data.fullParams,
587
+ sumSeries: data.sumSeries
588
+ })
589
+ }
590
+
591
+ groupBubbles({
592
+ fullParams: data.fullParams,
593
+ SeriesContainerPositionMap: data.SeriesContainerPositionMap
594
+ // graphicWidth: data.layout.width,
595
+ // graphicHeight: data.layout.height
596
+ })
597
+ force!.nodes(data.bubblesData);
598
+ })
599
+
600
+ return () => {
601
+ destroy$.next(undefined)
602
+ }
572
603
  })