@amcharts/amcharts5 5.16.2 → 5.17.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 (227) hide show
  1. package/.internal/charts/map/MapChartDefaultTheme.d.ts.map +1 -1
  2. package/.internal/charts/map/MapChartDefaultTheme.js +3 -0
  3. package/.internal/charts/map/MapChartDefaultTheme.js.map +1 -1
  4. package/.internal/charts/map/MapSankeyNodes.d.ts +155 -0
  5. package/.internal/charts/map/MapSankeyNodes.d.ts.map +1 -0
  6. package/.internal/charts/map/MapSankeyNodes.js +151 -0
  7. package/.internal/charts/map/MapSankeyNodes.js.map +1 -0
  8. package/.internal/charts/map/MapSankeySeries.d.ts +383 -0
  9. package/.internal/charts/map/MapSankeySeries.d.ts.map +1 -0
  10. package/.internal/charts/map/MapSankeySeries.js +794 -0
  11. package/.internal/charts/map/MapSankeySeries.js.map +1 -0
  12. package/.internal/core/Classes.d.ts +6 -0
  13. package/.internal/core/Classes.d.ts.map +1 -1
  14. package/.internal/core/Classes.js.map +1 -1
  15. package/.internal/core/Registry.js +1 -1
  16. package/.internal/core/Registry.js.map +1 -1
  17. package/.internal/core/util/Entity.d.ts.map +1 -1
  18. package/.internal/core/util/Entity.js +6 -0
  19. package/.internal/core/util/Entity.js.map +1 -1
  20. package/.internal/core/util/Math.d.ts +132 -49
  21. package/.internal/core/util/Math.d.ts.map +1 -1
  22. package/.internal/core/util/Math.js +151 -50
  23. package/.internal/core/util/Math.js.map +1 -1
  24. package/.internal/core/util/Object.d.ts +24 -1
  25. package/.internal/core/util/Object.d.ts.map +1 -1
  26. package/.internal/core/util/Object.js +24 -1
  27. package/.internal/core/util/Object.js.map +1 -1
  28. package/.internal/core/util/Utils.d.ts +69 -3
  29. package/.internal/core/util/Utils.d.ts.map +1 -1
  30. package/.internal/core/util/Utils.js +72 -18
  31. package/.internal/core/util/Utils.js.map +1 -1
  32. package/.internal/plugins/json/Classes-script.d.ts +6 -0
  33. package/.internal/plugins/json/Classes-script.d.ts.map +1 -1
  34. package/.internal/plugins/json/Classes-script.js +3 -0
  35. package/.internal/plugins/json/Classes-script.js.map +1 -1
  36. package/.internal/plugins/json/Classes.d.ts +6 -0
  37. package/.internal/plugins/json/Classes.d.ts.map +1 -1
  38. package/.internal/plugins/json/Classes.js +3 -0
  39. package/.internal/plugins/json/Classes.js.map +1 -1
  40. package/CHANGELOG.md +17 -8
  41. package/examples/javascript/flow-arc-horizontal/package.json +1 -1
  42. package/examples/javascript/flow-arc-vertical/package.json +1 -1
  43. package/examples/javascript/flow-chord/package.json +1 -1
  44. package/examples/javascript/flow-chord-directed/package.json +1 -1
  45. package/examples/javascript/flow-chord-non-ribbon/package.json +1 -1
  46. package/examples/javascript/flow-sankey/package.json +1 -1
  47. package/examples/javascript/gantt/package.json +1 -1
  48. package/examples/javascript/gantt-multilevel/package.json +1 -1
  49. package/examples/javascript/gantt-simple/package.json +1 -1
  50. package/examples/javascript/gauge/package.json +1 -1
  51. package/examples/javascript/gauge-bands/package.json +1 -1
  52. package/examples/javascript/hierarchy-force-directed/package.json +1 -1
  53. package/examples/javascript/hierarchy-pack/package.json +1 -1
  54. package/examples/javascript/hierarchy-partition/package.json +1 -1
  55. package/examples/javascript/hierarchy-sunburst/package.json +1 -1
  56. package/examples/javascript/hierarchy-tree/package.json +1 -1
  57. package/examples/javascript/hierarchy-treemap/package.json +1 -1
  58. package/examples/javascript/hierarchy-voronoi-treemap/package.json +1 -1
  59. package/examples/javascript/jest/package.json +1 -1
  60. package/examples/javascript/json-pie/package.json +1 -1
  61. package/examples/javascript/json-xy/package.json +1 -1
  62. package/examples/javascript/map-animating-along-lines/package.json +1 -1
  63. package/examples/javascript/map-clustered-points/package.json +1 -1
  64. package/examples/javascript/map-day-and-night/package.json +1 -1
  65. package/examples/javascript/map-globe-rotate-to-country/package.json +1 -1
  66. package/examples/javascript/map-globe-with-projected-circles/package.json +1 -1
  67. package/examples/javascript/map-sankey/README.md +6 -0
  68. package/examples/javascript/map-sankey/index.css +11 -0
  69. package/examples/javascript/map-sankey/index.html +13 -0
  70. package/examples/javascript/map-sankey/index.js +368 -0
  71. package/examples/javascript/map-sankey/package.json +17 -0
  72. package/examples/javascript/map-sankey/webpack.config.js +38 -0
  73. package/examples/javascript/map-sankey-waypoints/README.md +6 -0
  74. package/examples/javascript/map-sankey-waypoints/index.css +11 -0
  75. package/examples/javascript/map-sankey-waypoints/index.html +13 -0
  76. package/examples/javascript/map-sankey-waypoints/index.js +394 -0
  77. package/examples/javascript/map-sankey-waypoints/package.json +17 -0
  78. package/examples/javascript/map-sankey-waypoints/webpack.config.js +38 -0
  79. package/examples/javascript/map-with-bubbles/package.json +1 -1
  80. package/examples/javascript/map-zoom-to-country/package.json +1 -1
  81. package/examples/javascript/misc-40-charts/package.json +1 -1
  82. package/examples/javascript/misc-microchart-grid/package.json +1 -1
  83. package/examples/javascript/pie-chart/package.json +1 -1
  84. package/examples/javascript/pie-donut-chart/package.json +1 -1
  85. package/examples/javascript/pie-variable-radius/package.json +1 -1
  86. package/examples/javascript/radar-column-iwatch-style/package.json +1 -1
  87. package/examples/javascript/radar-heat-map/package.json +1 -1
  88. package/examples/javascript/radar-line/package.json +1 -1
  89. package/examples/javascript/radar-time-line/package.json +1 -1
  90. package/examples/javascript/sliced-funnel/package.json +1 -1
  91. package/examples/javascript/sliced-pictorial-stacked/package.json +1 -1
  92. package/examples/javascript/sliced-pyramid/package.json +1 -1
  93. package/examples/javascript/stock-chart/package.json +1 -1
  94. package/examples/javascript/stock-chart-comparing-stocks/package.json +1 -1
  95. package/examples/javascript/stock-chart-data-granularity/package.json +1 -1
  96. package/examples/javascript/stock-chart-data-grouping/package.json +1 -1
  97. package/examples/javascript/stock-chart-intraday/package.json +1 -1
  98. package/examples/javascript/stock-chart-live/package.json +1 -1
  99. package/examples/javascript/stock-chart-volume-separate-panel/package.json +1 -1
  100. package/examples/javascript/timeline-horizontal-serpentine-chart/package.json +1 -1
  101. package/examples/javascript/timeline-linear-process-diagram/package.json +1 -1
  102. package/examples/javascript/timeline-serpentine-chart/package.json +1 -1
  103. package/examples/javascript/timeline-spiral-chart/package.json +1 -1
  104. package/examples/javascript/venn-diagram/package.json +1 -1
  105. package/examples/javascript/wordcloud-with-data/package.json +1 -1
  106. package/examples/javascript/wordcloud-with-text/package.json +1 -1
  107. package/examples/javascript/xy-100-percent-stacked-column/package.json +1 -1
  108. package/examples/javascript/xy-animated-bullet-at-the-end-of-the-series/package.json +1 -1
  109. package/examples/javascript/xy-bubble/package.json +1 -1
  110. package/examples/javascript/xy-candlestick/package.json +1 -1
  111. package/examples/javascript/xy-clustered-column/package.json +1 -1
  112. package/examples/javascript/xy-column/package.json +1 -1
  113. package/examples/javascript/xy-comparing-series-google-analytics-style/package.json +1 -1
  114. package/examples/javascript/xy-data-grouping/package.json +1 -1
  115. package/examples/javascript/xy-draggable-range/package.json +1 -1
  116. package/examples/javascript/xy-drawing-series-with-mouse-or-touch/package.json +1 -1
  117. package/examples/javascript/xy-dumbbell plot/package.json +1 -1
  118. package/examples/javascript/xy-evenly-spaced-date-axis/package.json +1 -1
  119. package/examples/javascript/xy-line/package.json +1 -1
  120. package/examples/javascript/xy-line-highlight-on-legend-hover/package.json +1 -1
  121. package/examples/javascript/xy-live-data/package.json +1 -1
  122. package/examples/javascript/xy-multiple-synced-value-axes/package.json +1 -1
  123. package/examples/javascript/xy-ohlc/package.json +1 -1
  124. package/examples/javascript/xy-real-time-data-sorting/package.json +1 -1
  125. package/examples/javascript/xy-smoothed-line/package.json +1 -1
  126. package/examples/javascript/xy-stacked-and-clustered-column/package.json +1 -1
  127. package/examples/javascript/xy-stacked-column/package.json +1 -1
  128. package/examples/javascript/xy-stacked-step/package.json +1 -1
  129. package/examples/javascript/xy-stock/package.json +1 -1
  130. package/examples/javascript/xy-stock-comparing/package.json +1 -1
  131. package/examples/typescript/flow-arc-horizontal/package.json +1 -1
  132. package/examples/typescript/flow-arc-vertical/package.json +1 -1
  133. package/examples/typescript/flow-chord/package.json +1 -1
  134. package/examples/typescript/flow-chord-directed/package.json +1 -1
  135. package/examples/typescript/flow-chord-non-ribbon/package.json +1 -1
  136. package/examples/typescript/flow-sankey/package.json +1 -1
  137. package/examples/typescript/gantt/package.json +1 -1
  138. package/examples/typescript/gantt-multilevel/package.json +1 -1
  139. package/examples/typescript/gantt-simple/package.json +1 -1
  140. package/examples/typescript/gauge/package.json +1 -1
  141. package/examples/typescript/gauge-bands/package.json +1 -1
  142. package/examples/typescript/hierarchy-force-directed/package.json +1 -1
  143. package/examples/typescript/hierarchy-pack/package.json +1 -1
  144. package/examples/typescript/hierarchy-partition/package.json +1 -1
  145. package/examples/typescript/hierarchy-sunburst/package.json +1 -1
  146. package/examples/typescript/hierarchy-tree/package.json +1 -1
  147. package/examples/typescript/hierarchy-treemap/package.json +1 -1
  148. package/examples/typescript/hierarchy-voronoi-treemap/package.json +1 -1
  149. package/examples/typescript/jest/package.json +1 -1
  150. package/examples/typescript/json-pie/package.json +1 -1
  151. package/examples/typescript/json-xy/package.json +1 -1
  152. package/examples/typescript/map-animating-along-lines/package.json +1 -1
  153. package/examples/typescript/map-clustered-points/package.json +1 -1
  154. package/examples/typescript/map-day-and-night/package.json +1 -1
  155. package/examples/typescript/map-globe-rotate-to-country/package.json +1 -1
  156. package/examples/typescript/map-globe-with-projected-circles/package.json +1 -1
  157. package/examples/typescript/map-sankey/README.md +6 -0
  158. package/examples/typescript/map-sankey/index.css +11 -0
  159. package/examples/typescript/map-sankey/index.html +13 -0
  160. package/examples/typescript/map-sankey/index.ts +368 -0
  161. package/examples/typescript/map-sankey/package.json +19 -0
  162. package/examples/typescript/map-sankey/tsconfig.json +12 -0
  163. package/examples/typescript/map-sankey/webpack.config.js +45 -0
  164. package/examples/typescript/map-sankey-waypoints/README.md +6 -0
  165. package/examples/typescript/map-sankey-waypoints/index.css +11 -0
  166. package/examples/typescript/map-sankey-waypoints/index.html +13 -0
  167. package/examples/typescript/map-sankey-waypoints/index.ts +394 -0
  168. package/examples/typescript/map-sankey-waypoints/package.json +19 -0
  169. package/examples/typescript/map-sankey-waypoints/tsconfig.json +12 -0
  170. package/examples/typescript/map-sankey-waypoints/webpack.config.js +45 -0
  171. package/examples/typescript/map-with-bubbles/package.json +1 -1
  172. package/examples/typescript/map-zoom-to-country/package.json +1 -1
  173. package/examples/typescript/misc-40-charts/package.json +1 -1
  174. package/examples/typescript/misc-microchart-grid/package.json +1 -1
  175. package/examples/typescript/pie-chart/package.json +1 -1
  176. package/examples/typescript/pie-donut-chart/package.json +1 -1
  177. package/examples/typescript/pie-variable-radius/package.json +1 -1
  178. package/examples/typescript/radar-column-iwatch-style/package.json +1 -1
  179. package/examples/typescript/radar-heat-map/package.json +1 -1
  180. package/examples/typescript/radar-line/package.json +1 -1
  181. package/examples/typescript/radar-time-line/package.json +1 -1
  182. package/examples/typescript/sliced-funnel/package.json +1 -1
  183. package/examples/typescript/sliced-pictorial-stacked/package.json +1 -1
  184. package/examples/typescript/sliced-pyramid/package.json +1 -1
  185. package/examples/typescript/stock-chart/package.json +1 -1
  186. package/examples/typescript/stock-chart-comparing-stocks/package.json +1 -1
  187. package/examples/typescript/stock-chart-data-granularity/package.json +1 -1
  188. package/examples/typescript/stock-chart-data-grouping/package.json +1 -1
  189. package/examples/typescript/stock-chart-intraday/package.json +1 -1
  190. package/examples/typescript/stock-chart-live/package.json +1 -1
  191. package/examples/typescript/stock-chart-volume-separate-panel/package.json +1 -1
  192. package/examples/typescript/timeline-horizontal-serpentine-chart/package.json +1 -1
  193. package/examples/typescript/timeline-linear-process-diagram/package.json +1 -1
  194. package/examples/typescript/timeline-serpentine-chart/package.json +1 -1
  195. package/examples/typescript/timeline-spiral-chart/package.json +1 -1
  196. package/examples/typescript/venn-diagram/package.json +1 -1
  197. package/examples/typescript/wordcloud-with-data/package.json +1 -1
  198. package/examples/typescript/wordcloud-with-text/package.json +1 -1
  199. package/examples/typescript/xy-100-percent-stacked-column/package.json +1 -1
  200. package/examples/typescript/xy-animated-bullet-at-the-end-of-the-series/package.json +1 -1
  201. package/examples/typescript/xy-bubble/package.json +1 -1
  202. package/examples/typescript/xy-candlestick/package.json +1 -1
  203. package/examples/typescript/xy-clustered-column/package.json +1 -1
  204. package/examples/typescript/xy-column/package.json +1 -1
  205. package/examples/typescript/xy-comparing-series-google-analytics-style/package.json +1 -1
  206. package/examples/typescript/xy-data-grouping/package.json +1 -1
  207. package/examples/typescript/xy-draggable-range/package.json +1 -1
  208. package/examples/typescript/xy-drawing-series-with-mouse-or-touch/package.json +1 -1
  209. package/examples/typescript/xy-dumbbell plot/package.json +1 -1
  210. package/examples/typescript/xy-evenly-spaced-date-axis/package.json +1 -1
  211. package/examples/typescript/xy-line/package.json +1 -1
  212. package/examples/typescript/xy-line-highlight-on-legend-hover/package.json +1 -1
  213. package/examples/typescript/xy-live-data/package.json +1 -1
  214. package/examples/typescript/xy-multiple-synced-value-axes/package.json +1 -1
  215. package/examples/typescript/xy-ohlc/package.json +1 -1
  216. package/examples/typescript/xy-real-time-data-sorting/package.json +1 -1
  217. package/examples/typescript/xy-smoothed-line/package.json +1 -1
  218. package/examples/typescript/xy-stacked-and-clustered-column/package.json +1 -1
  219. package/examples/typescript/xy-stacked-column/package.json +1 -1
  220. package/examples/typescript/xy-stacked-step/package.json +1 -1
  221. package/examples/typescript/xy-stock/package.json +1 -1
  222. package/examples/typescript/xy-stock-comparing/package.json +1 -1
  223. package/map.d.ts +2 -0
  224. package/map.d.ts.map +1 -1
  225. package/map.js +2 -0
  226. package/map.js.map +1 -1
  227. package/package.json +1 -1
@@ -0,0 +1,794 @@
1
+ import { MapPolygonSeries } from "./MapPolygonSeries";
2
+ import { MapSankeyNodes } from "./MapSankeyNodes";
3
+ import { getGeoCentroid, getGeoCircle } from "./MapUtils";
4
+ import { geoArea } from "d3-geo";
5
+ import * as $array from "../../core/util/Array";
6
+ import * as $math from "../../core/util/Math";
7
+ /**
8
+ * Creates a map series for displaying Sankey-style flow bands on a map.
9
+ *
10
+ * Generates actual GeoJSON polygon geometries for each flow, so they
11
+ * properly follow the map projection during panning, zooming, and rotation.
12
+ *
13
+ * @see {@link https://www.amcharts.com/docs/v5/charts/map-chart/map-sankey-series/} for more info
14
+ * @since 5.17.0
15
+ * @important
16
+ */
17
+ export class MapSankeySeries extends MapPolygonSeries {
18
+ constructor() {
19
+ super(...arguments);
20
+ /**
21
+ * Don't consume polygon features from geodata - our data comes from user's data array.
22
+ */
23
+ this._types = [];
24
+ /**
25
+ * A sub-series that manages node data items and visuals.
26
+ *
27
+ * Nodes are auto-created from link data. You can also set
28
+ * `nodes.data.setAll([...])` with custom names and fills.
29
+ *
30
+ * `nodes.mapPolygons.template` configures node appearance.
31
+ */
32
+ this.nodes = this.addDisposer(MapSankeyNodes.new(this._root, {}));
33
+ /**
34
+ * Stored bezier segment params per data item for bullet positioning.
35
+ * Each link can have multiple segments (when waypoints are used).
36
+ * @ignore
37
+ */
38
+ this._bezierSegments = new Map();
39
+ /**
40
+ * Cumulative arc-length array per data item for uniform-speed bullet positioning.
41
+ * Entry i = cumulative length from segment 0 through segment i.
42
+ * Last entry = total path length.
43
+ * @ignore
44
+ */
45
+ this._segmentCumulativeLengths = new Map();
46
+ // Reusable scratch Maps for _generateGeometries — cleared and reused each call
47
+ // to avoid allocating 10 fresh Maps per dirty cycle.
48
+ this._geoSourceCoords = new Map();
49
+ this._geoTargetCoords = new Map();
50
+ this._geoWidths = new Map();
51
+ this._geoSourceGroups = new Map();
52
+ this._geoTargetGroups = new Map();
53
+ this._geoNodeSourceTotal = new Map();
54
+ this._geoNodeTargetTotal = new Map();
55
+ this._geoNodeRange = new Map();
56
+ this._geoSourceOffsets = new Map();
57
+ this._geoTargetOffsets = new Map();
58
+ }
59
+ /**
60
+ * @ignore
61
+ */
62
+ _afterNew() {
63
+ this.fields.push("sourceId", "targetId", "sourceLongitude", "sourceLatitude", "targetLongitude", "targetLatitude", "waypoints");
64
+ this.valueFields.push("value");
65
+ this._setRawDefault("sourceIdField", "sourceId");
66
+ this._setRawDefault("targetIdField", "targetId");
67
+ this._setRawDefault("sourceLongitudeField", "sourceLongitude");
68
+ this._setRawDefault("sourceLatitudeField", "sourceLatitude");
69
+ this._setRawDefault("targetLongitudeField", "targetLongitude");
70
+ this._setRawDefault("targetLatitudeField", "targetLatitude");
71
+ this._setRawDefault("valueField", "value");
72
+ this._setRawDefault("waypointsField", "waypoints");
73
+ this.nodes.flow = this;
74
+ super._afterNew();
75
+ }
76
+ /**
77
+ * @ignore
78
+ */
79
+ markDirtyProjection() {
80
+ super.markDirtyProjection();
81
+ $array.each(this.nodes.dataItems, (nodeDataItem) => {
82
+ const mapPolygon = nodeDataItem.get("mapPolygon");
83
+ if (mapPolygon) {
84
+ mapPolygon.markDirtyProjection();
85
+ }
86
+ });
87
+ }
88
+ /**
89
+ * Evaluate a single bezier segment at parameter t.
90
+ *
91
+ * Same formula as $math.getPointOnCubicCurve but operates on flat
92
+ * IBezierSegment fields (lon/lat) to avoid IPoint object allocation.
93
+ */
94
+ _evalBezier(bp, t) {
95
+ const s = 1 - t;
96
+ return {
97
+ longitude: s * s * s * bp.p0Lon + 3 * s * s * t * bp.cp0Lon + 3 * s * t * t * bp.cp1Lon + t * t * t * bp.p1Lon,
98
+ latitude: s * s * s * bp.p0Lat + 3 * s * s * t * bp.cp0Lat + 3 * s * t * t * bp.cp1Lat + t * t * t * bp.p1Lat
99
+ };
100
+ }
101
+ /**
102
+ * Approximates the arc length of a single bezier segment by sampling
103
+ * points along the curve and summing Haversine distances.
104
+ *
105
+ * @param bp Bezier segment parameters
106
+ * @param samples Number of sample intervals (default 20)
107
+ * @return Approximate arc length in radians
108
+ */
109
+ _segmentArcLength(bp, samples = 20) {
110
+ let length = 0;
111
+ let prev = this._evalBezier(bp, 0);
112
+ for (let i = 1; i <= samples; i++) {
113
+ const pt = this._evalBezier(bp, i / samples);
114
+ const dLon = (pt.longitude - prev.longitude) * Math.PI / 180;
115
+ const dLat = (pt.latitude - prev.latitude) * Math.PI / 180;
116
+ // Haversine approximation
117
+ const a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(prev.latitude * Math.PI / 180) * Math.cos(pt.latitude * Math.PI / 180) * Math.pow(Math.sin(dLon / 2), 2);
118
+ length += 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
119
+ prev = pt;
120
+ }
121
+ return length;
122
+ }
123
+ /**
124
+ * Computes cumulative arc lengths for a set of bezier segments and
125
+ * stores them in `_segmentCumulativeLengths` for arc-length parameterization.
126
+ *
127
+ * @param dataItem Link data item (used as cache key)
128
+ * @param segments Array of bezier segment parameters
129
+ */
130
+ _computeCumulativeLengths(dataItem, segments) {
131
+ const cumulative = [];
132
+ let total = 0;
133
+ for (let i = 0; i < segments.length; i++) {
134
+ total += this._segmentArcLength(segments[i]);
135
+ cumulative.push(total);
136
+ }
137
+ this._segmentCumulativeLengths.set(dataItem, cumulative);
138
+ }
139
+ /**
140
+ * Resolves a normalized location (0–1) to a segment index and local t
141
+ * using arc-length parameterization. Delegates to `$math.resolveLocationOnPath`
142
+ * when cumulative lengths are available; falls back to equal distribution.
143
+ *
144
+ * @param dataItem Link data item (for cumulative length lookup)
145
+ * @param location Relative position along the full path (0–1)
146
+ * @param segments Array of bezier segment parameters (used for fallback)
147
+ * @return Segment index and local t (0–1)
148
+ */
149
+ _resolveLocation(dataItem, location, segments) {
150
+ const cumLengths = this._segmentCumulativeLengths.get(dataItem);
151
+ if (cumLengths && cumLengths.length > 0) {
152
+ const result = $math.resolveLocationOnPath(location, cumLengths);
153
+ return { segIdx: result.index, t: result.t };
154
+ }
155
+ // Fallback: equal distribution
156
+ const n = segments.length;
157
+ const scaled = location * n;
158
+ const segIdx = Math.min(Math.floor(scaled), n - 1);
159
+ return { segIdx, t: scaled - segIdx };
160
+ }
161
+ /**
162
+ * Returns a screen-space point at a relative position (0-1) along
163
+ * the center-line bezier for the given data item.
164
+ *
165
+ * Used internally for bullet positioning, but can also be called
166
+ * directly to get coordinates along a flow path.
167
+ *
168
+ * @param dataItem Target data item
169
+ * @param location Relative position (0 = source, 1 = target)
170
+ * @return Screen coordinates and angle
171
+ */
172
+ getPoint(dataItem, location) {
173
+ const segments = this._bezierSegments.get(dataItem);
174
+ const chart = this.chart;
175
+ if (!segments || segments.length === 0 || !chart)
176
+ return { x: 0, y: 0, angle: 0 };
177
+ const { segIdx, t } = this._resolveLocation(dataItem, location, segments);
178
+ const bp = segments[segIdx];
179
+ const pt = this._evalBezier(bp, t);
180
+ const screenPoint = chart.convert({ longitude: pt.longitude, latitude: pt.latitude });
181
+ // Angle from a nearby point — use backward difference near t=1 to avoid identical points
182
+ const dt = 0.001;
183
+ let pt2;
184
+ let sign = 1;
185
+ if (t + dt <= 1) {
186
+ pt2 = this._evalBezier(bp, t + dt);
187
+ }
188
+ else {
189
+ pt2 = this._evalBezier(bp, t - dt);
190
+ sign = -1;
191
+ }
192
+ const screenPoint2 = chart.convert({ longitude: pt2.longitude, latitude: pt2.latitude });
193
+ const angle = Math.atan2(sign * (screenPoint2.y - screenPoint.y), sign * (screenPoint2.x - screenPoint.x)) * 180 / Math.PI;
194
+ return { x: screenPoint.x, y: screenPoint.y, angle };
195
+ }
196
+ /**
197
+ * Returns a geo point at a relative position (0-1) along the center-line bezier.
198
+ */
199
+ getGeoPoint(dataItem, location) {
200
+ const segments = this._bezierSegments.get(dataItem);
201
+ if (!segments || segments.length === 0)
202
+ return undefined;
203
+ const { segIdx, t } = this._resolveLocation(dataItem, location, segments);
204
+ return this._evalBezier(segments[segIdx], t);
205
+ }
206
+ /**
207
+ * Returns the approximate arc length of the path for a data item.
208
+ * Useful for scaling animation duration proportionally to distance.
209
+ */
210
+ getPathLength(dataItem) {
211
+ const cumLengths = this._segmentCumulativeLengths.get(dataItem);
212
+ if (cumLengths && cumLengths.length > 0) {
213
+ return cumLengths[cumLengths.length - 1];
214
+ }
215
+ return 0;
216
+ }
217
+ /**
218
+ * Combined getPoint + getGeoPoint in a single pass (avoids double
219
+ * _resolveLocation + _evalBezier per bullet per frame).
220
+ * @ignore
221
+ */
222
+ _getPointWithGeo(dataItem, location) {
223
+ const segments = this._bezierSegments.get(dataItem);
224
+ const chart = this.chart;
225
+ if (!segments || segments.length === 0 || !chart)
226
+ return undefined;
227
+ const { segIdx, t } = this._resolveLocation(dataItem, location, segments);
228
+ const bp = segments[segIdx];
229
+ const geoPoint = this._evalBezier(bp, t);
230
+ const screenPoint = chart.convert({ longitude: geoPoint.longitude, latitude: geoPoint.latitude });
231
+ const dt = 0.001;
232
+ let pt2;
233
+ let sign = 1;
234
+ if (t + dt <= 1) {
235
+ pt2 = this._evalBezier(bp, t + dt);
236
+ }
237
+ else {
238
+ pt2 = this._evalBezier(bp, t - dt);
239
+ sign = -1;
240
+ }
241
+ const screenPoint2 = chart.convert({ longitude: pt2.longitude, latitude: pt2.latitude });
242
+ const angle = Math.atan2(sign * (screenPoint2.y - screenPoint.y), sign * (screenPoint2.x - screenPoint.x)) * 180 / Math.PI;
243
+ return { x: screenPoint.x, y: screenPoint.y, angle, geoPoint };
244
+ }
245
+ /**
246
+ * Positions a bullet along its parent link's bezier path.
247
+ *
248
+ * @param bullet Bullet to position
249
+ */
250
+ _positionBullet(bullet) {
251
+ const sprite = bullet.get("sprite");
252
+ if (sprite) {
253
+ const dataItem = sprite.dataItem;
254
+ if (dataItem) {
255
+ const location = bullet.get("locationX", 0.5);
256
+ const result = this._getPointWithGeo(dataItem, location);
257
+ if (!result)
258
+ return;
259
+ sprite.setAll({ x: result.x, y: result.y });
260
+ if (bullet.get("autoRotate")) {
261
+ sprite.set("rotation", result.angle + bullet.get("autoRotateAngle", 0));
262
+ }
263
+ // Hide bullets on the back side of the globe (like MapPointSeries clipBack)
264
+ const chart = this.chart;
265
+ if (chart) {
266
+ const geoPath = chart.getPrivate("geoPath");
267
+ if (geoPath) {
268
+ const geometry = { type: "Point", coordinates: [result.geoPoint.longitude, result.geoPoint.latitude] };
269
+ sprite.setPrivate("visible", !!geoPath(geometry));
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+ /**
276
+ * @ignore
277
+ */
278
+ disposeDataItem(dataItem) {
279
+ super.disposeDataItem(dataItem);
280
+ this._bezierSegments.delete(dataItem);
281
+ this._segmentCumulativeLengths.delete(dataItem);
282
+ }
283
+ /**
284
+ * @ignore
285
+ */
286
+ _dispose() {
287
+ super._dispose();
288
+ this._bezierSegments.clear();
289
+ this._segmentCumulativeLengths.clear();
290
+ this._geoSourceCoords.clear();
291
+ this._geoTargetCoords.clear();
292
+ this._geoWidths.clear();
293
+ this._geoSourceGroups.clear();
294
+ this._geoTargetGroups.clear();
295
+ this._geoNodeSourceTotal.clear();
296
+ this._geoNodeTargetTotal.clear();
297
+ this._geoNodeRange.clear();
298
+ this._geoSourceOffsets.clear();
299
+ this._geoTargetOffsets.clear();
300
+ }
301
+ /**
302
+ * Processes a newly added data item, creating placeholder geometry and
303
+ * registering it with source/target nodes.
304
+ *
305
+ * @param dataItem Data item to process
306
+ */
307
+ processDataItem(dataItem) {
308
+ // Set placeholder geometry so parent creates the MapPolygon
309
+ // Use empty coordinates array (no rings) to avoid d3-geo crash on empty ring
310
+ if (!dataItem.get("geometry")) {
311
+ dataItem.set("geometry", { type: "Polygon", coordinates: [] });
312
+ }
313
+ super.processDataItem(dataItem);
314
+ const polygonSeries = this.get("polygonSeries");
315
+ // Resolve source coordinates
316
+ let sourceLon = dataItem.get("sourceLongitude");
317
+ let sourceLat = dataItem.get("sourceLatitude");
318
+ const sourceId = dataItem.get("sourceId");
319
+ if (sourceId && polygonSeries) {
320
+ const polygonDI = polygonSeries.getDataItemById(sourceId);
321
+ if (polygonDI) {
322
+ const geometry = polygonDI.get("geometry");
323
+ if (geometry) {
324
+ const centroid = getGeoCentroid(geometry);
325
+ sourceLon = centroid.longitude;
326
+ sourceLat = centroid.latitude;
327
+ }
328
+ }
329
+ }
330
+ // Resolve target coordinates
331
+ let targetLon = dataItem.get("targetLongitude");
332
+ let targetLat = dataItem.get("targetLatitude");
333
+ const targetId = dataItem.get("targetId");
334
+ if (targetId && polygonSeries) {
335
+ const polygonDI = polygonSeries.getDataItemById(targetId);
336
+ if (polygonDI) {
337
+ const geometry = polygonDI.get("geometry");
338
+ if (geometry) {
339
+ const centroid = getGeoCentroid(geometry);
340
+ targetLon = centroid.longitude;
341
+ targetLat = centroid.latitude;
342
+ }
343
+ }
344
+ }
345
+ // Create/find source node
346
+ if (sourceLon != null && sourceLat != null) {
347
+ const sourceKey = sourceId !== null && sourceId !== void 0 ? sourceId : (Number(sourceLon) + "," + Number(sourceLat));
348
+ let sourceNode = this.nodes.getDataItemById(sourceKey);
349
+ if (!sourceNode) {
350
+ const ctx = dataItem.dataContext;
351
+ const name = (ctx && ctx.source) || sourceKey;
352
+ this.nodes.data.push({ id: sourceKey, name, longitude: Number(sourceLon), latitude: Number(sourceLat) });
353
+ sourceNode = this.nodes.dataItems[this.nodes.dataItems.length - 1];
354
+ }
355
+ if (sourceNode) {
356
+ dataItem.setRaw("sourceNode", sourceNode);
357
+ this.nodes.addOutgoingLink(sourceNode, dataItem);
358
+ }
359
+ }
360
+ // Create/find target node
361
+ if (targetLon != null && targetLat != null) {
362
+ const targetKey = targetId !== null && targetId !== void 0 ? targetId : (Number(targetLon) + "," + Number(targetLat));
363
+ let targetNode = this.nodes.getDataItemById(targetKey);
364
+ if (!targetNode) {
365
+ const ctx = dataItem.dataContext;
366
+ const name = (ctx && ctx.target) || targetKey;
367
+ this.nodes.data.push({ id: targetKey, name, longitude: Number(targetLon), latitude: Number(targetLat) });
368
+ targetNode = this.nodes.dataItems[this.nodes.dataItems.length - 1];
369
+ }
370
+ if (targetNode) {
371
+ dataItem.setRaw("targetNode", targetNode);
372
+ this.nodes.addIncomingLink(targetNode, dataItem);
373
+ }
374
+ }
375
+ }
376
+ /**
377
+ * @ignore
378
+ */
379
+ _onDataClear() {
380
+ // Clear link references on preserved nodes
381
+ $array.each(this.nodes.dataItems, (nodeDataItem) => {
382
+ nodeDataItem.setRaw("incomingLinks", []);
383
+ nodeDataItem.setRaw("outgoingLinks", []);
384
+ });
385
+ if (!this.nodes._userDataSet) {
386
+ // setAll([]) triggers _onDataClear which sets _userDataSet = true;
387
+ // reset it so auto-created nodes work again on next data cycle.
388
+ this.nodes.data.setAll([]);
389
+ this.nodes._userDataSet = false;
390
+ }
391
+ }
392
+ /**
393
+ * @ignore
394
+ */
395
+ _prepareChildren() {
396
+ if (this._valuesDirty || this._sizeDirty) {
397
+ this._generateGeometries();
398
+ }
399
+ super._prepareChildren();
400
+ }
401
+ /**
402
+ * Rebuilds all link band geometries from current data.
403
+ *
404
+ * Computes bezier control points, stacking offsets, band polygons,
405
+ * and cumulative arc lengths for each link.
406
+ */
407
+ _generateGeometries() {
408
+ const maxWidthDeg = this.get("maxWidth", 5);
409
+ const cpdBase = Math.min(0.4999, this.get("controlPointDistance", 0.5));
410
+ const cpdSource = Math.min(0.4999, this.get("controlPointDistanceSource", cpdBase));
411
+ const cpdTarget = Math.min(0.4999, this.get("controlPointDistanceTarget", cpdBase));
412
+ const resolution = this.get("resolution", 50);
413
+ const linkColorMode = this.get("linkColorMode", "solid");
414
+ // Find max value
415
+ let maxValue = 0;
416
+ $array.each(this.dataItems, (dataItem) => {
417
+ const value = dataItem.get("value");
418
+ if (value != null && value > maxValue) {
419
+ maxValue = value;
420
+ }
421
+ });
422
+ if (maxValue === 0)
423
+ maxValue = 1;
424
+ const sourceCoords = this._geoSourceCoords;
425
+ sourceCoords.clear();
426
+ const targetCoords = this._geoTargetCoords;
427
+ targetCoords.clear();
428
+ const widths = this._geoWidths;
429
+ widths.clear();
430
+ const sourceGroups = this._geoSourceGroups;
431
+ sourceGroups.clear();
432
+ const targetGroups = this._geoTargetGroups;
433
+ targetGroups.clear();
434
+ // Build coord maps and groups from node references on links
435
+ $array.each(this.dataItems, (dataItem) => {
436
+ const value = +(dataItem.get("value") || 0);
437
+ widths.set(dataItem, maxWidthDeg * value / maxValue);
438
+ const sourceNode = dataItem.get("sourceNode");
439
+ if (sourceNode) {
440
+ const lon = sourceNode.get("longitude");
441
+ const lat = sourceNode.get("latitude");
442
+ if (lon != null && lat != null) {
443
+ sourceCoords.set(dataItem, { longitude: lon, latitude: lat });
444
+ const key = sourceNode.get("id");
445
+ if (!sourceGroups.has(key))
446
+ sourceGroups.set(key, []);
447
+ sourceGroups.get(key).push(dataItem);
448
+ }
449
+ }
450
+ const targetNode = dataItem.get("targetNode");
451
+ if (targetNode) {
452
+ const lon = targetNode.get("longitude");
453
+ const lat = targetNode.get("latitude");
454
+ if (lon != null && lat != null) {
455
+ targetCoords.set(dataItem, { longitude: lon, latitude: lat });
456
+ const key = targetNode.get("id");
457
+ if (!targetGroups.has(key))
458
+ targetGroups.set(key, []);
459
+ targetGroups.get(key).push(dataItem);
460
+ }
461
+ }
462
+ });
463
+ // Coordinated stacking at intermediate nodes:
464
+ // At nodes with both incoming and outgoing flows, use the same
465
+ // vertical range for both sides so bands align like in a Sankey.
466
+ const nodePadding = this.get("nodePadding", 0.3);
467
+ const autoSort = this.get("autoSort", true);
468
+ // First: compute the unified stacking range at each node
469
+ const nodeSourceTotal = this._geoNodeSourceTotal;
470
+ nodeSourceTotal.clear();
471
+ const nodeTargetTotal = this._geoNodeTargetTotal;
472
+ nodeTargetTotal.clear();
473
+ sourceGroups.forEach((items, key) => {
474
+ let total = 0;
475
+ $array.each(items, (item) => { total += widths.get(item) || 0; });
476
+ nodeSourceTotal.set(key, total);
477
+ });
478
+ targetGroups.forEach((items, key) => {
479
+ let total = 0;
480
+ $array.each(items, (item) => { total += widths.get(item) || 0; });
481
+ nodeTargetTotal.set(key, total);
482
+ });
483
+ // Unified range = max(incoming, outgoing) at each node
484
+ const nodeRange = this._geoNodeRange;
485
+ nodeRange.clear();
486
+ const allKeys = new Set([...nodeSourceTotal.keys(), ...nodeTargetTotal.keys()]);
487
+ allKeys.forEach((key) => {
488
+ nodeRange.set(key, Math.max(nodeSourceTotal.get(key) || 0, nodeTargetTotal.get(key) || 0));
489
+ });
490
+ // Stack outgoing flows (sourceGroups) centered within the unified range
491
+ const sourceOffsets = this._geoSourceOffsets;
492
+ sourceOffsets.clear();
493
+ const targetOffsets = this._geoTargetOffsets;
494
+ targetOffsets.clear();
495
+ sourceGroups.forEach((items, key) => {
496
+ if (autoSort) {
497
+ items.sort((a, b) => { var _a, _b; return ((((_a = targetCoords.get(a)) === null || _a === void 0 ? void 0 : _a.latitude) || 0) - (((_b = targetCoords.get(b)) === null || _b === void 0 ? void 0 : _b.latitude) || 0)); });
498
+ }
499
+ const ownTotal = nodeSourceTotal.get(key) || 0;
500
+ const range = nodeRange.get(key) || ownTotal;
501
+ // Center this side's bands within the unified range
502
+ let offset = -range / 2 + (range - ownTotal) / 2;
503
+ $array.each(items, (item) => {
504
+ const w = widths.get(item) || 0;
505
+ sourceOffsets.set(item, offset + w / 2);
506
+ offset += w;
507
+ });
508
+ });
509
+ // Stack incoming flows (targetGroups) centered within the unified range
510
+ targetGroups.forEach((items, key) => {
511
+ if (autoSort) {
512
+ items.sort((a, b) => { var _a, _b; return ((((_a = sourceCoords.get(a)) === null || _a === void 0 ? void 0 : _a.latitude) || 0) - (((_b = sourceCoords.get(b)) === null || _b === void 0 ? void 0 : _b.latitude) || 0)); });
513
+ }
514
+ const ownTotal = nodeTargetTotal.get(key) || 0;
515
+ const range = nodeRange.get(key) || ownTotal;
516
+ let offset = -range / 2 + (range - ownTotal) / 2;
517
+ $array.each(items, (item) => {
518
+ const w = widths.get(item) || 0;
519
+ targetOffsets.set(item, offset + w / 2);
520
+ offset += w;
521
+ });
522
+ });
523
+ // Generate polygon for each link
524
+ const orientationSetting = this.get("orientation", "horizontal");
525
+ $array.each(this.dataItems, (dataItem) => {
526
+ var _a, _b, _c, _d, _e;
527
+ const source = sourceCoords.get(dataItem);
528
+ const target = targetCoords.get(dataItem);
529
+ if (!source || !target)
530
+ return;
531
+ const width = widths.get(dataItem) || 0;
532
+ if (width <= 0)
533
+ return;
534
+ const halfWidth = width / 2;
535
+ const sourceOff = sourceOffsets.get(dataItem) || 0;
536
+ const targetOff = targetOffsets.get(dataItem) || 0;
537
+ // Build path points: [source, ...waypoints, target]
538
+ // Apply stacking offset to source/target based on orientation
539
+ let startLon, startLat, endLon, endLat;
540
+ if (orientationSetting === "vertical") {
541
+ startLon = source.longitude + sourceOff;
542
+ startLat = source.latitude;
543
+ endLon = target.longitude + targetOff;
544
+ endLat = target.latitude;
545
+ }
546
+ else {
547
+ startLon = source.longitude;
548
+ startLat = source.latitude + sourceOff;
549
+ endLon = target.longitude;
550
+ endLat = target.latitude + targetOff;
551
+ }
552
+ // Per-link control point distances (fall back to series-level)
553
+ const linkCpd = Math.min(0.4999, (_a = dataItem.get("controlPointDistance")) !== null && _a !== void 0 ? _a : cpdBase);
554
+ const linkCpdSource = Math.min(0.4999, (_c = (_b = dataItem.get("controlPointDistanceSource")) !== null && _b !== void 0 ? _b : dataItem.get("controlPointDistance")) !== null && _c !== void 0 ? _c : cpdSource);
555
+ const linkCpdTarget = Math.min(0.4999, (_e = (_d = dataItem.get("controlPointDistanceTarget")) !== null && _d !== void 0 ? _d : dataItem.get("controlPointDistance")) !== null && _e !== void 0 ? _e : cpdTarget);
556
+ const waypoints = dataItem.get("waypoints");
557
+ const hasWaypoints = waypoints && waypoints.length > 0;
558
+ // Adjust for antimeridian crossing (only without waypoints)
559
+ if (!hasWaypoints) {
560
+ const antimeridian = this.get("antimeridian", "short");
561
+ if (antimeridian === "short") {
562
+ let dLon = endLon - startLon;
563
+ if (dLon > 180)
564
+ endLon -= 360;
565
+ else if (dLon < -180)
566
+ endLon += 360;
567
+ }
568
+ }
569
+ const pathPoints = [{ longitude: startLon, latitude: startLat }];
570
+ if (hasWaypoints) {
571
+ for (const wp of waypoints) {
572
+ pathPoints.push({ longitude: wp.longitude, latitude: wp.latitude });
573
+ }
574
+ }
575
+ pathPoints.push({ longitude: endLon, latitude: endLat });
576
+ // Build bezier segments between consecutive path points
577
+ const segments = [];
578
+ for (let seg = 0; seg < pathPoints.length - 1; seg++) {
579
+ const p0 = pathPoints[seg];
580
+ const p1 = pathPoints[seg + 1];
581
+ // Tangent direction at each point:
582
+ // - First point: direction toward next point
583
+ // - Last point: direction from previous point
584
+ // - Middle points: average of incoming and outgoing directions
585
+ let tangent0Lon, tangent0Lat;
586
+ let tangent1Lon, tangent1Lat;
587
+ if (seg === 0 && pathPoints.length === 2) {
588
+ // Simple case: single segment, use S-curve logic
589
+ if (orientationSetting === "vertical") {
590
+ const dLat = p1.latitude - p0.latitude;
591
+ tangent0Lon = 0;
592
+ tangent0Lat = dLat;
593
+ tangent1Lon = 0;
594
+ tangent1Lat = dLat;
595
+ }
596
+ else {
597
+ const dLon = p1.longitude - p0.longitude;
598
+ tangent0Lon = dLon;
599
+ tangent0Lat = 0;
600
+ tangent1Lon = dLon;
601
+ tangent1Lat = 0;
602
+ }
603
+ }
604
+ else {
605
+ // Multi-point: use direction to neighbor
606
+ if (seg === 0) {
607
+ // First segment start: direction toward next
608
+ tangent0Lon = p1.longitude - p0.longitude;
609
+ tangent0Lat = p1.latitude - p0.latitude;
610
+ }
611
+ else {
612
+ // Average of incoming and outgoing
613
+ const prev = pathPoints[seg - 1];
614
+ tangent0Lon = p1.longitude - prev.longitude;
615
+ tangent0Lat = p1.latitude - prev.latitude;
616
+ }
617
+ if (seg === pathPoints.length - 2) {
618
+ // Last segment end: direction from previous
619
+ tangent1Lon = p1.longitude - p0.longitude;
620
+ tangent1Lat = p1.latitude - p0.latitude;
621
+ }
622
+ else {
623
+ const next = pathPoints[seg + 2];
624
+ tangent1Lon = next.longitude - p0.longitude;
625
+ tangent1Lat = next.latitude - p0.latitude;
626
+ }
627
+ }
628
+ // Normalize tangents and scale by cpd * segment distance
629
+ // Use cpdSource at the start of the first segment, cpdTarget at the end of the last
630
+ const segDist = Math.sqrt(Math.pow((p1.longitude - p0.longitude), 2) + Math.pow((p1.latitude - p0.latitude), 2));
631
+ const t0Len = Math.sqrt(Math.pow(tangent0Lon, 2) + Math.pow(tangent0Lat, 2));
632
+ const t1Len = Math.sqrt(Math.pow(tangent1Lon, 2) + Math.pow(tangent1Lat, 2));
633
+ const scale0 = segDist * (seg === 0 ? linkCpdSource : linkCpd);
634
+ const scale1 = segDist * (seg === pathPoints.length - 2 ? linkCpdTarget : linkCpd);
635
+ const cp0Lon = p0.longitude + (t0Len > 0 ? tangent0Lon / t0Len * scale0 : 0);
636
+ const cp0Lat = p0.latitude + (t0Len > 0 ? tangent0Lat / t0Len * scale0 : 0);
637
+ const cp1Lon = p1.longitude - (t1Len > 0 ? tangent1Lon / t1Len * scale1 : 0);
638
+ const cp1Lat = p1.latitude - (t1Len > 0 ? tangent1Lat / t1Len * scale1 : 0);
639
+ segments.push({
640
+ p0Lon: p0.longitude, p0Lat: p0.latitude,
641
+ p1Lon: p1.longitude, p1Lat: p1.latitude,
642
+ cp0Lon, cp0Lat, cp1Lon, cp1Lat
643
+ });
644
+ }
645
+ // Store for bullet positioning
646
+ this._bezierSegments.set(dataItem, segments);
647
+ this._computeCumulativeLengths(dataItem, segments);
648
+ // Sample all segments to build left/right edges
649
+ const leftEdge = [];
650
+ const rightEdge = [];
651
+ for (let seg = 0; seg < segments.length; seg++) {
652
+ const bp = segments[seg];
653
+ const startI = (seg === 0) ? 0 : 1; // avoid duplicate at joins
654
+ for (let i = startI; i <= resolution; i++) {
655
+ const t = i / resolution;
656
+ const s = 1 - t;
657
+ const lon = s * s * s * bp.p0Lon + 3 * s * s * t * bp.cp0Lon + 3 * s * t * t * bp.cp1Lon + t * t * t * bp.p1Lon;
658
+ const lat = s * s * s * bp.p0Lat + 3 * s * s * t * bp.cp0Lat + 3 * s * t * t * bp.cp1Lat + t * t * t * bp.p1Lat;
659
+ const dtLon = 3 * s * s * (bp.cp0Lon - bp.p0Lon) + 6 * s * t * (bp.cp1Lon - bp.cp0Lon) + 3 * t * t * (bp.p1Lon - bp.cp1Lon);
660
+ const dtLat = 3 * s * s * (bp.cp0Lat - bp.p0Lat) + 6 * s * t * (bp.cp1Lat - bp.cp0Lat) + 3 * t * t * (bp.p1Lat - bp.cp1Lat);
661
+ const tLen = Math.sqrt(dtLon * dtLon + dtLat * dtLat);
662
+ if (tLen === 0)
663
+ continue;
664
+ const perpLon = -dtLat / tLen;
665
+ const perpLat = dtLon / tLen;
666
+ leftEdge.push([lon + halfWidth * perpLon, lat + halfWidth * perpLat]);
667
+ rightEdge.push([lon - halfWidth * perpLon, lat - halfWidth * perpLat]);
668
+ }
669
+ }
670
+ // Simple closed polygon: leftEdge forward + rightEdge backward
671
+ const ring = [];
672
+ for (let i = 0; i < leftEdge.length; i++)
673
+ ring.push(leftEdge[i]);
674
+ for (let i = rightEdge.length - 1; i >= 0; i--)
675
+ ring.push(rightEdge[i]);
676
+ ring.push(leftEdge[0]);
677
+ // d3-geo requires clockwise winding for small polygons.
678
+ // If geoArea > 2*PI the winding is inverted — reverse the ring.
679
+ let geometry = {
680
+ type: "Polygon",
681
+ coordinates: [ring]
682
+ };
683
+ if (geoArea(geometry) > 2 * Math.PI) {
684
+ ring.reverse();
685
+ geometry = {
686
+ type: "Polygon",
687
+ coordinates: [ring]
688
+ };
689
+ }
690
+ dataItem.setRaw("geometry", geometry);
691
+ const mapPolygon = dataItem.get("mapPolygon");
692
+ if (mapPolygon) {
693
+ mapPolygon.set("geometry", geometry);
694
+ if (linkColorMode !== "solid") {
695
+ const nodeDataItem = linkColorMode === "source"
696
+ ? dataItem.get("sourceNode")
697
+ : dataItem.get("targetNode");
698
+ if (nodeDataItem) {
699
+ const nodeFill = nodeDataItem.get("fill");
700
+ if (nodeFill) {
701
+ mapPolygon.set("fill", nodeFill);
702
+ }
703
+ }
704
+ }
705
+ }
706
+ });
707
+ // Compute sums and generate geometries for node shapes
708
+ const nodeType = this.get("nodeType", "circle");
709
+ const nodeWidth = this.get("nodeWidth", 1);
710
+ $array.each(this.nodes.dataItems, (nodeDataItem) => {
711
+ const outgoing = nodeDataItem.get("outgoingLinks") || [];
712
+ const incoming = nodeDataItem.get("incomingLinks") || [];
713
+ const nodeKey = nodeDataItem.get("id");
714
+ // Compute sums and store on node data item for tooltip resolution
715
+ let sumOutgoing = 0;
716
+ $array.each(outgoing, (link) => { sumOutgoing += link.get("value") || 0; });
717
+ let sumIncoming = 0;
718
+ $array.each(incoming, (link) => { sumIncoming += link.get("value") || 0; });
719
+ nodeDataItem.setRaw("sumOutgoing", sumOutgoing);
720
+ nodeDataItem.setRaw("sumIncoming", sumIncoming);
721
+ nodeDataItem.setRaw("sum", sumOutgoing + sumIncoming);
722
+ const totalWidth = nodeRange.get(nodeKey) || 0;
723
+ const lon = nodeDataItem.get("longitude");
724
+ const lat = nodeDataItem.get("latitude");
725
+ if (lon == null || lat == null)
726
+ return;
727
+ let nodeGeo;
728
+ if (nodeType === "bar") {
729
+ const steps = 10;
730
+ const halfSpan = totalWidth / 2 + nodePadding;
731
+ const halfW = nodeWidth / 2;
732
+ const ring = [];
733
+ if (orientationSetting === "vertical") {
734
+ for (let i = 0; i <= steps; i++) {
735
+ const t = i / steps;
736
+ ring.push([lon - halfSpan + totalWidth * t, lat - halfW]);
737
+ }
738
+ for (let i = 0; i <= steps; i++) {
739
+ const t = i / steps;
740
+ ring.push([lon + halfSpan, lat - halfW + nodeWidth * t]);
741
+ }
742
+ for (let i = 0; i <= steps; i++) {
743
+ const t = i / steps;
744
+ ring.push([lon + halfSpan - totalWidth * t, lat + halfW]);
745
+ }
746
+ for (let i = 0; i <= steps; i++) {
747
+ const t = i / steps;
748
+ ring.push([lon - halfSpan, lat + halfW - nodeWidth * t]);
749
+ }
750
+ ring.push([lon - halfSpan, lat - halfW]);
751
+ }
752
+ else {
753
+ for (let i = 0; i <= steps; i++) {
754
+ const t = i / steps;
755
+ ring.push([lon - halfW, lat - halfSpan + totalWidth * t]);
756
+ }
757
+ for (let i = 0; i <= steps; i++) {
758
+ const t = i / steps;
759
+ ring.push([lon - halfW + nodeWidth * t, lat + halfSpan]);
760
+ }
761
+ for (let i = 0; i <= steps; i++) {
762
+ const t = i / steps;
763
+ ring.push([lon + halfW, lat + halfSpan - totalWidth * t]);
764
+ }
765
+ for (let i = 0; i <= steps; i++) {
766
+ const t = i / steps;
767
+ ring.push([lon + halfW - nodeWidth * t, lat - halfSpan]);
768
+ }
769
+ ring.push([lon - halfW, lat - halfSpan]);
770
+ }
771
+ nodeGeo = { type: "Polygon", coordinates: [ring] };
772
+ if (geoArea(nodeGeo) > 2 * Math.PI) {
773
+ ring.reverse();
774
+ nodeGeo = { type: "Polygon", coordinates: [ring] };
775
+ }
776
+ }
777
+ else {
778
+ const radius = totalWidth / 2 + nodePadding;
779
+ nodeGeo = getGeoCircle({ longitude: lon, latitude: lat }, radius);
780
+ }
781
+ const mapPolygon = nodeDataItem.get("mapPolygon");
782
+ if (mapPolygon) {
783
+ mapPolygon.set("geometry", nodeGeo);
784
+ // Reorder at display level so nodes render above bands.
785
+ // Avoids Children.moveValue which triggers markDirty() loop.
786
+ this._childrenDisplay.removeChild(mapPolygon._display);
787
+ this._childrenDisplay.addChild(mapPolygon._display);
788
+ }
789
+ });
790
+ }
791
+ }
792
+ MapSankeySeries.className = "MapSankeySeries";
793
+ MapSankeySeries.classNames = MapPolygonSeries.classNames.concat([MapSankeySeries.className]);
794
+ //# sourceMappingURL=MapSankeySeries.js.map