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

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 (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
  })