@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,1318 @@
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ first,
6
+ switchMap,
7
+ debounceTime,
8
+ takeUntil,
9
+ distinctUntilChanged,
10
+ shareReplay,
11
+ iif,
12
+ EMPTY,
13
+ Observable,
14
+ Subject,
15
+ BehaviorSubject
16
+ } from 'rxjs'
17
+ import type { Theme, EventData } from '@orbcharts/core'
18
+ import type { NetworkPlotPluginParams, NetworkPlotExtendContext, ForceDirectedBubblesParams } from '../types'
19
+ import { defineSVGLayer } from '@orbcharts/core'
20
+ import { validateObject } from '@orbcharts/core'
21
+ import { DEFAULT_FORCE_DIRECTED_BUBBLES_PARAMS } from "../defaults"
22
+ import { multivariateSelectionsObservable } from "../../../utils/multivariateObservables"
23
+ import { getColor, getDatumColor } from '../../../utils/orbchartsUtils'
24
+ import { createClassName, createUniID } from '../../../utils/orbchartsUtils'
25
+ import type { ComputedDatumGraphNode, ComputedDatumGraphEdge } from '../../../types/ComputedData'
26
+ import type { ContainerPosition, GraphicStyles, Layout } from '../../../types/PluginParams'
27
+ import { LAYER_INDEX_OF_GRAPHIC } from '../../../const/layerIndex'
28
+ import { renderCircleText } from '../../../utils/d3Graphics'
29
+ import { getMinMax } from '../../../utils/commonUtils'
30
+
31
+ // interface BubblesDatum extends ComputedDatumGraphNode {
32
+ // x: number
33
+ // y: number
34
+ // r: number
35
+ // _originR: number // 紀錄變化前的r
36
+ // }
37
+
38
+ type Zoom = {
39
+ xOffset: number
40
+ yOffset: number
41
+ scaleExtent: {
42
+ min: number
43
+ max: number
44
+ }
45
+ }
46
+
47
+ // d3 forceSimulation使用的node資料
48
+ type RenderNode = d3.SimulationNodeDatum & ComputedDatumGraphNode & {
49
+ r: number
50
+ }
51
+
52
+ // d3 forceSimulation使用的edge資料
53
+ interface RenderEdge extends ComputedDatumGraphEdge {
54
+ _source: RenderNode
55
+ _target: RenderNode
56
+ strokeWidth: number
57
+ markerId: string
58
+ }
59
+
60
+ // d3 forceSimulation使用的資料
61
+ type RenderData = {
62
+ nodes: (ComputedDatumGraphNode | RenderNode)[] // 經過d3 forceSimulation計算後的node才有座標資訊
63
+ edges: RenderEdge[]
64
+ }
65
+
66
+ interface D3DragEvent {
67
+ active: number
68
+ dx: number
69
+ dy: number
70
+ identifier: string
71
+ sourceEvent: MouseEvent
72
+ subject: RenderNode
73
+ target: any
74
+ type: string
75
+ x: number
76
+ y: number
77
+ }
78
+
79
+ type DragStatus = 'start' | 'drag' | 'end'
80
+
81
+ interface MarkerParams {
82
+ viewBox: string
83
+ d: string
84
+ pointerWidth: number
85
+ pointerHeight: number
86
+ // refX: number
87
+ }
88
+
89
+ interface MarkerDatum {
90
+ id: string
91
+ edgeId: string
92
+ strokeWidth: number
93
+ refX: number
94
+ }
95
+
96
+
97
+ // type BubblesSimulationDatum = BubblesDatum & d3.SimulationNodeDatum
98
+
99
+ const pluginName = 'NetworkPlot'
100
+ const layerName = 'ForceDirectedBubbles'
101
+
102
+ const baseLineHeight = 12 // 未變形前的字體大小(代入計算用而已,數字多少都不會有影響)
103
+
104
+ const gSelectionClassName = createClassName(pluginName, layerName, 'zoom-area')
105
+ const defsArrowMarkerId = createUniID(pluginName, layerName, 'arrow')
106
+ const defsArrowMarkerClassName = createClassName(pluginName, layerName, 'arrow-marker')
107
+ const edgeListGClassName = createClassName(pluginName, layerName, 'edge-list-g')
108
+ const edgeGClassName = createClassName(pluginName, layerName, 'edge-g')
109
+ const edgeArrowPathClassName = createClassName(pluginName, layerName, 'edge-arrow-path')
110
+ const edgeLabelGClassName = createClassName(pluginName, layerName, 'edge-label-g')
111
+ const edgeLabelClassName = createClassName(pluginName, layerName, 'edge-label')
112
+ const nodeListGClassName = createClassName(pluginName, layerName, 'node-list-g')
113
+ const nodeGClassName = createClassName(pluginName, layerName, 'node-g')
114
+ const nodeCircleClassName = createClassName(pluginName, layerName, 'node-circle')
115
+ const nodeLabelGClassName = createClassName(pluginName, layerName, 'node-label-g')
116
+ const nodeLabelClassName = createClassName(pluginName, layerName, 'node-label')
117
+
118
+
119
+ // let force: d3.Simulation<d3.SimulationNodeDatum, undefined> | undefined
120
+
121
+ function createSimulation (layout: Layout, layerParams: ForceDirectedBubblesParams) {
122
+ return d3.forceSimulation()
123
+ .velocityDecay(layerParams.force.velocityDecay)
124
+ .alphaDecay(layerParams.force.alphaDecay)
125
+ .force(
126
+ "link",
127
+ d3.forceLink()
128
+ .id((d: d3.SimulationNodeDatum & ComputedDatumGraphNode) => d.id)
129
+ .strength(1)
130
+ .distance((d: d3.SimulationLinkDatum<d3.SimulationNodeDatum & ComputedDatumGraphNode>) => {
131
+ // if (d.direction === 'top') {
132
+ // return 200
133
+ // } else {
134
+ // return 250
135
+ // }
136
+ return layerParams.force.linkDistance
137
+ })
138
+ )
139
+ .force("charge", d3.forceManyBody().strength(layerParams.force.nodeStrength))
140
+ .force("collision", d3.forceCollide(layerParams.bubble.radiusMax).strength(1)) // 先以最大值設定
141
+ .force("center", d3.forceCenter(layout.width / 2, layout.height / 2))
142
+
143
+ }
144
+
145
+ function translateFn (d: any): string {
146
+ // console.log('translateFn', d)
147
+ return "translate(" + d.x + "," + d.y + ")";
148
+ }
149
+
150
+ function translateCenterFn (d: any): string {
151
+ // console.log('translateCenterFn', d)
152
+ const x = d.source.x + ((d.target.x - d.source.x) / 2) // 置中的話除2
153
+ const y = d.source.y + ((d.target.y - d.source.y) / 2) // 置中的話除2
154
+ return "translate(" + x + "," + y + ")";
155
+ }
156
+
157
+ function linkArcFn (d: RenderEdge): string {
158
+ // console.log('linkArcFn', d)
159
+
160
+ // const dx = d.target.x - d.source.x,
161
+ // dy = d.target.y - d.source.y
162
+ // dr讓方向線變成有弧度的
163
+ // dr = Math.sqrt(dx * dx + dy * dy);
164
+ // return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
165
+
166
+ // 直線
167
+ return "M" + d._source.x + "," + d._source.y + " L" + d._target.x + "," + d._target.y;
168
+
169
+
170
+ }
171
+
172
+
173
+
174
+ function renderArrowMarker ({ defsSelection, markerParams, markerData, layerParams, theme }: {
175
+ defsSelection: d3.Selection<SVGDefsElement, any, any, unknown>
176
+ markerParams: MarkerParams
177
+ markerData: MarkerDatum[]
178
+ layerParams: ForceDirectedBubblesParams
179
+ theme: Theme
180
+ }) {
181
+ return defsSelection
182
+ .selectAll<SVGMarkerElement, any>(`marker.${defsArrowMarkerClassName}`)
183
+ .data(markerData)
184
+ .join(
185
+ enter => {
186
+ const enterSelection = enter
187
+ .append("marker")
188
+ .classed(defsArrowMarkerClassName, true)
189
+ .attr("viewBox", markerParams.viewBox)
190
+ .attr("orient", "auto")
191
+ enterSelection.append("path")
192
+ .attr("d", markerParams.d)
193
+ return enterSelection
194
+ },
195
+ update => {
196
+ return update
197
+ },
198
+ exit => {
199
+ return exit.remove()
200
+ }
201
+ )
202
+ .attr('id', d => d.id)
203
+ .attr('fill', d => getColor(layerParams.arrow.colorType, theme ))
204
+ .attr("markerWidth", markerParams.pointerWidth)
205
+ .attr("markerHeight", markerParams.pointerHeight)
206
+ .attr('refX', d => d.refX)
207
+ .attr("refY", 0)
208
+
209
+
210
+ }
211
+
212
+ // function drag (): d3.DragBehavior<Element, unknown, unknown> {
213
+ // let originHighlightLockMode: boolean // 拖拽前的highlightLockMode
214
+
215
+ // return d3.drag()
216
+ // .on("start", (event: D3DragEvent) => {
217
+ // console.log('start', event.sourceEvent)
218
+ // // if (this.params.lockMode) {
219
+ // // return
220
+ // // }
221
+ // // if (!d3.event.active) {
222
+ // // this.forceRestart()
223
+ // // }
224
+ // // d.fx = d.x
225
+ // // d.fy = d.y
226
+
227
+ // // // 鎖定模式才不會在拖拽過程式觸發到其他事件造成衝突
228
+ // // originHighlightLockMode = this.highlightLockMode
229
+ // // this.highlightLockMode = true
230
+ // // this.noneStopMode = true
231
+ // // // 動畫會有點卡住所以乾脆拿掉
232
+ // // if(this.tooltip != null) {
233
+ // // this.tooltip.remove()
234
+ // // }
235
+ // })
236
+ // .on("drag", function (event: D3DragEvent) {
237
+ // console.log('drag', event)
238
+ // // if (this.params.lockMode) {
239
+ // // return
240
+ // // }
241
+ // // if (!d3.event.active) {
242
+ // // this.force.alphaTarget(0)
243
+ // // }
244
+ // // d.fx = d3.event.x
245
+ // // d.fy = d3.event.y
246
+ // // d3.select(this).attr({
247
+ // // 'cx': event.x,
248
+ // // 'cy': event.y,
249
+ // // })
250
+ // d3.select(this)
251
+ // .attr('fx', event.x)
252
+ // .attr('fy', event.y)
253
+ // })
254
+ // .on("end", (event: D3DragEvent) => {
255
+ // console.log('end', event)
256
+ // // if (this.params.lockMode) {
257
+ // // return
258
+ // // }
259
+ // // d.fx = null
260
+ // // d.fy = null
261
+
262
+ // // this.highlightLockMode = originHighlightLockMode // 還原拖拽前的highlightLockMode
263
+ // // this.noneStopMode = false
264
+ // // if (this.highlightLockMode) {
265
+ // // this.forceStop()
266
+ // // }
267
+ // })
268
+ // }
269
+
270
+ function drag (simulation: d3.Simulation<d3.SimulationNodeDatum, undefined>, dragStatus$: BehaviorSubject<DragStatus>) {
271
+ function dragstarted (event: D3DragEvent, node: RenderNode) {
272
+ if (!event.active) {
273
+ simulation.alphaTarget(0.3).restart()
274
+ }
275
+ event.subject.fx = event.subject.x
276
+ event.subject.fy = event.subject.y
277
+
278
+ dragStatus$.next('start')
279
+ }
280
+
281
+ function dragged (event: D3DragEvent, node: RenderNode) {
282
+ event.subject.fx = event.x
283
+ event.subject.fy = event.y
284
+
285
+ dragStatus$.next('drag')
286
+ }
287
+
288
+ function dragended (event: D3DragEvent, node: RenderNode) {
289
+ if (!event.active) {
290
+ simulation.alphaTarget(0)
291
+ }
292
+ event.subject.fx = null
293
+ event.subject.fy = null
294
+
295
+ dragStatus$.next('end')
296
+ }
297
+
298
+ return d3.drag()
299
+ .on("start", dragstarted)
300
+ .on("drag", dragged)
301
+ .on("end", dragended)
302
+ }
303
+
304
+ function renderNodeG ({ nodeListGSelection, nodes }: {
305
+ nodeListGSelection: d3.Selection<SVGGElement, any, any, unknown>
306
+ nodes: RenderNode[]
307
+ }) {
308
+ return nodeListGSelection.selectAll<SVGGElement, RenderNode>('g')
309
+ .data(nodes, d => d.id)
310
+ .join(
311
+ enter => {
312
+ const enterSelection = enter
313
+ .append('g')
314
+ .classed(nodeGClassName, true)
315
+ // .attr('cursor', 'pointer')
316
+ return enterSelection
317
+ },
318
+ update => {
319
+ return update
320
+ },
321
+ exit => {
322
+ return exit.remove()
323
+ }
324
+ )
325
+ }
326
+
327
+ function renderNodeCircle ({ nodeGSelection, layerParams, theme }: {
328
+ nodeGSelection: d3.Selection<SVGGElement, RenderNode, any, unknown>
329
+ layerParams: ForceDirectedBubblesParams
330
+ theme: Theme
331
+ }) {
332
+ nodeGSelection
333
+ .each((data,i,g) => {
334
+ const gSelection = d3.select(g[i])
335
+ gSelection.selectAll<SVGCircleElement, ComputedDatumGraphEdge>('circle')
336
+ .data([data])
337
+ .join(
338
+ enter => {
339
+ const enterSelection = enter
340
+ .append('circle')
341
+ .classed(nodeCircleClassName, true)
342
+ .attr('cursor', 'pointer')
343
+ return enterSelection
344
+ },
345
+ update => {
346
+ return update
347
+ },
348
+ exit => {
349
+ return exit.remove()
350
+ }
351
+ )
352
+ .attr('r', d => d.r)
353
+ .attr('fill', d => getDatumColor({ datum: d, colorType: layerParams.bubble.fillColorType, theme }))
354
+ .attr('stroke', d => getDatumColor({ datum: d, colorType: layerParams.bubble.strokeColorType, theme }))
355
+ .attr('stroke-width', layerParams.bubble.strokeWidth)
356
+ .attr('style', d => layerParams.bubble.styleFn(d))
357
+
358
+ })
359
+ .attr("text-anchor", "middle")
360
+ .attr('font-size', baseLineHeight)
361
+ .each((d,i,g) => {
362
+ const gSelection = d3.select(g[i])
363
+
364
+ gSelection.call(renderCircleText, {
365
+ text: d.name,
366
+ radius: d.r * layerParams.bubbleLabel.fillRate,
367
+ lineHeight: baseLineHeight * layerParams.bubbleLabel.lineHeight,
368
+ isBreakAll: d.name.length <= layerParams.bubbleLabel.maxLineLength
369
+ ? false
370
+ : layerParams.bubbleLabel.wordBreakAll
371
+ })
372
+
373
+ // -- text color --
374
+ gSelection.select('text').attr('fill', _ => getDatumColor({
375
+ datum: d,
376
+ colorType: layerParams.bubbleLabel.colorType,
377
+ theme: theme
378
+ }))
379
+
380
+
381
+ })
382
+
383
+ nodeGSelection.select('text')
384
+ .attr('pointer-events', 'none')
385
+ .attr('style', d => layerParams.bubbleLabel.styleFn(d))
386
+
387
+ return nodeGSelection.select<SVGCircleElement>(`circle.${nodeCircleClassName}`)
388
+ }
389
+
390
+ // function renderNodeLabelG ({ nodeGSelection }: {
391
+ // nodeGSelection: d3.Selection<SVGGElement, any, any, unknown>
392
+ // }) {
393
+ // nodeGSelection.each((data,i,g) => {
394
+ // const gSelection = d3.select(g[i])
395
+ // gSelection.selectAll<SVGGElement, RenderNode>('g')
396
+ // .data([data])
397
+ // .join(
398
+ // enter => {
399
+ // const enterSelection = enter
400
+ // .append('g')
401
+ // .classed(nodeLabelGClassName, true)
402
+ // // .attr('cursor', 'pointer')
403
+ // return enterSelection
404
+ // },
405
+ // update => {
406
+ // return update
407
+ // },
408
+ // exit => {
409
+ // return exit.remove()
410
+ // }
411
+ // )
412
+ // })
413
+
414
+ // return nodeGSelection.select<SVGTextElement>(`g.${nodeLabelGClassName}`)
415
+ // }
416
+
417
+ // function renderNodeLabel ({ nodeLabelGSelection, layerParams, fullChartParams }: {
418
+ // nodeLabelGSelection: d3.Selection<SVGGElement, RenderNode, any, unknown>
419
+ // layerParams: ForceDirectedBubblesParams
420
+ // fullChartParams: ChartParams
421
+ // }) {
422
+ // nodeLabelGSelection.each((data,i,g) => {
423
+ // const gSelection = d3.select(g[i])
424
+ // gSelection.selectAll<SVGTextElement, RenderNode>('text')
425
+ // .data([data], d => d.id)
426
+ // .join(
427
+ // enter => {
428
+ // const enterSelection = enter
429
+ // .append('text')
430
+ // .classed(nodeLabelClassName, true)
431
+ // // .attr('cursor', 'pointer')
432
+ // .attr('text-anchor', 'middle')
433
+ // .attr('pointer-events', 'none')
434
+ // return enterSelection
435
+ // },
436
+ // update => {
437
+ // return update
438
+ // },
439
+ // exit => {
440
+ // return exit.remove()
441
+ // }
442
+ // )
443
+ // .text(d => d.label)
444
+ // .attr('transform', d => `translate(0, ${- d.r - 10})`)
445
+ // .attr('fill', d => getDatumColor({ datum: d, colorType: layerParams.node.labelColorType, fullChartParams }))
446
+ // .attr('font-size', fullChartParams.styles.textSize)
447
+ // .attr('style', d => layerParams.node.labelStyleFn(d))
448
+ // })
449
+
450
+ // return nodeLabelGSelection.select<SVGTextElement>(`text.${nodeLabelClassName}`)
451
+ // }
452
+
453
+ function renderEdgeG ({ edgeListGSelection, edges }: {
454
+ edgeListGSelection: d3.Selection<SVGGElement, any, any, unknown>
455
+ edges: RenderEdge[]
456
+ }) {
457
+ return edgeListGSelection.selectAll<SVGGElement, RenderEdge>('g')
458
+ .data(edges, d => d.id)
459
+ .join(
460
+ enter => {
461
+ const enterSelection = enter
462
+ .append('g')
463
+ .classed(edgeGClassName, true)
464
+ // .attr('cursor', 'pointer')
465
+ return enterSelection
466
+ },
467
+ update => {
468
+ return update
469
+ },
470
+ exit => {
471
+ return exit.remove()
472
+ }
473
+ )
474
+ }
475
+
476
+ function renderEdgeArrowPath ({ edgeGSelection, layerParams, theme }: {
477
+ edgeGSelection: d3.Selection<SVGGElement, RenderEdge, any, unknown>
478
+ layerParams: ForceDirectedBubblesParams
479
+ theme: Theme
480
+ }) {
481
+ edgeGSelection.each((data,i,g) => {
482
+ const gSelection = d3.select(g[i])
483
+ gSelection.selectAll<SVGPathElement, ComputedDatumGraphEdge>('path')
484
+ .data([data])
485
+ .join(
486
+ enter => {
487
+ return enter
488
+ .append('path')
489
+ .classed(edgeArrowPathClassName, true)
490
+ },
491
+ update => {
492
+ return update
493
+ },
494
+ exit => {
495
+ return exit.remove()
496
+ }
497
+ )
498
+ .attr('marker-end', d => `url(#${d.markerId})`)
499
+ .attr('stroke', d => getDatumColor({ datum: d.data, colorType: layerParams.arrow.colorType, theme }))
500
+ .attr('stroke-width', d => d.strokeWidth)
501
+ .attr('style', d => layerParams.arrow.styleFn(d))
502
+ })
503
+
504
+ return edgeGSelection.select<SVGPathElement>(`path.${edgeArrowPathClassName}`)
505
+ }
506
+
507
+ function renderEdgeLabelG ({ edgeGSelection }: {
508
+ edgeGSelection: d3.Selection<SVGGElement, any, any, unknown>
509
+ }) {
510
+ edgeGSelection.each((data,i,g) => {
511
+ const gSelection = d3.select(g[i])
512
+ gSelection.selectAll<SVGGElement, RenderEdge>('g')
513
+ .data([data])
514
+ .join(
515
+ enter => {
516
+ const enterSelection = enter
517
+ .append('g')
518
+ .classed(edgeLabelGClassName, true)
519
+ // .attr('cursor', 'pointer')
520
+ return enterSelection
521
+ },
522
+ update => {
523
+ return update
524
+ },
525
+ exit => {
526
+ return exit.remove()
527
+ }
528
+ )
529
+ })
530
+
531
+ return edgeGSelection.select<SVGTextElement>(`g.${edgeLabelGClassName}`)
532
+ }
533
+
534
+ function renderEdgeLabel ({ edgeLabelGSelection, layerParams, theme }: {
535
+ edgeLabelGSelection: d3.Selection<SVGGElement, RenderEdge, any, unknown>
536
+ layerParams: ForceDirectedBubblesParams
537
+ theme: Theme
538
+ }) {
539
+ edgeLabelGSelection.each((data,i,g) => {
540
+ const gSelection = d3.select(g[i])
541
+ gSelection.selectAll<SVGTextElement, RenderEdge>('text')
542
+ .data([data], d => d.id)
543
+ .join(
544
+ enter => {
545
+ const enterSelection = enter
546
+ .append('text')
547
+ .classed(edgeLabelClassName, true)
548
+ // .attr('cursor', 'pointer')
549
+ .attr('text-anchor', 'middle')
550
+ .attr('pointer-events', 'none')
551
+ return enterSelection
552
+ },
553
+ update => {
554
+ return update
555
+ },
556
+ exit => {
557
+ return exit.remove()
558
+ }
559
+ )
560
+ .text(d => d.name)
561
+ .attr('fill', d => getDatumColor({ datum: d, colorType: layerParams.arrowLabel.colorType, theme }))
562
+ .attr('font-size', theme.fontSize)
563
+ .attr('style', d => layerParams.arrowLabel.styleFn(d))
564
+ })
565
+
566
+ return edgeLabelGSelection.select<SVGTextElement>(`text.${edgeLabelClassName}`)
567
+ }
568
+
569
+ function highlightNodes ({ nodeGSelection, edgeGSelection, highlightIds, styles }: {
570
+ nodeGSelection: d3.Selection<SVGGElement, RenderNode, SVGGElement, any>
571
+ edgeGSelection: d3.Selection<SVGGElement, RenderEdge, SVGGElement, any>
572
+ styles: GraphicStyles
573
+ highlightIds: string[]
574
+ }) {
575
+ nodeGSelection.interrupt('highlight')
576
+ edgeGSelection.interrupt('highlight')
577
+ // console.log(highlightIds)
578
+ if (!highlightIds.length) {
579
+ nodeGSelection
580
+ .transition('highlight')
581
+ .style('opacity', 1)
582
+ edgeGSelection
583
+ .transition('highlight')
584
+ .style('opacity', 1)
585
+ return
586
+ }
587
+
588
+ edgeGSelection
589
+ .style('opacity', styles.unhighlightedOpacity)
590
+
591
+ nodeGSelection.each((d, i, n) => {
592
+ const segment = d3.select(n[i])
593
+
594
+ if (highlightIds.includes(d.id)) {
595
+ segment
596
+ .style('opacity', 1)
597
+ .transition('highlight')
598
+ .ease(d3.easeElastic)
599
+ .duration(500)
600
+ } else {
601
+ // 取消
602
+ segment
603
+ .style('opacity', styles.unhighlightedOpacity)
604
+ }
605
+ })
606
+ }
607
+
608
+ // 暫不處理edge的highlight
609
+ // function highlightEdges ({ edgeGSelection, highlightIds, fullChartParams }: {
610
+ // edgeGSelection: d3.Selection<SVGGElement, RenderEdge, SVGGElement, any>
611
+ // fullChartParams: ChartParams
612
+ // highlightIds: string[]
613
+ // }) {
614
+ // edgeGSelection.interrupt('highlight')
615
+
616
+ // if (!highlightIds.length) {
617
+ // edgeGSelection
618
+ // .transition('highlight')
619
+ // .style('opacity', 1)
620
+ // return
621
+ // }
622
+
623
+ // edgeGSelection.each((d, i, n) => {
624
+ // const segment = d3.select(n[i])
625
+
626
+ // if (highlightIds.includes(d.id)) {
627
+ // segment
628
+ // .style('opacity', 1)
629
+ // .transition('highlight')
630
+ // .ease(d3.easeElastic)
631
+ // .duration(500)
632
+ // } else {
633
+ // // 取消放大
634
+ // segment
635
+ // .style('opacity', fullChartParams.styles.unhighlightedOpacity)
636
+ // }
637
+ // })
638
+ // }
639
+
640
+ export const ForceDirectedBubbles = defineSVGLayer<NetworkPlotExtendContext, NetworkPlotPluginParams, ForceDirectedBubblesParams>({
641
+ name: layerName,
642
+ defaultParams: DEFAULT_FORCE_DIRECTED_BUBBLES_PARAMS,
643
+ layerIndex: LAYER_INDEX_OF_GRAPHIC,
644
+ initShow: false,
645
+ validator: (params) => {
646
+ const result = validateObject(params, {
647
+ bubble: {
648
+ toBeTypes: ['object']
649
+ },
650
+ bubbleLabel: {
651
+ toBeTypes: ['object']
652
+ },
653
+ arrow: {
654
+ toBeTypes: ['object']
655
+ },
656
+ arrowLabel: {
657
+ toBeTypes: ['object']
658
+ },
659
+ force: {
660
+ toBeTypes: ['object']
661
+ },
662
+ zoomable: {
663
+ toBeTypes: ['boolean']
664
+ },
665
+ transform: {
666
+ toBeTypes: ['object']
667
+ },
668
+ scaleExtent: {
669
+ toBeTypes: ['object']
670
+ }
671
+ })
672
+ if (params.bubble) {
673
+ const bubbleResult = validateObject(params.bubble, {
674
+ radiusMin: {
675
+ toBeTypes: ['number']
676
+ },
677
+ radiusMax: {
678
+ toBeTypes: ['number']
679
+ },
680
+ arcScaleType: {
681
+ toBe: '"area" | "radius"',
682
+ test: (value) => value === 'area' || value === 'radius'
683
+ },
684
+ fillColorType: {
685
+ toBeOption: 'ColorType'
686
+ },
687
+ strokeColorType: {
688
+ toBeOption: 'ColorType'
689
+ },
690
+ strokeWidth: {
691
+ toBeTypes: ['number']
692
+ },
693
+ styleFn: {
694
+ toBeTypes: ['Function']
695
+ },
696
+ })
697
+ if (bubbleResult.status === 'error') {
698
+ return bubbleResult
699
+ }
700
+ }
701
+ if (params.bubbleLabel) {
702
+ const bubbleLabelResult = validateObject(params.bubbleLabel, {
703
+ fillRate: {
704
+ toBeTypes: ['number']
705
+ },
706
+ lineHeight: {
707
+ toBeTypes: ['number']
708
+ },
709
+ maxLineLength: {
710
+ toBeTypes: ['number']
711
+ },
712
+ colorType: {
713
+ toBeOption: 'ColorType'
714
+ },
715
+ styleFn: {
716
+ toBeTypes: ['Function']
717
+ },
718
+ })
719
+ if (bubbleLabelResult.status === 'error') {
720
+ return bubbleLabelResult
721
+ }
722
+ }
723
+ if (params.arrow) {
724
+ const arrowResult = validateObject(params.arrow, {
725
+ colorType: {
726
+ toBeOption: 'ColorType'
727
+ },
728
+ strokeWidthMin: {
729
+ toBeTypes: ['number']
730
+ },
731
+ strokeWidthMax: {
732
+ toBeTypes: ['number']
733
+ },
734
+ pointerWidth: {
735
+ toBeTypes: ['number']
736
+ },
737
+ pointerHeight: {
738
+ toBeTypes: ['number']
739
+ },
740
+ styleFn: {
741
+ toBeTypes: ['Function']
742
+ },
743
+ })
744
+ if (arrowResult.status === 'error') {
745
+ return arrowResult
746
+ }
747
+ }
748
+ if (params.arrowLabel) {
749
+ const arrowLabelResult = validateObject(params.arrowLabel, {
750
+ colorType: {
751
+ toBeOption: 'ColorType'
752
+ },
753
+ sizeFixed: {
754
+ toBeTypes: ['boolean']
755
+ },
756
+ styleFn: {
757
+ toBeTypes: ['Function']
758
+ },
759
+ })
760
+ if (arrowLabelResult.status === 'error') {
761
+ return arrowLabelResult
762
+ }
763
+ }
764
+ if (params.force) {
765
+ const forceResult = validateObject(params.force, {
766
+ nodeStrength: {
767
+ toBeTypes: ['number']
768
+ },
769
+ linkDistance: {
770
+ toBeTypes: ['number']
771
+ },
772
+ velocityDecay: {
773
+ toBeTypes: ['number']
774
+ },
775
+ alphaDecay: {
776
+ toBeTypes: ['number']
777
+ },
778
+ })
779
+ if (forceResult.status === 'error') {
780
+ return forceResult
781
+ }
782
+ }
783
+ if (params.transform) {
784
+ const transformResult = validateObject(params.transform, {
785
+ x: {
786
+ toBeTypes: ['number']
787
+ },
788
+ y: {
789
+ toBeTypes: ['number']
790
+ },
791
+ k: {
792
+ toBeTypes: ['number']
793
+ },
794
+ })
795
+ if (transformResult.status === 'error') {
796
+ return transformResult
797
+ }
798
+ }
799
+ if (params.scaleExtent) {
800
+ const scaleExtentResult = validateObject(params.scaleExtent, {
801
+ min: {
802
+ toBeTypes: ['number']
803
+ },
804
+ max: {
805
+ toBeTypes: ['number']
806
+ },
807
+ })
808
+ if (scaleExtentResult.status === 'error') {
809
+ return scaleExtentResult
810
+ }
811
+ }
812
+ return result
813
+ },
814
+ setup: ({ svgG, pluginParams$, layerParams$, context }) => {
815
+
816
+ const destroy$ = new Subject()
817
+
818
+ const rootSelection = d3.select(context.svg)
819
+ const selection = d3.select(svgG)
820
+ const gSelection = selection.append('g').classed(gSelectionClassName, true)
821
+ const defsSelection = gSelection.append('defs')
822
+ const edgeListGSelection = gSelection.append('g').classed(edgeListGClassName, true)
823
+ const nodeListGSelection = gSelection.append('g').classed(nodeListGClassName, true)
824
+
825
+ let nodeGSelection: d3.Selection<SVGGElement, RenderNode, SVGGElement, any> | undefined
826
+ let nodeCircleSelection: d3.Selection<SVGCircleElement, RenderNode, SVGGElement, any> | undefined
827
+ // let nodeLabelGSelection: d3.Selection<SVGGElement, RenderNode, SVGGElement, any> | undefined
828
+ // let nodeLabelSelection: d3.Selection<SVGTextElement, RenderNode, SVGGElement, any> | undefined
829
+ let edgeGSelection: d3.Selection<SVGGElement, RenderEdge, SVGGElement, any> | undefined
830
+ let edgeArrowSelection: d3.Selection<SVGPathElement, RenderEdge, SVGGElement, any> | undefined
831
+ let edgeLabelGSelection: d3.Selection<SVGGElement, RenderEdge, SVGGElement, any> | undefined
832
+ let edgeLabelSelection: d3.Selection<SVGTextElement, RenderEdge, SVGGElement, any> | undefined
833
+
834
+ const dragStatus$ = new BehaviorSubject<DragStatus>('end') // start, drag, end
835
+ const mouseEvent$ = new Subject<EventData>()
836
+
837
+ context.layout$
838
+ .pipe(
839
+ takeUntil(destroy$)
840
+ )
841
+ .subscribe(layout => {
842
+ selection
843
+ .attr('transform', `translate(${layout.left}, ${layout.top})`)
844
+ })
845
+
846
+ // init zoom
847
+ const d3Zoom$ = layerParams$.pipe(
848
+ takeUntil(destroy$),
849
+ // map(d => d.scaleExtent),
850
+ // distinctUntilChanged((a, b) => String(a) === String(b)),
851
+ // first(),
852
+ map(data => {
853
+ let d3Zoom = data.zoomable
854
+ ? d3.zoom().on('zoom', (event) => {
855
+ // console.log(event)
856
+ // this.svgGroup.attr('transform', `translate(
857
+ // ${event.transform.x + (this.zoom.xOffset * event.transform.k)},
858
+ // ${event.transform.y + (this.zoom.yOffset * event.transform.k)}
859
+ // ) scale(
860
+ // ${event.transform.k}
861
+ // )`)
862
+ gSelection.attr('transform', `translate(
863
+ ${event.transform.x},
864
+ ${event.transform.y}
865
+ ) scale(
866
+ ${event.transform.k}
867
+ )`)
868
+
869
+ // if (data.node.labelSizeFixed && nodeLabelSelection) {
870
+ // // 反向 scale 抵消掉放大縮小
871
+ // nodeLabelSelection.attr('transform', `scale(${1 / event.transform.k})`)
872
+ // }
873
+ if (data.arrowLabel.sizeFixed && edgeLabelSelection) {
874
+ // 反向 scale 抵消掉放大縮小
875
+ edgeLabelSelection.attr('transform', `scale(${1 / event.transform.k})`)
876
+ }
877
+ })
878
+ : d3.zoom().on('zoom', null)
879
+ if (data.scaleExtent) {
880
+ d3Zoom.scaleExtent([data.scaleExtent.min, data.scaleExtent.max])
881
+ }
882
+ rootSelection.call(d3Zoom)
883
+
884
+ return d3Zoom
885
+ }),
886
+ // shareReplay(1)
887
+ )
888
+
889
+ // zoom transform
890
+ combineLatest({
891
+ d3Zoom: d3Zoom$,
892
+ transform: layerParams$.pipe(
893
+ takeUntil(destroy$),
894
+ map(d => d.transform),
895
+ )
896
+ }).pipe(
897
+ takeUntil(destroy$),
898
+ debounceTime(0)
899
+ ).subscribe(data => {
900
+ // console.log('call')
901
+ selection.call(
902
+ data.d3Zoom.transform, d3.zoomIdentity
903
+ .translate(data.transform.x, data.transform.y)
904
+ .scale(data.transform.k)
905
+ )
906
+ })
907
+
908
+
909
+ const simulation$: Observable<d3.Simulation<d3.SimulationNodeDatum, undefined>> = combineLatest({
910
+ layout: context.layout$.pipe(
911
+ first() // 只使用第一次的尺寸(置中)
912
+ ),
913
+ layerParams: layerParams$
914
+ }).pipe(
915
+ takeUntil(destroy$),
916
+ debounceTime(0),
917
+ map(data => createSimulation(data.layout, data.layerParams)),
918
+ shareReplay(1)
919
+ )
920
+
921
+ const nodeMinMaxValue$ = context.computedData$.pipe(
922
+ takeUntil(destroy$),
923
+ map(data => {
924
+ const hadValueData = data.nodes.filter(d => d.value != undefined)
925
+ if (!hadValueData.length) {
926
+ return [0, 2] // 給預設值
927
+ }
928
+ const minMax = getMinMax(data.nodes.map(d => d.value))
929
+ if (hadValueData.length == 1 || minMax[0] === minMax[1]) {
930
+ return [minMax[0] - 1, minMax[1] + 1] // 避免最大最小值相同
931
+ }
932
+ return minMax
933
+ }),
934
+ shareReplay(1)
935
+ )
936
+
937
+ const edgeMinMaxValue$ = context.computedData$.pipe(
938
+ takeUntil(destroy$),
939
+ map(data => {
940
+ const hadValueData = data.edges.filter(d => d.value != undefined)
941
+ if (!hadValueData.length) {
942
+ return [0, 2] // 給預設值
943
+ }
944
+ const minMax = getMinMax(data.edges.map(d => d.value))
945
+ if (hadValueData.length == 1 || minMax[0] === minMax[1]) {
946
+ return [minMax[0] - 1, minMax[1] + 1] // 避免最大最小值相同
947
+ }
948
+ return minMax
949
+ }),
950
+ shareReplay(1)
951
+ )
952
+
953
+ // 當無value時給的預設值
954
+ const defaultNodeValue$ = nodeMinMaxValue$.pipe(
955
+ takeUntil(destroy$),
956
+ map(data => (data[1] - data[0]) / 2) // 預設值為最大及最小的中間值
957
+ )
958
+
959
+ // 當無value時給的預設值
960
+ const defaultEdgeValue$ = edgeMinMaxValue$.pipe(
961
+ takeUntil(destroy$),
962
+ map(data => (data[1] - data[0]) / 2) // 預設值為最大及最小的中間值
963
+ )
964
+
965
+ const radiusScale$ = combineLatest({
966
+ nodeMinMaxValue: nodeMinMaxValue$,
967
+ layerParams: layerParams$
968
+ }).pipe(
969
+ takeUntil(destroy$),
970
+ switchMap(async (d) => d),
971
+ map(data => {
972
+ // console.log({ totalR: data.totalR, totalValue: data.totalValue })
973
+ return d3.scalePow()
974
+ .domain(data.nodeMinMaxValue)
975
+ .range([data.layerParams.bubble.radiusMin, data.layerParams.bubble.radiusMax])
976
+ .exponent(data.layerParams.bubble.arcScaleType === 'area'
977
+ ? 0.5 // 數值映射面積(0.5為取平方根)
978
+ : 1 // 數值映射半徑
979
+ )
980
+ })
981
+ )
982
+
983
+ const strokeWidthScale$ = combineLatest({
984
+ edgeMinMaxValue: edgeMinMaxValue$,
985
+ layerParams: layerParams$
986
+ }).pipe(
987
+ takeUntil(destroy$),
988
+ switchMap(async (d) => d),
989
+ map(data => {
990
+ return d3.scaleLinear()
991
+ .domain(data.edgeMinMaxValue)
992
+ .range([data.layerParams.arrow.strokeWidthMin, data.layerParams.arrow.strokeWidthMax])
993
+ })
994
+ )
995
+
996
+ // 先將未篩選的資料全部儲起來,就不會因為 visibleFilter 而重新計算
997
+ const RenderNodeMap$: Observable<Map<string, RenderNode>> = combineLatest({
998
+ computedData: context.computedData$,
999
+ radiusScale: radiusScale$,
1000
+ defaultNodeValue: defaultNodeValue$,
1001
+ }).pipe(
1002
+ takeUntil(destroy$),
1003
+ switchMap(async (d) => d),
1004
+ map(data => {
1005
+ return new Map(
1006
+ data.computedData.nodes.map(_d => {
1007
+ let d: RenderNode = _d as RenderNode
1008
+ d.r = data.radiusScale(d.value ?? data.defaultNodeValue)
1009
+ return [d.id, d]
1010
+ })
1011
+ )
1012
+ }),
1013
+ )
1014
+
1015
+ // 先將未篩選的資料全部儲起來,就不會因為 visibleFilter 而重新計算
1016
+ const RenderEdgeMap$: Observable<Map<string, RenderEdge>> = combineLatest({
1017
+ computedData: context.computedData$,
1018
+ strokeWidthScale: strokeWidthScale$,
1019
+ defaultEdgeValue: defaultEdgeValue$,
1020
+ NodeMap: RenderNodeMap$,
1021
+ }).pipe(
1022
+ takeUntil(destroy$),
1023
+ switchMap(async (d) => d),
1024
+ map(data => {
1025
+ return new Map(
1026
+ data.computedData.edges.map(_d => {
1027
+ let d: RenderEdge = _d as RenderEdge
1028
+ // d.source = _d.startNode // reference
1029
+ // d.target = _d.endNode
1030
+ d._source = data.NodeMap.get(_d.source)!
1031
+ d._target = data.NodeMap.get(_d.target)!
1032
+ d.strokeWidth = data.strokeWidthScale(d.value ?? data.defaultEdgeValue)
1033
+ d.markerId = `${defsArrowMarkerId}__${d.id}`
1034
+ return [d.id, d]
1035
+ })
1036
+ )
1037
+ }),
1038
+ )
1039
+
1040
+ const renderData$: Observable<RenderData> = combineLatest({
1041
+ visibleComputedData: context.visibleComputedData$,
1042
+ RenderNodeMap: RenderNodeMap$,
1043
+ RenderEdgeMap: RenderEdgeMap$,
1044
+ }).pipe(
1045
+ takeUntil(destroy$),
1046
+ switchMap(async (d) => d),
1047
+ map(data => {
1048
+ return {
1049
+ nodes: data.visibleComputedData.nodes.map(d => data.RenderNodeMap.get(d.id)!),
1050
+ edges: data.visibleComputedData.edges.map(d => data.RenderEdgeMap.get(d.id)!),
1051
+ }
1052
+ }),
1053
+ shareReplay(1)
1054
+ )
1055
+
1056
+ const markerParams$: Observable<MarkerParams> = layerParams$.pipe(
1057
+ takeUntil(destroy$),
1058
+ map(layerParams => {
1059
+ return {
1060
+ viewBox: `-${layerParams.arrow.pointerWidth} -${layerParams.arrow.pointerHeight / 2} ${layerParams.arrow.pointerWidth} ${layerParams.arrow.pointerHeight}`,
1061
+ d: `M${-layerParams.arrow.pointerWidth},${-layerParams.arrow.pointerHeight / 2}L0,0L${-layerParams.arrow.pointerWidth},${layerParams.arrow.pointerHeight / 2}`, // 箭頭的尖端為(0,0)
1062
+ pointerWidth: layerParams.arrow.pointerWidth,
1063
+ pointerHeight: layerParams.arrow.pointerHeight,
1064
+ }
1065
+ })
1066
+ )
1067
+
1068
+ const markerData$: Observable<MarkerDatum[]> = combineLatest({
1069
+ computedData: context.computedData$,
1070
+ layerParams: layerParams$,
1071
+ RenderNodeMap: RenderNodeMap$,
1072
+ RenderEdgeMap: RenderEdgeMap$,
1073
+ }).pipe(
1074
+ takeUntil(destroy$),
1075
+ debounceTime(0),
1076
+ map(data => {
1077
+ return data.computedData.edges.map(d => {
1078
+ const renderEdge = data.RenderEdgeMap.get(d.id)!
1079
+ const renderEndNode = data.RenderNodeMap.get(d.target)!
1080
+ return {
1081
+ id: renderEdge.markerId,
1082
+ edgeId: d.id,
1083
+ strokeWidth: renderEdge.strokeWidth,
1084
+ /* refX:修正marker位置(計算出和circle半徑相等的寬度)
1085
+ (1)circle半徑需加上 strokeWidth/2 是因為框線是以 circle 的邊緣往內及往外擴展,所以 stroke 多出來的寬度是一半而已
1086
+ (2)circle半徑需除以 path 寬度是因為「marker 的位置會受到 path 的stroke-width影響」,所以要進行修正
1087
+ (3)- 1 是要修正奇怪的誤差(不知原因)
1088
+ */
1089
+ refX: ((renderEndNode.r + (data.layerParams.bubble.strokeWidth / 2)) / renderEdge.strokeWidth) - 1
1090
+ }
1091
+ })
1092
+ }),
1093
+ )
1094
+
1095
+ // <marker> marker selection
1096
+ combineLatest({
1097
+ defsSelection,
1098
+ markerParams: markerParams$,
1099
+ markerData: markerData$,
1100
+ layerParams: layerParams$,
1101
+ theme: context.theme$
1102
+ }).pipe(
1103
+ takeUntil(destroy$),
1104
+ debounceTime(0),
1105
+ map(data => {
1106
+ return renderArrowMarker({
1107
+ defsSelection,
1108
+ markerParams: data.markerParams,
1109
+ markerData: data.markerData,
1110
+ layerParams: data.layerParams,
1111
+ theme: data.theme
1112
+ })
1113
+ })
1114
+ ).subscribe()
1115
+
1116
+
1117
+ combineLatest({
1118
+ renderData: renderData$,
1119
+ // computedData: context.computedData$,
1120
+ // CategoryNodeMap: context.CategoryNodeMap$,
1121
+ simulation: simulation$,
1122
+ layerParams: layerParams$,
1123
+ theme: context.theme$
1124
+ }).pipe(
1125
+ takeUntil(destroy$),
1126
+ debounceTime(0),
1127
+ ).subscribe(data => {
1128
+
1129
+ nodeGSelection = renderNodeG({
1130
+ nodeListGSelection: nodeListGSelection,
1131
+ nodes: data.renderData.nodes as RenderNode[],
1132
+ })
1133
+
1134
+ nodeCircleSelection = renderNodeCircle({
1135
+ nodeGSelection: nodeGSelection,
1136
+ layerParams: data.layerParams,
1137
+ theme: data.theme
1138
+ })
1139
+ nodeGSelection.call(drag(data.simulation, dragStatus$))
1140
+
1141
+ // nodeLabelGSelection = renderNodeLabelG({
1142
+ // nodeGSelection: nodeGSelection,
1143
+ // })
1144
+
1145
+ // nodeLabelSelection = renderNodeLabel({
1146
+ // nodeLabelGSelection: nodeLabelGSelection,
1147
+ // layerParams: data.layerParams,
1148
+ // fullChartParams: data.fullChartParams
1149
+ // })
1150
+
1151
+ edgeGSelection = renderEdgeG({
1152
+ edgeListGSelection: edgeListGSelection,
1153
+ edges: data.renderData.edges
1154
+ })
1155
+
1156
+ edgeArrowSelection = renderEdgeArrowPath({
1157
+ edgeGSelection: edgeGSelection,
1158
+ layerParams: data.layerParams,
1159
+ theme: data.theme
1160
+ })
1161
+
1162
+ edgeLabelGSelection = renderEdgeLabelG({
1163
+ edgeGSelection: edgeGSelection,
1164
+ })
1165
+
1166
+ edgeLabelSelection = renderEdgeLabel({
1167
+ edgeLabelGSelection: edgeLabelGSelection,
1168
+ layerParams: data.layerParams,
1169
+ theme: data.theme
1170
+ })
1171
+
1172
+ data.simulation.nodes(data.renderData.nodes)
1173
+ .on('tick', () => {
1174
+ edgeArrowSelection.attr('d', linkArcFn)
1175
+ nodeGSelection.attr('transform', translateFn)
1176
+ // nodeLabelGSelection.attr('transform', d => translateFn({
1177
+ // x: d.x,
1178
+ // y: d.y - d.r - 10
1179
+ // }))
1180
+ edgeLabelGSelection.attr('transform', d => translateCenterFn(d))
1181
+ })
1182
+ ;(data.simulation.force("link") as any).links(data.renderData.edges)
1183
+
1184
+ data.simulation.alpha(0.3).restart()
1185
+
1186
+ nodeCircleSelection
1187
+ .on('mouseover', (event, datum) => {
1188
+ event.stopPropagation()
1189
+
1190
+ mouseEvent$.next({
1191
+ // type: 'relationship',
1192
+ // eventName: 'mouseover',
1193
+ // pluginName,
1194
+ // highlightTarget: data.fullChartParams.highlightTarget,
1195
+ // datum: datum,
1196
+ // category: data.CategoryNodeMap.get(datum.categoryLabel)!,
1197
+ // categoryIndex: datum.categoryIndex,
1198
+ // categoryLabel: datum.categoryLabel,
1199
+ // event,
1200
+ // data: data.computedData
1201
+ eventName: 'mouseover',
1202
+ pluginName,
1203
+ layerName,
1204
+ target: datum,
1205
+ event
1206
+ })
1207
+ })
1208
+ .on('mousemove', (event, datum) => {
1209
+ event.stopPropagation()
1210
+
1211
+ mouseEvent$.next({
1212
+ // type: 'relationship',
1213
+ // eventName: 'mousemove',
1214
+ // pluginName,
1215
+ // highlightTarget: data.fullChartParams.highlightTarget,
1216
+ // datum: datum,
1217
+ // category: data.CategoryNodeMap.get(datum.categoryLabel)!,
1218
+ // categoryIndex: datum.categoryIndex,
1219
+ // categoryLabel: datum.categoryLabel,
1220
+ // event,
1221
+ // data: data.computedData
1222
+ eventName: 'mousemove',
1223
+ pluginName,
1224
+ layerName,
1225
+ target: datum,
1226
+ event
1227
+ })
1228
+ })
1229
+ .on('mouseout', (event, datum) => {
1230
+ event.stopPropagation()
1231
+
1232
+ mouseEvent$.next({
1233
+ // type: 'relationship',
1234
+ // eventName: 'mouseout',
1235
+ // pluginName,
1236
+ // highlightTarget: data.fullChartParams.highlightTarget,
1237
+ // datum: datum,
1238
+ // category: data.CategoryNodeMap.get(datum.categoryLabel)!,
1239
+ // categoryIndex: datum.categoryIndex,
1240
+ // categoryLabel: datum.categoryLabel,
1241
+ // event,
1242
+ // data: data.computedData
1243
+ eventName: 'mouseout',
1244
+ pluginName,
1245
+ layerName,
1246
+ target: datum,
1247
+ event
1248
+ })
1249
+ })
1250
+ .on('click', (event, datum) => {
1251
+ event.stopPropagation()
1252
+
1253
+ mouseEvent$.next({
1254
+ // type: 'relationship',
1255
+ // eventName: 'click',
1256
+ // pluginName,
1257
+ // highlightTarget: data.fullChartParams.highlightTarget,
1258
+ // datum: datum,
1259
+ // category: data.CategoryNodeMap.get(datum.categoryLabel)!,
1260
+ // categoryIndex: datum.categoryIndex,
1261
+ // categoryLabel: datum.categoryLabel,
1262
+ // event,
1263
+ // data: data.computedData
1264
+ eventName: 'click',
1265
+ pluginName,
1266
+ layerName,
1267
+ target: datum,
1268
+ event
1269
+ })
1270
+ })
1271
+ })
1272
+
1273
+ dragStatus$.pipe(
1274
+ distinctUntilChanged((a, b) => a === b),
1275
+ // 只有沒有托曳時才執行
1276
+ switchMap(d => iif(() => d === 'end', mouseEvent$, EMPTY))
1277
+ ).subscribe(data => {
1278
+ context.eventTrigger$.next(data)
1279
+ })
1280
+
1281
+ combineLatest({
1282
+ // renderData: renderData$,
1283
+ highlightNodes: context.graphHighlightNodes$.pipe(
1284
+ map(data => data.map(d => d.id))
1285
+ ),
1286
+ // highlightEdges: context.graphHighlightEdges$.pipe(
1287
+ // map(data => data.map(d => d.id))
1288
+ // ),
1289
+ styles: pluginParams$.pipe(
1290
+ map(d => d.styles)
1291
+ ),
1292
+ // layerParams: layerParams$,
1293
+ }).pipe(
1294
+ takeUntil(destroy$),
1295
+ debounceTime(0)
1296
+ ).subscribe(data => {
1297
+ if (!nodeGSelection || !edgeGSelection) {
1298
+ return
1299
+ }
1300
+
1301
+ highlightNodes({
1302
+ nodeGSelection,
1303
+ edgeGSelection,
1304
+ highlightIds: data.highlightNodes,
1305
+ styles: data.styles
1306
+ })
1307
+ // highlightEdges({
1308
+ // edgeGSelection,
1309
+ // highlightIds: data.highlightEdges,
1310
+ // fullChartParams: data.fullChartParams
1311
+ // })
1312
+ })
1313
+
1314
+ return () => {
1315
+ destroy$.next(undefined)
1316
+ }
1317
+ }
1318
+ })