@orbcharts/plugin-basic 4.0.0-pre-alpha.0

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 (208) hide show
  1. package/LICENSE +201 -0
  2. package/dist/orbcharts-plugin-basic.es.js +25335 -0
  3. package/dist/orbcharts-plugin-basic.umd.js +341 -0
  4. package/dist/plugin-basic/src/baseLayers/BaseBars.d.ts +38 -0
  5. package/dist/plugin-basic/src/baseLayers/BaseBarsTriangle.d.ts +37 -0
  6. package/dist/plugin-basic/src/baseLayers/BaseCategoryAxis.d.ts +42 -0
  7. package/dist/plugin-basic/src/baseLayers/BaseDots.d.ts +38 -0
  8. package/dist/plugin-basic/src/baseLayers/BaseLegend.d.ts +31 -0
  9. package/dist/plugin-basic/src/baseLayers/BaseLineAreas.d.ts +36 -0
  10. package/dist/plugin-basic/src/baseLayers/BaseLines.d.ts +36 -0
  11. package/dist/plugin-basic/src/baseLayers/BaseStackedBars.d.ts +41 -0
  12. package/dist/plugin-basic/src/baseLayers/BaseTooltip.d.ts +47 -0
  13. package/dist/plugin-basic/src/baseLayers/BaseValueAxis.d.ts +38 -0
  14. package/dist/plugin-basic/src/baseLayers/BaseXAxis.d.ts +25 -0
  15. package/dist/plugin-basic/src/baseLayers/BaseXZoom.d.ts +22 -0
  16. package/dist/plugin-basic/src/baseLayers/BaseYAxis.d.ts +23 -0
  17. package/dist/plugin-basic/src/baseLayers/types.d.ts +171 -0
  18. package/dist/plugin-basic/src/const/layerIndex.d.ts +10 -0
  19. package/dist/plugin-basic/src/const/sharedPluginParams.d.ts +6 -0
  20. package/dist/plugin-basic/src/index.d.ts +2 -0
  21. package/dist/plugin-basic/src/plugins/CompositionPlot/CompositionPlot.d.ts +22 -0
  22. package/dist/plugin-basic/src/plugins/CompositionPlot/contextObservables.d.ts +40 -0
  23. package/dist/plugin-basic/src/plugins/CompositionPlot/defaults.d.ts +10 -0
  24. package/dist/plugin-basic/src/plugins/CompositionPlot/index.d.ts +3 -0
  25. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/Bubbles.d.ts +16 -0
  26. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/Indicator.d.ts +0 -0
  27. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/Pie.d.ts +16 -0
  28. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/PieEventTexts.d.ts +16 -0
  29. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/PieLabels.d.ts +16 -0
  30. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/Rose.d.ts +16 -0
  31. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/RoseLabels.d.ts +16 -0
  32. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/Waffle.d.ts +0 -0
  33. package/dist/plugin-basic/src/plugins/CompositionPlot/types.d.ts +110 -0
  34. package/dist/plugin-basic/src/plugins/CompositionPlot/utils.d.ts +19 -0
  35. package/dist/plugin-basic/src/plugins/HierarchyPlot/HierarchyPlot.d.ts +22 -0
  36. package/dist/plugin-basic/src/plugins/HierarchyPlot/contextObservables.d.ts +16 -0
  37. package/dist/plugin-basic/src/plugins/HierarchyPlot/defaults.d.ts +4 -0
  38. package/dist/plugin-basic/src/plugins/HierarchyPlot/index.d.ts +3 -0
  39. package/dist/plugin-basic/src/plugins/HierarchyPlot/layers/TreeMap.d.ts +16 -0
  40. package/dist/plugin-basic/src/plugins/HierarchyPlot/types.d.ts +29 -0
  41. package/dist/plugin-basic/src/plugins/Legend/Legend.d.ts +22 -0
  42. package/dist/plugin-basic/src/plugins/Legend/contextObservables.d.ts +9 -0
  43. package/dist/plugin-basic/src/plugins/Legend/defaults.d.ts +4 -0
  44. package/dist/plugin-basic/src/plugins/Legend/index.d.ts +3 -0
  45. package/dist/plugin-basic/src/plugins/Legend/layers/Legend.d.ts +16 -0
  46. package/dist/plugin-basic/src/plugins/Legend/types.d.ts +31 -0
  47. package/dist/plugin-basic/src/plugins/Legend/utils.d.ts +19 -0
  48. package/dist/plugin-basic/src/plugins/NetworkPlot/NetworkPlot.d.ts +22 -0
  49. package/dist/plugin-basic/src/plugins/NetworkPlot/contextObservables.d.ts +19 -0
  50. package/dist/plugin-basic/src/plugins/NetworkPlot/defaults.d.ts +5 -0
  51. package/dist/plugin-basic/src/plugins/NetworkPlot/index.d.ts +3 -0
  52. package/dist/plugin-basic/src/plugins/NetworkPlot/layers/ForceDirected.d.ts +16 -0
  53. package/dist/plugin-basic/src/plugins/NetworkPlot/layers/ForceDirectedBubbles.d.ts +16 -0
  54. package/dist/plugin-basic/src/plugins/NetworkPlot/types.d.ts +117 -0
  55. package/dist/plugin-basic/src/plugins/ScatterPlot/ScatterPlot.d.ts +22 -0
  56. package/dist/plugin-basic/src/plugins/ScatterPlot/contextObservables.d.ts +140 -0
  57. package/dist/plugin-basic/src/plugins/ScatterPlot/defaults.d.ts +8 -0
  58. package/dist/plugin-basic/src/plugins/ScatterPlot/index.d.ts +3 -0
  59. package/dist/plugin-basic/src/plugins/ScatterPlot/layers/Scatter.d.ts +16 -0
  60. package/dist/plugin-basic/src/plugins/ScatterPlot/layers/ScatterBubbles.d.ts +16 -0
  61. package/dist/plugin-basic/src/plugins/ScatterPlot/layers/XYAux.d.ts +16 -0
  62. package/dist/plugin-basic/src/plugins/ScatterPlot/layers/XYAxes.d.ts +16 -0
  63. package/dist/plugin-basic/src/plugins/ScatterPlot/layers/XZoom.d.ts +16 -0
  64. package/dist/plugin-basic/src/plugins/ScatterPlot/types.d.ts +146 -0
  65. package/dist/plugin-basic/src/plugins/SeriesPlot/SeriesPlot.d.ts +22 -0
  66. package/dist/plugin-basic/src/plugins/SeriesPlot/contextObservables.d.ts +77 -0
  67. package/dist/plugin-basic/src/plugins/SeriesPlot/defaults.d.ts +15 -0
  68. package/dist/plugin-basic/src/plugins/SeriesPlot/index.d.ts +3 -0
  69. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/Bars.d.ts +16 -0
  70. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/BarsPN.d.ts +16 -0
  71. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/BarsTriangle.d.ts +16 -0
  72. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/CategoryAux.d.ts +16 -0
  73. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/CategoryAxis.d.ts +16 -0
  74. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/CategoryZoom.d.ts +16 -0
  75. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/Dots.d.ts +16 -0
  76. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/LineAreas.d.ts +16 -0
  77. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/Lines.d.ts +16 -0
  78. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/StackedBars.d.ts +16 -0
  79. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/StackedValueAxis.d.ts +16 -0
  80. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/ValueAxis.d.ts +16 -0
  81. package/dist/plugin-basic/src/plugins/SeriesPlot/types.d.ts +140 -0
  82. package/dist/plugin-basic/src/plugins/Tooltip/Tooltip.d.ts +22 -0
  83. package/dist/plugin-basic/src/plugins/Tooltip/contextObservables.d.ts +9 -0
  84. package/dist/plugin-basic/src/plugins/Tooltip/defaults.d.ts +4 -0
  85. package/dist/plugin-basic/src/plugins/Tooltip/index.d.ts +3 -0
  86. package/dist/plugin-basic/src/plugins/Tooltip/layers/Tooltip.d.ts +16 -0
  87. package/dist/plugin-basic/src/plugins/Tooltip/types.d.ts +35 -0
  88. package/dist/plugin-basic/src/plugins/Tooltip/utils.d.ts +19 -0
  89. package/dist/plugin-basic/src/plugins/index.d.ts +7 -0
  90. package/dist/plugin-basic/src/types/BaseLayer.d.ts +3 -0
  91. package/dist/plugin-basic/src/types/Common.d.ts +14 -0
  92. package/dist/plugin-basic/src/types/ComputedData.d.ts +27 -0
  93. package/dist/plugin-basic/src/types/PluginParams.d.ts +66 -0
  94. package/dist/plugin-basic/src/types/index.d.ts +3 -0
  95. package/dist/plugin-basic/src/utils/commonUtils.d.ts +3 -0
  96. package/dist/plugin-basic/src/utils/d3Graphics.d.ts +24 -0
  97. package/dist/plugin-basic/src/utils/d3Scale.d.ts +28 -0
  98. package/dist/plugin-basic/src/utils/d3Utils.d.ts +14 -0
  99. package/dist/plugin-basic/src/utils/graphObservables.d.ts +0 -0
  100. package/dist/plugin-basic/src/utils/gridObservables.d.ts +51 -0
  101. package/dist/plugin-basic/src/utils/multivariateObservables.d.ts +74 -0
  102. package/dist/plugin-basic/src/utils/observables.d.ts +34 -0
  103. package/dist/plugin-basic/src/utils/orbchartsUtils.d.ts +26 -0
  104. package/dist/plugin-basic/src/utils/seriesObservables.d.ts +22 -0
  105. package/dist/plugin-basic/vite.config.d.ts +2 -0
  106. package/dist/src/index.d.ts +1 -0
  107. package/package.json +62 -0
  108. package/src/baseLayers/BaseBars.ts +783 -0
  109. package/src/baseLayers/BaseBarsTriangle.ts +692 -0
  110. package/src/baseLayers/BaseCategoryAxis.ts +708 -0
  111. package/src/baseLayers/BaseDots.ts +495 -0
  112. package/src/baseLayers/BaseLegend.ts +684 -0
  113. package/src/baseLayers/BaseLineAreas.ts +644 -0
  114. package/src/baseLayers/BaseLines.ts +721 -0
  115. package/src/baseLayers/BaseStackedBars.ts +818 -0
  116. package/src/baseLayers/BaseTooltip.ts +435 -0
  117. package/src/baseLayers/BaseValueAxis.ts +612 -0
  118. package/src/baseLayers/BaseXAxis.ts +412 -0
  119. package/src/baseLayers/BaseXZoom.ts +250 -0
  120. package/src/baseLayers/BaseYAxis.ts +371 -0
  121. package/src/baseLayers/types.ts +174 -0
  122. package/src/const/layerIndex.ts +36 -0
  123. package/src/const/sharedPluginParams.ts +29 -0
  124. package/src/index.ts +3 -0
  125. package/src/plugins/CompositionPlot/CompositionPlot.ts +308 -0
  126. package/src/plugins/CompositionPlot/contextObservables.ts +251 -0
  127. package/src/plugins/CompositionPlot/defaults.ts +162 -0
  128. package/src/plugins/CompositionPlot/index.ts +3 -0
  129. package/src/plugins/CompositionPlot/layers/Bubbles.ts +808 -0
  130. package/src/plugins/CompositionPlot/layers/Indicator.ts +0 -0
  131. package/src/plugins/CompositionPlot/layers/Pie.ts +776 -0
  132. package/src/plugins/CompositionPlot/layers/PieEventTexts.ts +326 -0
  133. package/src/plugins/CompositionPlot/layers/PieLabels.ts +651 -0
  134. package/src/plugins/CompositionPlot/layers/Rose.ts +546 -0
  135. package/src/plugins/CompositionPlot/layers/RoseLabels.ts +616 -0
  136. package/src/plugins/CompositionPlot/layers/Waffle.ts +0 -0
  137. package/src/plugins/CompositionPlot/types.ts +129 -0
  138. package/src/plugins/CompositionPlot/utils.ts +53 -0
  139. package/src/plugins/HierarchyPlot/HierarchyPlot.ts +190 -0
  140. package/src/plugins/HierarchyPlot/contextObservables.ts +136 -0
  141. package/src/plugins/HierarchyPlot/defaults.ts +31 -0
  142. package/src/plugins/HierarchyPlot/index.ts +3 -0
  143. package/src/plugins/HierarchyPlot/layers/TreeMap.ts +371 -0
  144. package/src/plugins/HierarchyPlot/types.ts +36 -0
  145. package/src/plugins/Legend/Legend.ts +151 -0
  146. package/src/plugins/Legend/contextObservables.ts +55 -0
  147. package/src/plugins/Legend/defaults.ts +37 -0
  148. package/src/plugins/Legend/index.ts +3 -0
  149. package/src/plugins/Legend/layers/Legend.ts +114 -0
  150. package/src/plugins/Legend/types.ts +45 -0
  151. package/src/plugins/Legend/utils.ts +53 -0
  152. package/src/plugins/NetworkPlot/NetworkPlot.ts +228 -0
  153. package/src/plugins/NetworkPlot/contextObservables.ts +123 -0
  154. package/src/plugins/NetworkPlot/defaults.ts +147 -0
  155. package/src/plugins/NetworkPlot/index.ts +3 -0
  156. package/src/plugins/NetworkPlot/layers/ForceDirected.ts +1048 -0
  157. package/src/plugins/NetworkPlot/layers/ForceDirectedBubbles.ts +1318 -0
  158. package/src/plugins/NetworkPlot/types.ts +146 -0
  159. package/src/plugins/ScatterPlot/ScatterPlot.ts +569 -0
  160. package/src/plugins/ScatterPlot/contextObservables.ts +901 -0
  161. package/src/plugins/ScatterPlot/defaults.ts +212 -0
  162. package/src/plugins/ScatterPlot/index.ts +3 -0
  163. package/src/plugins/ScatterPlot/layers/Scatter.ts +518 -0
  164. package/src/plugins/ScatterPlot/layers/ScatterBubbles.ts +670 -0
  165. package/src/plugins/ScatterPlot/layers/XYAux.ts +686 -0
  166. package/src/plugins/ScatterPlot/layers/XYAxes.ts +205 -0
  167. package/src/plugins/ScatterPlot/layers/XZoom.ts +48 -0
  168. package/src/plugins/ScatterPlot/types.ts +179 -0
  169. package/src/plugins/SeriesPlot/SeriesPlot.ts +494 -0
  170. package/src/plugins/SeriesPlot/contextObservables.ts +726 -0
  171. package/src/plugins/SeriesPlot/defaults.ts +142 -0
  172. package/src/plugins/SeriesPlot/index.ts +3 -0
  173. package/src/plugins/SeriesPlot/layers/Bars.ts +84 -0
  174. package/src/plugins/SeriesPlot/layers/BarsPN.ts +85 -0
  175. package/src/plugins/SeriesPlot/layers/BarsTriangle.ts +89 -0
  176. package/src/plugins/SeriesPlot/layers/CategoryAux.ts +1131 -0
  177. package/src/plugins/SeriesPlot/layers/CategoryAxis.ts +92 -0
  178. package/src/plugins/SeriesPlot/layers/CategoryZoom.ts +233 -0
  179. package/src/plugins/SeriesPlot/layers/Dots.ts +91 -0
  180. package/src/plugins/SeriesPlot/layers/LineAreas.ts +82 -0
  181. package/src/plugins/SeriesPlot/layers/Lines.ts +75 -0
  182. package/src/plugins/SeriesPlot/layers/StackedBars.ts +85 -0
  183. package/src/plugins/SeriesPlot/layers/StackedValueAxis.ts +111 -0
  184. package/src/plugins/SeriesPlot/layers/ValueAxis.ts +111 -0
  185. package/src/plugins/SeriesPlot/types.ts +201 -0
  186. package/src/plugins/Tooltip/Tooltip.ts +159 -0
  187. package/src/plugins/Tooltip/contextObservables.ts +55 -0
  188. package/src/plugins/Tooltip/defaults.ts +458 -0
  189. package/src/plugins/Tooltip/index.ts +3 -0
  190. package/src/plugins/Tooltip/layers/Tooltip.ts +90 -0
  191. package/src/plugins/Tooltip/types.ts +55 -0
  192. package/src/plugins/Tooltip/utils.ts +53 -0
  193. package/src/plugins/index.ts +8 -0
  194. package/src/types/BaseLayer.ts +3 -0
  195. package/src/types/Common.ts +20 -0
  196. package/src/types/ComputedData.ts +55 -0
  197. package/src/types/PluginParams.ts +81 -0
  198. package/src/types/index.ts +3 -0
  199. package/src/utils/commonUtils.ts +31 -0
  200. package/src/utils/d3Graphics.ts +177 -0
  201. package/src/utils/d3Scale.ts +198 -0
  202. package/src/utils/d3Utils.ts +92 -0
  203. package/src/utils/graphObservables.ts +0 -0
  204. package/src/utils/gridObservables.ts +637 -0
  205. package/src/utils/multivariateObservables.ts +790 -0
  206. package/src/utils/observables.ts +357 -0
  207. package/src/utils/orbchartsUtils.ts +335 -0
  208. package/src/utils/seriesObservables.ts +172 -0
@@ -0,0 +1,808 @@
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ of,
6
+ switchMap,
7
+ takeUntil,
8
+ distinctUntilChanged,
9
+ shareReplay,
10
+ debounceTime,
11
+ Observable,
12
+ Subject,
13
+ filter,
14
+ first,
15
+ iif} from 'rxjs'
16
+ import type { DefinePluginConfig, Theme } from '@orbcharts/core'
17
+ import type { CompositionPlotExtendContext, CompositionPlotPluginParams, BubblesParams } from "../types"
18
+ import { defineSVGLayer } from '@orbcharts/core'
19
+ import { validateObject } from '@orbcharts/core'
20
+ import { DEFAULT_BUBBLES_PARAMS } from "../defaults"
21
+ import { seriesCenterSelectionObservable } from "../../../utils/seriesObservables"
22
+ import { getDatumColor } from '../../../utils/orbchartsUtils'
23
+ import { createClassName } from '../../../utils/orbchartsUtils'
24
+ import type { ComputedDatumSeries } from '../../../types/ComputedData'
25
+ import type { ContainerPosition } from '../../../types/PluginParams'
26
+ import { LAYER_INDEX_OF_GRAPHIC } from '../../../const/layerIndex'
27
+ import { renderCircleText } from '../../../utils/d3Graphics'
28
+
29
+ interface BubblesDatum extends ComputedDatumSeries {
30
+ x: number
31
+ y: number
32
+ r: number
33
+ renderLabel: string
34
+ _originR: number // 紀錄變化前的r
35
+ }
36
+
37
+ type BubblesSimulationDatum = BubblesDatum & d3.SimulationNodeDatum
38
+
39
+ const pluginName = 'CompositionPlot'
40
+ const layerName = 'Bubbles'
41
+ const baseLineHeight = 12 // 未變形前的字體大小(代入計算用而已,數字多少都不會有影響)
42
+
43
+ // let isRunning = false
44
+
45
+ function createSimulation (bubblesSelection: d3.Selection<SVGGElement, BubblesDatum, any, any>, fullParams: BubblesParams) {
46
+ return d3.forceSimulation()
47
+ .velocityDecay(fullParams.force!.velocityDecay!)
48
+ // .alphaDecay(0.2)
49
+ .force(
50
+ "collision",
51
+ d3.forceCollide()
52
+ .radius((d: d3.SimulationNodeDatum & BubblesDatum) => {
53
+ return d.r + fullParams.force!.collisionSpacing
54
+ })
55
+ // .strength(0.01)
56
+ )
57
+ .force("charge", d3.forceManyBody().strength((d: d3.SimulationNodeDatum & BubblesDatum) => {
58
+ return - Math.pow(d.r, 2.0) * fullParams.force!.strength
59
+ }))
60
+ // .force("charge", d3.forceManyBody().strength(-2000))
61
+ // .force("collision", d3.forceCollide(60).strength(1)) // @Q@ 60為泡泡的R,暫時是先寫死的
62
+ // .force("x", d3.forceX().strength(forceStrength).x(this.graphicWidth / 2))
63
+ // .force("y", d3.forceY().strength(forceStrength).y(this.graphicHeight / 2))
64
+ .on("tick", () => {
65
+ // if (!bubblesSelection) {
66
+ // return
67
+ // }
68
+ bubblesSelection
69
+ .attr("transform", (d) => {
70
+ return `translate(${d.x},${d.y})`
71
+ })
72
+ // .attr("cx", (d) => d.x)
73
+ // .attr("cy", (d) => d.y)
74
+
75
+
76
+ })
77
+ // .on("end", () => {
78
+
79
+ // })
80
+
81
+ }
82
+
83
+
84
+ // // 計算最大泡泡的半徑
85
+ // function getMaxR ({ data, totalR, maxValue, avgValue }: {
86
+ // data: DatumValue[]
87
+ // totalR: number
88
+ // maxValue: number
89
+ // avgValue: number
90
+ // }) {
91
+ // // 平均r(假想是正方型來計算的,比如說大正方型裡有4個正方型,則 r = width/Math.sqrt(4)/2)
92
+ // const avgR = totalR / Math.sqrt(data.length)
93
+ // const avgSize = avgR * avgR * Math.PI
94
+ // const sizeRate = avgSize / avgValue
95
+ // const maxSize = maxValue * sizeRate
96
+ // const maxR = Math.pow(maxSize / Math.PI, 0.5)
97
+
98
+ // const modifier = 0.785 // @Q@ 因為以下公式是假設泡泡是正方型來計算,所以畫出來的圖會偏大一些,這個數值是用來修正用的
99
+ // return maxR * modifier
100
+ // }
101
+
102
+ // function createBubblesData ({ visibleComputedSortedData, LastBubbleDataMap, fullParams, graphicWidth, graphicHeight, DatumContainerPositionMap, scaleType }: {
103
+ // visibleComputedSortedData: ComputedDataSeries
104
+ // LastBubbleDataMap: Map<string, BubblesDatum>
105
+ // fullParams: BubblesParams
106
+ // graphicWidth: number
107
+ // graphicHeight: number
108
+ // DatumContainerPositionMap: Map<string, ContainerPosition>
109
+ // scaleType: ArcScaleType
110
+ // // highlightIds: string[]
111
+ // }): BubblesDatum[] {
112
+ // // 虛擬大圓(所有小圓聚合起來的大圓)的半徑
113
+ // const totalR = Math.min(...[graphicWidth, graphicHeight]) / 2
114
+
115
+ // const data = visibleComputedSortedData.flat()
116
+
117
+ // const totalValue = data.reduce((acc, current) => acc + current.value, 0)
118
+
119
+ // // 半徑比例尺
120
+ // const radiusScale = d3.scalePow()
121
+ // .domain([0, totalValue])
122
+ // .range([0, totalR])
123
+ // .exponent(scaleType === 'area'
124
+ // ? 0.5 // 數值映射面積(0.5為取平方根)
125
+ // : 1 // 數值映射半徑
126
+ // )
127
+
128
+ // // 縮放比例 - 確保多個小圓的總面積等於大圓的面積
129
+ // const scaleFactor = scaleType === 'area'
130
+ // ? 1
131
+ // // 當數值映射半徑時,多個小圓的總面積會小於大圓的面積,所以要計算縮放比例
132
+ // : (() => {
133
+ // const totalArea = totalR * totalR * Math.PI
134
+ // return Math.sqrt(totalArea / d3.sum(data, d => Math.PI * Math.pow(radiusScale(d.value), 2)))
135
+ // })()
136
+
137
+ // // 調整係數 - 因為圓和圓之間的空隙造成聚合起來的大圓會略大,所以稍作微調
138
+ // const adjustmentFactor = 0.9
139
+
140
+ // return data.map((_d) => {
141
+ // const d: BubblesDatum = _d as BubblesDatum
142
+
143
+ // d.renderLabel = fullParams.bubbleLabel.labelFn(d)
144
+
145
+ // const existDatum = LastBubbleDataMap.get(d.id)
146
+
147
+ // if (existDatum) {
148
+ // // 使用現有的座標
149
+ // d.x = existDatum.x
150
+ // d.y = existDatum.y
151
+ // } else {
152
+ // const seriesContainerPosition = DatumContainerPositionMap.get(d.id)!
153
+ // d.x = Math.random() * seriesContainerPosition.width
154
+ // d.y = Math.random() * seriesContainerPosition.height
155
+ // }
156
+ // const r = radiusScale!(d.value ?? 0)! * scaleFactor * adjustmentFactor
157
+ // d.r = r
158
+ // d._originR = r
159
+
160
+ // return d
161
+ // })
162
+ // }
163
+
164
+ function renderBubbles ({ selection, bubblesData, fullParams, theme }: {
165
+ selection: d3.Selection<SVGGElement, any, any, any>
166
+ bubblesData: BubblesDatum[]
167
+ fullParams: BubblesParams
168
+ theme: Theme
169
+ }) {
170
+ const bubblesSelection = selection.selectAll<SVGGElement, BubblesDatum>("g")
171
+ .data(bubblesData, (d) => d.id)
172
+ .join(
173
+ enter => {
174
+ const enterSelection = enter
175
+ .append('g')
176
+ .attr('cursor', 'pointer')
177
+ .attr('font-size', baseLineHeight)
178
+ .style('fill', '#ffffff')
179
+ .attr("text-anchor", "middle")
180
+
181
+ enterSelection
182
+ .append("circle")
183
+ .attr("class", "node")
184
+ .attr("cx", 0)
185
+ .attr("cy", 0)
186
+ // .attr("r", 1e-6)
187
+ .attr('fill', (d) => d.color)
188
+ // .transition()
189
+ // .duration(500)
190
+
191
+ enterSelection
192
+ .append('text')
193
+ .style('opacity', 0.8)
194
+ .attr('pointer-events', 'none')
195
+
196
+ return enterSelection
197
+ },
198
+ update => {
199
+ return update
200
+ },
201
+ exit => {
202
+ return exit
203
+ .remove()
204
+ }
205
+ )
206
+ .attr("transform", (d) => {
207
+ return `translate(${d.x},${d.y})`
208
+ })
209
+
210
+ bubblesSelection.select('circle')
211
+ .transition()
212
+ .duration(200)
213
+ // .ease(d3.easeLinear)
214
+ .attr("r", (d) => d.r)
215
+ .attr('fill', (d) => d.color)
216
+ bubblesSelection
217
+ .each((d,i,g) => {
218
+ const gSelection = d3.select(g[i])
219
+ const text = d.renderLabel
220
+
221
+ gSelection.call(renderCircleText, {
222
+ text,
223
+ radius: d.r * fullParams.bubbleLabel.fillRate,
224
+ lineHeight: baseLineHeight * fullParams.bubbleLabel.lineHeight,
225
+ isBreakAll: text.length <= fullParams.bubbleLabel.maxLineLength
226
+ ? false
227
+ : fullParams.bubbleLabel.wordBreakAll
228
+ })
229
+
230
+ // -- text color --
231
+ gSelection.select('text').attr('fill', _ => getDatumColor({
232
+ datum: d,
233
+ colorType: fullParams.bubbleLabel.colorType,
234
+ theme
235
+ }))
236
+
237
+ })
238
+
239
+ return bubblesSelection
240
+ }
241
+
242
+ function setHighlightData ({ data, highlightRIncrease, highlightIds }: {
243
+ data: BubblesDatum[]
244
+ // fullParams: BubblesParams
245
+ highlightRIncrease: number
246
+ highlightIds: string[]
247
+ }) {
248
+ if (highlightRIncrease == 0) {
249
+ return
250
+ }
251
+ if (!highlightIds.length) {
252
+ data.forEach(d => d.r = d._originR)
253
+ return
254
+ }
255
+ data.forEach(d => {
256
+ if (highlightIds.includes(d.id)) {
257
+ d.r = d._originR + highlightRIncrease
258
+ } else {
259
+ d.r = d._originR
260
+ }
261
+ })
262
+ }
263
+
264
+ function drag (_simulation: d3.Simulation<d3.SimulationNodeDatum, undefined>): d3.DragBehavior<Element, unknown, unknown> {
265
+ return d3.drag()
266
+ .on("start", (event, d: any) => {
267
+ if (!event.active) {
268
+ _simulation!.alpha(1).restart()
269
+ }
270
+ d.fx = d.x
271
+ d.fy = d.y
272
+ })
273
+ .on("drag", (event, d: any) => {
274
+ if (!event.active) {
275
+ _simulation!.alphaTarget(0)
276
+ }
277
+ d.fx = event.x
278
+ d.fy = event.y
279
+ })
280
+ .on("end", (event, d: any) => {
281
+ d.fx = null
282
+ d.fy = null
283
+ _simulation!.alpha(1).restart()
284
+ })
285
+ }
286
+
287
+
288
+ // private nodeTypePos (d: any) {
289
+ // console.log(d)
290
+ // console.log(this.TypeCenters.get(d.type)!)
291
+ // const typeCenter = this.TypeCenters.get(d.type)!
292
+ // return typeCenter ? typeCenter.x : 0
293
+ // }
294
+
295
+ function groupBubbles ({ _simulation, fullParams, DatumContainerPositionMap }: {
296
+ _simulation: d3.Simulation<d3.SimulationNodeDatum, undefined>
297
+ fullParams: BubblesParams
298
+ // graphicWidth: number
299
+ // graphicHeight: number
300
+ DatumContainerPositionMap: Map<string, ContainerPosition>
301
+ }) {
302
+ // console.log('groupBubbles')
303
+
304
+ _simulation!
305
+ // .force('x', d3.forceX().strength(fullParams.force.strength).x(graphicWidth / 2))
306
+ // .force('y', d3.forceY().strength(fullParams.force.strength).y(graphicHeight / 2))
307
+ .force('x', d3.forceX().strength(fullParams.force.strength).x((data: BubblesSimulationDatum) => {
308
+ let position = DatumContainerPositionMap.get(data.id)!
309
+ if (!position) {
310
+ // 有時候可能會因為時間差而找不到,這時候取第一筆
311
+ position = DatumContainerPositionMap.get(Array.from(DatumContainerPositionMap.keys())[0])
312
+ }
313
+
314
+ return position?.centerX ?? null
315
+ }))
316
+ .force('y', d3.forceY().strength(fullParams.force.strength).y((data: BubblesSimulationDatum) => {
317
+ let position = DatumContainerPositionMap.get(data.id)!
318
+ if (!position) {
319
+ position = DatumContainerPositionMap.get(Array.from(DatumContainerPositionMap.keys())[0])
320
+ }
321
+
322
+ return position?.centerY ?? null
323
+ }))
324
+
325
+ // force!.alpha(1).restart()
326
+ }
327
+
328
+ function highlight ({ bubblesSelection, highlightIds, pluginParams }: {
329
+ bubblesSelection: d3.Selection<SVGGElement, BubblesDatum, any, any>
330
+ pluginParams: CompositionPlotPluginParams
331
+ highlightIds: string[]
332
+ }) {
333
+ bubblesSelection.interrupt('highlight')
334
+
335
+ if (!highlightIds.length) {
336
+ bubblesSelection
337
+ .transition('highlight')
338
+ .style('opacity', 1)
339
+ return
340
+ }
341
+
342
+ bubblesSelection.each((d, i, n) => {
343
+ const segment = d3.select(n[i])
344
+
345
+ if (highlightIds.includes(d.id)) {
346
+ segment
347
+ .style('opacity', 1)
348
+ .transition('highlight')
349
+ .ease(d3.easeElastic)
350
+ .duration(500)
351
+ } else {
352
+ // 取消放大
353
+ segment
354
+ .style('opacity', pluginParams.styles.unhighlightedOpacity)
355
+ }
356
+ })
357
+ }
358
+
359
+
360
+
361
+ export const Bubbles = defineSVGLayer<CompositionPlotExtendContext, CompositionPlotPluginParams, BubblesParams>({
362
+ name: layerName,
363
+ defaultParams: DEFAULT_BUBBLES_PARAMS,
364
+ layerIndex: LAYER_INDEX_OF_GRAPHIC,
365
+ initShow: true,
366
+ validator: (params) => {
367
+ const result = validateObject(params, {
368
+ force: {
369
+ toBeTypes: ['object']
370
+ },
371
+ bubbleLabel: {
372
+ toBeTypes: ['object']
373
+ },
374
+ arcScaleType: {
375
+ toBe: '"area" | "radius"',
376
+ test: (value) => value === 'area' || value === 'radius'
377
+ }
378
+ })
379
+ if (params.force) {
380
+ const forceResult = validateObject(params.force, {
381
+ velocityDecay: {
382
+ toBeTypes: ['number']
383
+ },
384
+ collisionSpacing: {
385
+ toBeTypes: ['number']
386
+ },
387
+ strength: {
388
+ toBeTypes: ['number']
389
+ },
390
+ })
391
+ if (forceResult.status === 'error') {
392
+ return forceResult
393
+ }
394
+ }
395
+ if (params.bubbleLabel) {
396
+ const bubbleLabelResult = validateObject(params.bubbleLabel, {
397
+ labelFn: {
398
+ toBeTypes: ['Function'],
399
+ },
400
+ colorType: {
401
+ toBeOption: 'ColorType'
402
+ },
403
+ fillRate: {
404
+ toBeTypes: ['number']
405
+ },
406
+ lineHeight: {
407
+ toBeTypes: ['number']
408
+ },
409
+ maxLineLength: {
410
+ toBeTypes: ['number']
411
+ },
412
+ })
413
+ if (bubbleLabelResult.status === 'error') {
414
+ return bubbleLabelResult
415
+ }
416
+ }
417
+ return result
418
+ },
419
+ setup: ({ svgG, pluginParams$, layerParams$, context }) => {
420
+
421
+ const destroy$ = new Subject()
422
+
423
+ context.layout$
424
+ .pipe(
425
+ takeUntil(destroy$)
426
+ )
427
+ .subscribe(layout => {
428
+ d3.select(svgG)
429
+ .attr('transform', `translate(${layout.left}, ${layout.top})`)
430
+ })
431
+
432
+ let simulation: d3.Simulation<d3.SimulationNodeDatum, undefined> | undefined
433
+
434
+ // 紀錄前一次bubble data
435
+ let LastBubbleDataMap: Map<string, BubblesDatum> = new Map()
436
+
437
+
438
+ const scaleType$ = layerParams$.pipe(
439
+ takeUntil(destroy$),
440
+ map(d => d.arcScaleType),
441
+ distinctUntilChanged(),
442
+ shareReplay(1)
443
+ )
444
+
445
+ // 虛擬大圓(所有小圓聚合起來的大圓)的半徑
446
+ const totalR$ = context.layout$.pipe(
447
+ takeUntil(destroy$),
448
+ map(d => Math.min(d.width, d.height) / 2),
449
+ distinctUntilChanged(),
450
+ shareReplay(1)
451
+ )
452
+
453
+ const totalValue$ = context.visibleComputedSortedData$.pipe(
454
+ takeUntil(destroy$),
455
+ map(d => d.flat().reduce((acc, current) => acc + current.value, 0)),
456
+ distinctUntilChanged(),
457
+ shareReplay(1)
458
+ )
459
+
460
+ // 半徑比例尺
461
+ const radiusScale$ = combineLatest({
462
+ totalR: totalR$,
463
+ totalValue: totalValue$,
464
+ scaleType: scaleType$
465
+ }).pipe(
466
+ takeUntil(destroy$),
467
+ switchMap(async (d) => d),
468
+ map(data => {
469
+ return d3.scalePow()
470
+ .domain([0, data.totalValue])
471
+ .range([0, data.totalR])
472
+ .exponent(data.scaleType === 'area'
473
+ ? 0.5 // 數值映射面積(0.5為取平方根)
474
+ : 1 // 數值映射半徑
475
+ )
476
+ }),
477
+ shareReplay(1)
478
+ )
479
+
480
+ // 縮放比例 - 確保多個小圓的總面積等於大圓的面積
481
+ const scaleFactor$ = scaleType$.pipe(
482
+ takeUntil(destroy$),
483
+ switchMap(scaleType => {
484
+ return iif(
485
+ () => scaleType === 'area',
486
+ of(1),
487
+ combineLatest({
488
+ totalR: totalR$,
489
+ radiusScale: radiusScale$,
490
+ visibleComputedSortedData: context.visibleComputedSortedData$
491
+ }).pipe(
492
+ switchMap(async (d) => d),
493
+ map(data => {
494
+ // 當數值映射半徑時,多個小圓的總面積會小於大圓的面積,所以要計算縮放比例
495
+ const totalArea = data.totalR * data.totalR * Math.PI
496
+ return Math.sqrt(totalArea / d3.sum(data.visibleComputedSortedData.flat(), d => Math.PI * Math.pow(data.radiusScale(d.value), 2)))
497
+ })
498
+ )
499
+ )
500
+ })
501
+ )
502
+
503
+ const DatumRMap$ = combineLatest({
504
+ visibleComputedSortedData: context.visibleComputedSortedData$,
505
+ radiusScale: radiusScale$,
506
+ scaleFactor: scaleFactor$
507
+ }).pipe(
508
+ takeUntil(destroy$),
509
+ switchMap(async (d) => d),
510
+ map(data => {
511
+ // 調整係數 - 因為圓和圓之間的空隙造成聚合起來的大圓會略大,所以稍作微調
512
+ const adjustmentFactor = 0.9
513
+
514
+ return new Map<string, number>(
515
+ data.visibleComputedSortedData
516
+ .flat()
517
+ .map(d => [d.id, data.radiusScale(d.value ?? 0) * data.scaleFactor * adjustmentFactor])
518
+ )
519
+ }),
520
+ shareReplay(1)
521
+ )
522
+
523
+ // 初始座標
524
+ const DatumInitXYMap$ = context.DatumContainerPositionMap$.pipe(
525
+ takeUntil(destroy$),
526
+ filter(data => data.size > 0), // 至少要有一筆資料
527
+ map(data => {
528
+ return new Map<string, { x: number, y: number }>(
529
+ Array.from(data).map(([id, position]) => {
530
+ return [
531
+ id,
532
+ {
533
+ x: position.startX + (position.width * Math.random()),
534
+ y: position.startY + (position.height * Math.random())
535
+ }
536
+ ]
537
+ })
538
+ )
539
+ }),
540
+ first(), // 只算一次
541
+ shareReplay(1)
542
+ )
543
+
544
+ const bubblesData$ = combineLatest({
545
+ visibleComputedSortedData: context.visibleComputedSortedData$,
546
+ DatumRMap: DatumRMap$,
547
+ DatumInitXYMap: DatumInitXYMap$,
548
+ fullParams: layerParams$,
549
+ }).pipe(
550
+ takeUntil(destroy$),
551
+ switchMap(async (d) => d),
552
+ map(data => {
553
+ return data.visibleComputedSortedData
554
+ .flat()
555
+ .map(_d => {
556
+ // 傳址,附加計算的欄位資料會 reference 到始資料上
557
+ const d: BubblesDatum = _d as BubblesDatum
558
+
559
+ // 第一次計算時沒有 x, y 座標,取得預設座標。第二次之後計算使用原有的座標
560
+ if (d.x === undefined || d.y === undefined) {
561
+ let xy = data.DatumInitXYMap.get(d.id)!
562
+ if (!xy) {
563
+ xy = data.DatumInitXYMap.get(Array.from(data.DatumInitXYMap.keys())[0])
564
+ }
565
+ d.x = xy.x
566
+ d.y = xy.y
567
+ }
568
+ d.r = data.DatumRMap.get(d.id)!
569
+ d._originR = d.r
570
+ d.renderLabel = data.fullParams.bubbleLabel.labelFn(d)
571
+ return d
572
+ })
573
+ }),
574
+ shareReplay(1)
575
+ )
576
+
577
+ // const bubblesData$ = combineLatest({
578
+ // layout: observer.layout$,
579
+ // fullParams: layerParams$,
580
+ // DatumContainerPositionMap: observer.DatumContainerPositionMap$,
581
+ // visibleComputedSortedData: observer.visibleComputedSortedData$,
582
+ // scaleType: scaleType$,
583
+ // }).pipe(
584
+ // takeUntil(destroy$),
585
+ // switchMap(async (d) => d),
586
+ // map(data => {
587
+ // // console.log(data.visibleComputedSortedData)
588
+ // return createBubblesData({
589
+ // visibleComputedSortedData: data.visibleComputedSortedData,
590
+ // LastBubbleDataMap,
591
+ // fullParams: data.fullParams,
592
+ // graphicWidth: data.layout.width,
593
+ // graphicHeight: data.layout.height,
594
+ // DatumContainerPositionMap: data.DatumContainerPositionMap,
595
+ // scaleType: data.scaleType
596
+ // })
597
+ // }),
598
+ // shareReplay(1)
599
+ // )
600
+
601
+ // // 紀錄前一次bubble data
602
+ // bubblesData$.subscribe(d => {
603
+ // LastBubbleDataMap = new Map(d.map(_d => [_d.id, _d])) // key: id, value: datum
604
+ // })
605
+
606
+ // const highlightTarget$ = pluginParams$.pipe(
607
+ // takeUntil(destroy$),
608
+ // map(d => d.styles.highlightTarget),
609
+ // distinctUntilChanged()
610
+ // )
611
+
612
+ const bubblesSelection$ = combineLatest({
613
+ bubblesData: bubblesData$,
614
+ fullParams: layerParams$,
615
+ theme: context.theme$,
616
+ DatumContainerPositionMap: context.DatumContainerPositionMap$,
617
+ }).pipe(
618
+ takeUntil(destroy$),
619
+ switchMap(async (d) => d),
620
+ map(data => {
621
+ if (simulation) {
622
+ // 先停止,重新計算之後再restart
623
+ simulation.stop()
624
+ }
625
+
626
+ const bubblesSelection = renderBubbles({
627
+ selection: d3.select(svgG),
628
+ bubblesData: data.bubblesData,
629
+ fullParams: data.fullParams,
630
+ theme: data.theme,
631
+ })
632
+
633
+ simulation = createSimulation(bubblesSelection, data.fullParams)
634
+
635
+ simulation.nodes(data.bubblesData)
636
+
637
+ groupBubbles({
638
+ _simulation: simulation,
639
+ fullParams: data.fullParams,
640
+ DatumContainerPositionMap: data.DatumContainerPositionMap
641
+ // graphicWidth: data.layout.width,
642
+ // graphicHeight: data.layout.height
643
+ })
644
+
645
+ simulation!.alpha(1).restart()
646
+
647
+ return bubblesSelection
648
+ }),
649
+ shareReplay(1)
650
+ )
651
+
652
+ combineLatest({
653
+ bubblesSelection: bubblesSelection$,
654
+ computedData: context.computedData$,
655
+ // SeriesDataMap: context.SeriesDataMap$,
656
+ // highlightTarget: highlightTarget$,
657
+ }).pipe(
658
+ takeUntil(destroy$),
659
+ switchMap(async (d) => d)
660
+ ).subscribe(data => {
661
+
662
+ data.bubblesSelection
663
+ .on('mouseover', (event, datum) => {
664
+ // this.tooltip!.setDatum({
665
+ // data: d,
666
+ // x: d3.event.clientX,
667
+ // y: d3.event.clientY
668
+ // })
669
+
670
+ context.eventTrigger$.next({
671
+ // type: 'series',
672
+ // eventName: 'mouseover',
673
+ // pluginName: name,
674
+ // highlightTarget: data.highlightTarget,
675
+ // datum,
676
+ // series: data.SeriesDataMap.get(datum.seriesLabel)!,
677
+ // seriesIndex: datum.seriesIndex,
678
+ // seriesLabel: datum.seriesLabel,
679
+ // event,
680
+ // data: data.computedData
681
+ eventName: 'mouseover',
682
+ pluginName,
683
+ layerName,
684
+ target: datum,
685
+ event,
686
+ })
687
+ })
688
+ .on('mousemove', (event, datum) => {
689
+ // this.tooltip!.setDatum({
690
+ // x: d3.event.clientX,
691
+ // y: d3.event.clientY
692
+ // })
693
+
694
+ context.eventTrigger$.next({
695
+ // type: 'series',
696
+ // eventName: 'mousemove',
697
+ // pluginName: name,
698
+ // highlightTarget: data.highlightTarget,
699
+ // datum,
700
+ // series: data.SeriesDataMap.get(datum.seriesLabel)!,
701
+ // seriesIndex: datum.seriesIndex,
702
+ // seriesLabel: datum.seriesLabel,
703
+ // event,
704
+ // data: data.computedData
705
+ eventName: 'mousemove',
706
+ pluginName,
707
+ layerName,
708
+ target: datum,
709
+ event,
710
+ })
711
+ })
712
+ .on('mouseout', (event, datum) => {
713
+ // this.tooltip!.remove()
714
+
715
+ context.eventTrigger$.next({
716
+ // type: 'series',
717
+ // eventName: 'mouseout',
718
+ // pluginName: name,
719
+ // highlightTarget: data.highlightTarget,
720
+ // datum,
721
+ // series: data.SeriesDataMap.get(datum.seriesLabel)!,
722
+ // seriesIndex: datum.seriesIndex,
723
+ // seriesLabel: datum.seriesLabel,
724
+ // event,
725
+ // data: data.computedData
726
+ eventName: 'mouseout',
727
+ pluginName,
728
+ layerName,
729
+ target: datum,
730
+ event,
731
+ })
732
+ })
733
+ .on('click', (event, datum) => {
734
+
735
+ context.eventTrigger$.next({
736
+ // type: 'series',
737
+ // eventName: 'click',
738
+ // pluginName: name,
739
+ // highlightTarget: data.highlightTarget,
740
+ // datum,
741
+ // series: data.SeriesDataMap.get(datum.seriesLabel)!,
742
+ // seriesIndex: datum.seriesIndex,
743
+ // seriesLabel: datum.seriesLabel,
744
+ // event,
745
+ // data: data.computedData
746
+ eventName: 'click',
747
+ pluginName,
748
+ layerName,
749
+ target: datum,
750
+ event,
751
+ })
752
+ })
753
+ .call(drag(simulation) as any)
754
+
755
+
756
+ })
757
+
758
+ combineLatest({
759
+ bubblesSelection: bubblesSelection$,
760
+ // bubblesData: bubblesData$,
761
+ highlight: context.seriesHighlight$.pipe(
762
+ map(data => data.map(d => d.id))
763
+ ),
764
+ pluginParams: pluginParams$,
765
+ // fullParams: layerParams$,
766
+ // sumSeries: sumSeries$,
767
+ // // layout: observer.layout$,
768
+ // DatumContainerPositionMap: observer.DatumContainerPositionMap$,
769
+ }).pipe(
770
+ takeUntil(destroy$),
771
+ debounceTime(0)
772
+ ).subscribe(data => {
773
+ highlight({
774
+ bubblesSelection: data.bubblesSelection,
775
+ highlightIds: data.highlight,
776
+ pluginParams: data.pluginParams
777
+ })
778
+
779
+ // if (data.fullParams.highlightRIncrease) {
780
+ // setHighlightData ({
781
+ // data: data.bubblesData,
782
+ // highlightRIncrease: data.fullParams.highlightRIncrease,
783
+ // highlightIds: data.highlight
784
+ // })
785
+ // data.bubblesSelection.select('circle')
786
+ // // .transition()
787
+ // // .duration(200)
788
+ // .attr("r", (d) => d.r)
789
+
790
+ // force!.nodes(data.bubblesData)
791
+
792
+ // groupBubbles({
793
+ // fullParams: data.fullParams,
794
+ // DatumContainerPositionMap: data.DatumContainerPositionMap
795
+ // })
796
+ // }
797
+
798
+ })
799
+
800
+ return () => {
801
+ destroy$.next(undefined)
802
+ if (simulation) {
803
+ simulation.stop()
804
+ simulation = undefined
805
+ }
806
+ }
807
+ }
808
+ })