@amcharts/amcharts5 5.16.2 → 5.17.1

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 +412 -0
  9. package/.internal/charts/map/MapSankeySeries.d.ts.map +1 -0
  10. package/.internal/charts/map/MapSankeySeries.js +868 -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 +23 -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 +361 -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 +361 -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,868 @@
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
+ // Watch for polygonSeries changes — re-attach the datavalidated
75
+ // listener so users don't have to wait for the polygon series to
76
+ // be ready before calling sankey.data.setAll().
77
+ this.on("polygonSeries", () => {
78
+ this._setupPolygonSeriesListener();
79
+ });
80
+ super._afterNew();
81
+ // Initial setup in case polygonSeries was passed in constructor settings.
82
+ this._setupPolygonSeriesListener();
83
+ }
84
+ /**
85
+ * (Re)subscribes to the current `polygonSeries` `datavalidated` event so
86
+ * that any data items added before the polygon series finished loading
87
+ * its geoJSON get their endpoints resolved automatically.
88
+ */
89
+ _setupPolygonSeriesListener() {
90
+ if (this._polygonSeriesDP) {
91
+ this._polygonSeriesDP.dispose();
92
+ this._polygonSeriesDP = undefined;
93
+ }
94
+ const polygonSeries = this.get("polygonSeries");
95
+ if (polygonSeries) {
96
+ this._polygonSeriesDP = polygonSeries.events.on("datavalidated", () => {
97
+ this._resolvePendingDataItems();
98
+ });
99
+ // If the polygon series already has data items (e.g. it was
100
+ // validated before our data was set), resolve immediately.
101
+ if (polygonSeries.dataItems.length > 0) {
102
+ this._resolvePendingDataItems();
103
+ }
104
+ }
105
+ }
106
+ /**
107
+ * Walks all data items and resolves any whose source/target endpoints
108
+ * weren't resolved during the initial `processDataItem` call (because
109
+ * the polygon series wasn't ready yet). Marks values dirty so the
110
+ * geometries get regenerated.
111
+ */
112
+ _resolvePendingDataItems() {
113
+ let anyResolved = false;
114
+ $array.each(this.dataItems, (dataItem) => {
115
+ if (this._resolveEndpoints(dataItem)) {
116
+ anyResolved = true;
117
+ }
118
+ });
119
+ if (anyResolved) {
120
+ // Trigger geometry regeneration on next validation cycle.
121
+ // Mirrors Component.markDirtyValues without the parent's
122
+ // MapPolygon geometry sync (handled later in _generateGeometries).
123
+ this._valuesDirty = true;
124
+ this.markDirty();
125
+ }
126
+ }
127
+ /**
128
+ * @ignore
129
+ */
130
+ markDirtyProjection() {
131
+ super.markDirtyProjection();
132
+ $array.each(this.nodes.dataItems, (nodeDataItem) => {
133
+ const mapPolygon = nodeDataItem.get("mapPolygon");
134
+ if (mapPolygon) {
135
+ mapPolygon.markDirtyProjection();
136
+ }
137
+ });
138
+ }
139
+ /**
140
+ * Evaluate a single bezier segment at parameter t.
141
+ *
142
+ * Same formula as $math.getPointOnCubicCurve but operates on flat
143
+ * IBezierSegment fields (lon/lat) to avoid IPoint object allocation.
144
+ */
145
+ _evalBezier(bp, t) {
146
+ const s = 1 - t;
147
+ return {
148
+ longitude: s * s * s * bp.p0Lon + 3 * s * s * t * bp.cp0Lon + 3 * s * t * t * bp.cp1Lon + t * t * t * bp.p1Lon,
149
+ latitude: s * s * s * bp.p0Lat + 3 * s * s * t * bp.cp0Lat + 3 * s * t * t * bp.cp1Lat + t * t * t * bp.p1Lat
150
+ };
151
+ }
152
+ /**
153
+ * Approximates the arc length of a single bezier segment by sampling
154
+ * points along the curve and summing Haversine distances.
155
+ *
156
+ * @param bp Bezier segment parameters
157
+ * @param samples Number of sample intervals (default 20)
158
+ * @return Approximate arc length in radians
159
+ */
160
+ _segmentArcLength(bp, samples = 20) {
161
+ let length = 0;
162
+ let prev = this._evalBezier(bp, 0);
163
+ for (let i = 1; i <= samples; i++) {
164
+ const pt = this._evalBezier(bp, i / samples);
165
+ const dLon = (pt.longitude - prev.longitude) * Math.PI / 180;
166
+ const dLat = (pt.latitude - prev.latitude) * Math.PI / 180;
167
+ // Haversine approximation
168
+ 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);
169
+ length += 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
170
+ prev = pt;
171
+ }
172
+ return length;
173
+ }
174
+ /**
175
+ * Computes cumulative arc lengths for a set of bezier segments and
176
+ * stores them in `_segmentCumulativeLengths` for arc-length parameterization.
177
+ *
178
+ * @param dataItem Link data item (used as cache key)
179
+ * @param segments Array of bezier segment parameters
180
+ */
181
+ _computeCumulativeLengths(dataItem, segments) {
182
+ const cumulative = [];
183
+ let total = 0;
184
+ for (let i = 0; i < segments.length; i++) {
185
+ total += this._segmentArcLength(segments[i]);
186
+ cumulative.push(total);
187
+ }
188
+ this._segmentCumulativeLengths.set(dataItem, cumulative);
189
+ }
190
+ /**
191
+ * Resolves a normalized location (0–1) to a segment index and local t
192
+ * using arc-length parameterization. Delegates to `$math.resolveLocationOnPath`
193
+ * when cumulative lengths are available; falls back to equal distribution.
194
+ *
195
+ * @param dataItem Link data item (for cumulative length lookup)
196
+ * @param location Relative position along the full path (0–1)
197
+ * @param segments Array of bezier segment parameters (used for fallback)
198
+ * @return Segment index and local t (0–1)
199
+ */
200
+ _resolveLocation(dataItem, location, segments) {
201
+ const cumLengths = this._segmentCumulativeLengths.get(dataItem);
202
+ if (cumLengths && cumLengths.length > 0) {
203
+ const result = $math.resolveLocationOnPath(location, cumLengths);
204
+ return { segIdx: result.index, t: result.t };
205
+ }
206
+ // Fallback: equal distribution
207
+ const n = segments.length;
208
+ const scaled = location * n;
209
+ const segIdx = Math.min(Math.floor(scaled), n - 1);
210
+ return { segIdx, t: scaled - segIdx };
211
+ }
212
+ /**
213
+ * Returns a screen-space point at a relative position (0-1) along
214
+ * the center-line bezier for the given data item.
215
+ *
216
+ * Used internally for bullet positioning, but can also be called
217
+ * directly to get coordinates along a flow path.
218
+ *
219
+ * @param dataItem Target data item
220
+ * @param location Relative position (0 = source, 1 = target)
221
+ * @return Screen coordinates and angle
222
+ */
223
+ getPoint(dataItem, location) {
224
+ const segments = this._bezierSegments.get(dataItem);
225
+ const chart = this.chart;
226
+ if (!segments || segments.length === 0 || !chart)
227
+ return { x: 0, y: 0, angle: 0 };
228
+ const { segIdx, t } = this._resolveLocation(dataItem, location, segments);
229
+ const bp = segments[segIdx];
230
+ const pt = this._evalBezier(bp, t);
231
+ const screenPoint = chart.convert({ longitude: pt.longitude, latitude: pt.latitude });
232
+ // Angle from a nearby point — use backward difference near t=1 to avoid identical points
233
+ const dt = 0.001;
234
+ let pt2;
235
+ let sign = 1;
236
+ if (t + dt <= 1) {
237
+ pt2 = this._evalBezier(bp, t + dt);
238
+ }
239
+ else {
240
+ pt2 = this._evalBezier(bp, t - dt);
241
+ sign = -1;
242
+ }
243
+ const screenPoint2 = chart.convert({ longitude: pt2.longitude, latitude: pt2.latitude });
244
+ const angle = Math.atan2(sign * (screenPoint2.y - screenPoint.y), sign * (screenPoint2.x - screenPoint.x)) * 180 / Math.PI;
245
+ return { x: screenPoint.x, y: screenPoint.y, angle };
246
+ }
247
+ /**
248
+ * Returns a geo point at a relative position (0-1) along the center-line bezier.
249
+ */
250
+ getGeoPoint(dataItem, location) {
251
+ const segments = this._bezierSegments.get(dataItem);
252
+ if (!segments || segments.length === 0)
253
+ return undefined;
254
+ const { segIdx, t } = this._resolveLocation(dataItem, location, segments);
255
+ return this._evalBezier(segments[segIdx], t);
256
+ }
257
+ /**
258
+ * Returns the approximate arc length of the path for a data item.
259
+ * Useful for scaling animation duration proportionally to distance.
260
+ */
261
+ getPathLength(dataItem) {
262
+ const cumLengths = this._segmentCumulativeLengths.get(dataItem);
263
+ if (cumLengths && cumLengths.length > 0) {
264
+ return cumLengths[cumLengths.length - 1];
265
+ }
266
+ return 0;
267
+ }
268
+ /**
269
+ * Combined getPoint + getGeoPoint in a single pass (avoids double
270
+ * _resolveLocation + _evalBezier per bullet per frame).
271
+ * @ignore
272
+ */
273
+ _getPointWithGeo(dataItem, location) {
274
+ const segments = this._bezierSegments.get(dataItem);
275
+ const chart = this.chart;
276
+ if (!segments || segments.length === 0 || !chart)
277
+ return undefined;
278
+ const { segIdx, t } = this._resolveLocation(dataItem, location, segments);
279
+ const bp = segments[segIdx];
280
+ const geoPoint = this._evalBezier(bp, t);
281
+ const screenPoint = chart.convert({ longitude: geoPoint.longitude, latitude: geoPoint.latitude });
282
+ const dt = 0.001;
283
+ let pt2;
284
+ let sign = 1;
285
+ if (t + dt <= 1) {
286
+ pt2 = this._evalBezier(bp, t + dt);
287
+ }
288
+ else {
289
+ pt2 = this._evalBezier(bp, t - dt);
290
+ sign = -1;
291
+ }
292
+ const screenPoint2 = chart.convert({ longitude: pt2.longitude, latitude: pt2.latitude });
293
+ const angle = Math.atan2(sign * (screenPoint2.y - screenPoint.y), sign * (screenPoint2.x - screenPoint.x)) * 180 / Math.PI;
294
+ return { x: screenPoint.x, y: screenPoint.y, angle, geoPoint };
295
+ }
296
+ /**
297
+ * Positions a bullet along its parent link's bezier path.
298
+ *
299
+ * @param bullet Bullet to position
300
+ */
301
+ _positionBullet(bullet) {
302
+ const sprite = bullet.get("sprite");
303
+ if (sprite) {
304
+ const dataItem = sprite.dataItem;
305
+ if (dataItem) {
306
+ const location = bullet.get("locationX", 0.5);
307
+ const result = this._getPointWithGeo(dataItem, location);
308
+ if (!result)
309
+ return;
310
+ sprite.setAll({ x: result.x, y: result.y });
311
+ if (bullet.get("autoRotate")) {
312
+ sprite.set("rotation", result.angle + bullet.get("autoRotateAngle", 0));
313
+ }
314
+ // Hide bullets on the back side of the globe (like MapPointSeries clipBack)
315
+ const chart = this.chart;
316
+ if (chart) {
317
+ const geoPath = chart.getPrivate("geoPath");
318
+ if (geoPath) {
319
+ const geometry = { type: "Point", coordinates: [result.geoPoint.longitude, result.geoPoint.latitude] };
320
+ sprite.setPrivate("visible", !!geoPath(geometry));
321
+ }
322
+ }
323
+ }
324
+ }
325
+ }
326
+ /**
327
+ * @ignore
328
+ */
329
+ disposeDataItem(dataItem) {
330
+ super.disposeDataItem(dataItem);
331
+ this._bezierSegments.delete(dataItem);
332
+ this._segmentCumulativeLengths.delete(dataItem);
333
+ }
334
+ /**
335
+ * @ignore
336
+ */
337
+ _dispose() {
338
+ super._dispose();
339
+ if (this._polygonSeriesDP) {
340
+ this._polygonSeriesDP.dispose();
341
+ this._polygonSeriesDP = undefined;
342
+ }
343
+ this._bezierSegments.clear();
344
+ this._segmentCumulativeLengths.clear();
345
+ this._geoSourceCoords.clear();
346
+ this._geoTargetCoords.clear();
347
+ this._geoWidths.clear();
348
+ this._geoSourceGroups.clear();
349
+ this._geoTargetGroups.clear();
350
+ this._geoNodeSourceTotal.clear();
351
+ this._geoNodeTargetTotal.clear();
352
+ this._geoNodeRange.clear();
353
+ this._geoSourceOffsets.clear();
354
+ this._geoTargetOffsets.clear();
355
+ }
356
+ /**
357
+ * Processes a newly added data item, creating placeholder geometry and
358
+ * registering it with source/target nodes.
359
+ *
360
+ * @param dataItem Data item to process
361
+ */
362
+ processDataItem(dataItem) {
363
+ // Set placeholder geometry so parent creates the MapPolygon
364
+ // Use empty coordinates array (no rings) to avoid d3-geo crash on empty ring
365
+ if (!dataItem.get("geometry")) {
366
+ dataItem.set("geometry", { type: "Polygon", coordinates: [] });
367
+ }
368
+ super.processDataItem(dataItem);
369
+ this._resolveEndpoints(dataItem);
370
+ }
371
+ /**
372
+ * Resolves the source/target endpoints for a single data item:
373
+ * looks up centroids on the polygon series (if `sourceId`/`targetId` is set),
374
+ * then creates or attaches the appropriate sankey nodes.
375
+ *
376
+ * Returns `true` if anything was newly resolved, so callers can decide
377
+ * whether to mark geometries dirty.
378
+ *
379
+ * Safe to call repeatedly: skips endpoints that are already resolved.
380
+ */
381
+ _resolveEndpoints(dataItem) {
382
+ const polygonSeries = this.get("polygonSeries");
383
+ let resolved = false;
384
+ // -- Source --
385
+ if (!dataItem.get("sourceNode")) {
386
+ let sourceLon = dataItem.get("sourceLongitude");
387
+ let sourceLat = dataItem.get("sourceLatitude");
388
+ const sourceId = dataItem.get("sourceId");
389
+ if (sourceId && polygonSeries) {
390
+ const polygonDI = polygonSeries.getDataItemById(sourceId);
391
+ if (polygonDI) {
392
+ const geometry = polygonDI.get("geometry");
393
+ if (geometry) {
394
+ const centroid = getGeoCentroid(geometry);
395
+ sourceLon = centroid.longitude;
396
+ sourceLat = centroid.latitude;
397
+ }
398
+ }
399
+ }
400
+ if (sourceLon != null && sourceLat != null) {
401
+ const sourceKey = sourceId !== null && sourceId !== void 0 ? sourceId : (Number(sourceLon) + "," + Number(sourceLat));
402
+ let sourceNode = this.nodes.getDataItemById(sourceKey);
403
+ if (!sourceNode) {
404
+ const ctx = dataItem.dataContext;
405
+ const name = (ctx && ctx.source) || sourceKey;
406
+ this.nodes.data.push({ id: sourceKey, name, longitude: Number(sourceLon), latitude: Number(sourceLat) });
407
+ sourceNode = this.nodes.dataItems[this.nodes.dataItems.length - 1];
408
+ }
409
+ if (sourceNode) {
410
+ dataItem.setRaw("sourceNode", sourceNode);
411
+ this.nodes.addOutgoingLink(sourceNode, dataItem);
412
+ resolved = true;
413
+ }
414
+ }
415
+ }
416
+ // -- Target --
417
+ if (!dataItem.get("targetNode")) {
418
+ let targetLon = dataItem.get("targetLongitude");
419
+ let targetLat = dataItem.get("targetLatitude");
420
+ const targetId = dataItem.get("targetId");
421
+ if (targetId && polygonSeries) {
422
+ const polygonDI = polygonSeries.getDataItemById(targetId);
423
+ if (polygonDI) {
424
+ const geometry = polygonDI.get("geometry");
425
+ if (geometry) {
426
+ const centroid = getGeoCentroid(geometry);
427
+ targetLon = centroid.longitude;
428
+ targetLat = centroid.latitude;
429
+ }
430
+ }
431
+ }
432
+ if (targetLon != null && targetLat != null) {
433
+ const targetKey = targetId !== null && targetId !== void 0 ? targetId : (Number(targetLon) + "," + Number(targetLat));
434
+ let targetNode = this.nodes.getDataItemById(targetKey);
435
+ if (!targetNode) {
436
+ const ctx = dataItem.dataContext;
437
+ const name = (ctx && ctx.target) || targetKey;
438
+ this.nodes.data.push({ id: targetKey, name, longitude: Number(targetLon), latitude: Number(targetLat) });
439
+ targetNode = this.nodes.dataItems[this.nodes.dataItems.length - 1];
440
+ }
441
+ if (targetNode) {
442
+ dataItem.setRaw("targetNode", targetNode);
443
+ this.nodes.addIncomingLink(targetNode, dataItem);
444
+ resolved = true;
445
+ }
446
+ }
447
+ }
448
+ return resolved;
449
+ }
450
+ /**
451
+ * @ignore
452
+ */
453
+ _onDataClear() {
454
+ // Clear link references on preserved nodes
455
+ $array.each(this.nodes.dataItems, (nodeDataItem) => {
456
+ nodeDataItem.setRaw("incomingLinks", []);
457
+ nodeDataItem.setRaw("outgoingLinks", []);
458
+ });
459
+ if (!this.nodes._userDataSet) {
460
+ // setAll([]) triggers _onDataClear which sets _userDataSet = true;
461
+ // reset it so auto-created nodes work again on next data cycle.
462
+ this.nodes.data.setAll([]);
463
+ this.nodes._userDataSet = false;
464
+ }
465
+ }
466
+ /**
467
+ * @ignore
468
+ */
469
+ _prepareChildren() {
470
+ if (this._valuesDirty || this._sizeDirty) {
471
+ this._generateGeometries();
472
+ }
473
+ super._prepareChildren();
474
+ }
475
+ /**
476
+ * Rebuilds all link band geometries from current data.
477
+ *
478
+ * Computes bezier control points, stacking offsets, band polygons,
479
+ * and cumulative arc lengths for each link.
480
+ */
481
+ _generateGeometries() {
482
+ const maxWidthDeg = this.get("maxWidth", 5);
483
+ const cpdBase = Math.min(0.4999, this.get("controlPointDistance", 0.5));
484
+ const cpdSource = Math.min(0.4999, this.get("controlPointDistanceSource", cpdBase));
485
+ const cpdTarget = Math.min(0.4999, this.get("controlPointDistanceTarget", cpdBase));
486
+ const resolution = this.get("resolution", 50);
487
+ const linkColorMode = this.get("linkColorMode", "solid");
488
+ // Find max value
489
+ let maxValue = 0;
490
+ $array.each(this.dataItems, (dataItem) => {
491
+ const value = dataItem.get("value");
492
+ if (value != null && value > maxValue) {
493
+ maxValue = value;
494
+ }
495
+ });
496
+ if (maxValue === 0)
497
+ maxValue = 1;
498
+ const sourceCoords = this._geoSourceCoords;
499
+ sourceCoords.clear();
500
+ const targetCoords = this._geoTargetCoords;
501
+ targetCoords.clear();
502
+ const widths = this._geoWidths;
503
+ widths.clear();
504
+ const sourceGroups = this._geoSourceGroups;
505
+ sourceGroups.clear();
506
+ const targetGroups = this._geoTargetGroups;
507
+ targetGroups.clear();
508
+ // Build coord maps and groups from node references on links
509
+ $array.each(this.dataItems, (dataItem) => {
510
+ const value = +(dataItem.get("value") || 0);
511
+ widths.set(dataItem, maxWidthDeg * value / maxValue);
512
+ const sourceNode = dataItem.get("sourceNode");
513
+ if (sourceNode) {
514
+ const lon = sourceNode.get("longitude");
515
+ const lat = sourceNode.get("latitude");
516
+ if (lon != null && lat != null) {
517
+ sourceCoords.set(dataItem, { longitude: lon, latitude: lat });
518
+ const key = sourceNode.get("id");
519
+ if (!sourceGroups.has(key))
520
+ sourceGroups.set(key, []);
521
+ sourceGroups.get(key).push(dataItem);
522
+ }
523
+ }
524
+ const targetNode = dataItem.get("targetNode");
525
+ if (targetNode) {
526
+ const lon = targetNode.get("longitude");
527
+ const lat = targetNode.get("latitude");
528
+ if (lon != null && lat != null) {
529
+ targetCoords.set(dataItem, { longitude: lon, latitude: lat });
530
+ const key = targetNode.get("id");
531
+ if (!targetGroups.has(key))
532
+ targetGroups.set(key, []);
533
+ targetGroups.get(key).push(dataItem);
534
+ }
535
+ }
536
+ });
537
+ // Coordinated stacking at intermediate nodes:
538
+ // At nodes with both incoming and outgoing flows, use the same
539
+ // vertical range for both sides so bands align like in a Sankey.
540
+ const nodePadding = this.get("nodePadding", 0.3);
541
+ const autoSort = this.get("autoSort", true);
542
+ // First: compute the unified stacking range at each node
543
+ const nodeSourceTotal = this._geoNodeSourceTotal;
544
+ nodeSourceTotal.clear();
545
+ const nodeTargetTotal = this._geoNodeTargetTotal;
546
+ nodeTargetTotal.clear();
547
+ sourceGroups.forEach((items, key) => {
548
+ let total = 0;
549
+ $array.each(items, (item) => { total += widths.get(item) || 0; });
550
+ nodeSourceTotal.set(key, total);
551
+ });
552
+ targetGroups.forEach((items, key) => {
553
+ let total = 0;
554
+ $array.each(items, (item) => { total += widths.get(item) || 0; });
555
+ nodeTargetTotal.set(key, total);
556
+ });
557
+ // Unified range = max(incoming, outgoing) at each node
558
+ const nodeRange = this._geoNodeRange;
559
+ nodeRange.clear();
560
+ const allKeys = new Set([...nodeSourceTotal.keys(), ...nodeTargetTotal.keys()]);
561
+ allKeys.forEach((key) => {
562
+ nodeRange.set(key, Math.max(nodeSourceTotal.get(key) || 0, nodeTargetTotal.get(key) || 0));
563
+ });
564
+ // Stack outgoing flows (sourceGroups) centered within the unified range
565
+ const sourceOffsets = this._geoSourceOffsets;
566
+ sourceOffsets.clear();
567
+ const targetOffsets = this._geoTargetOffsets;
568
+ targetOffsets.clear();
569
+ sourceGroups.forEach((items, key) => {
570
+ if (autoSort) {
571
+ 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)); });
572
+ }
573
+ const ownTotal = nodeSourceTotal.get(key) || 0;
574
+ const range = nodeRange.get(key) || ownTotal;
575
+ // Center this side's bands within the unified range
576
+ let offset = -range / 2 + (range - ownTotal) / 2;
577
+ $array.each(items, (item) => {
578
+ const w = widths.get(item) || 0;
579
+ sourceOffsets.set(item, offset + w / 2);
580
+ offset += w;
581
+ });
582
+ });
583
+ // Stack incoming flows (targetGroups) centered within the unified range
584
+ targetGroups.forEach((items, key) => {
585
+ if (autoSort) {
586
+ 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)); });
587
+ }
588
+ const ownTotal = nodeTargetTotal.get(key) || 0;
589
+ const range = nodeRange.get(key) || ownTotal;
590
+ let offset = -range / 2 + (range - ownTotal) / 2;
591
+ $array.each(items, (item) => {
592
+ const w = widths.get(item) || 0;
593
+ targetOffsets.set(item, offset + w / 2);
594
+ offset += w;
595
+ });
596
+ });
597
+ // Generate polygon for each link
598
+ const orientationSetting = this.get("orientation", "horizontal");
599
+ $array.each(this.dataItems, (dataItem) => {
600
+ var _a, _b, _c, _d, _e;
601
+ const source = sourceCoords.get(dataItem);
602
+ const target = targetCoords.get(dataItem);
603
+ if (!source || !target)
604
+ return;
605
+ const width = widths.get(dataItem) || 0;
606
+ if (width <= 0)
607
+ return;
608
+ const halfWidth = width / 2;
609
+ const sourceOff = sourceOffsets.get(dataItem) || 0;
610
+ const targetOff = targetOffsets.get(dataItem) || 0;
611
+ // Build path points: [source, ...waypoints, target]
612
+ // Apply stacking offset to source/target based on orientation
613
+ let startLon, startLat, endLon, endLat;
614
+ if (orientationSetting === "vertical") {
615
+ startLon = source.longitude + sourceOff;
616
+ startLat = source.latitude;
617
+ endLon = target.longitude + targetOff;
618
+ endLat = target.latitude;
619
+ }
620
+ else {
621
+ startLon = source.longitude;
622
+ startLat = source.latitude + sourceOff;
623
+ endLon = target.longitude;
624
+ endLat = target.latitude + targetOff;
625
+ }
626
+ // Per-link control point distances (fall back to series-level)
627
+ const linkCpd = Math.min(0.4999, (_a = dataItem.get("controlPointDistance")) !== null && _a !== void 0 ? _a : cpdBase);
628
+ 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);
629
+ 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);
630
+ const waypoints = dataItem.get("waypoints");
631
+ const hasWaypoints = waypoints && waypoints.length > 0;
632
+ // Adjust for antimeridian crossing (only without waypoints)
633
+ if (!hasWaypoints) {
634
+ const antimeridian = this.get("antimeridian", "short");
635
+ if (antimeridian === "short") {
636
+ let dLon = endLon - startLon;
637
+ if (dLon > 180)
638
+ endLon -= 360;
639
+ else if (dLon < -180)
640
+ endLon += 360;
641
+ }
642
+ }
643
+ const pathPoints = [{ longitude: startLon, latitude: startLat }];
644
+ if (hasWaypoints) {
645
+ for (const wp of waypoints) {
646
+ pathPoints.push({ longitude: wp.longitude, latitude: wp.latitude });
647
+ }
648
+ }
649
+ pathPoints.push({ longitude: endLon, latitude: endLat });
650
+ // Build bezier segments between consecutive path points
651
+ const segments = [];
652
+ for (let seg = 0; seg < pathPoints.length - 1; seg++) {
653
+ const p0 = pathPoints[seg];
654
+ const p1 = pathPoints[seg + 1];
655
+ // Tangent direction at each point:
656
+ // - First point: direction toward next point
657
+ // - Last point: direction from previous point
658
+ // - Middle points: average of incoming and outgoing directions
659
+ let tangent0Lon, tangent0Lat;
660
+ let tangent1Lon, tangent1Lat;
661
+ if (seg === 0 && pathPoints.length === 2) {
662
+ // Simple case: single segment, use S-curve logic
663
+ if (orientationSetting === "vertical") {
664
+ const dLat = p1.latitude - p0.latitude;
665
+ tangent0Lon = 0;
666
+ tangent0Lat = dLat;
667
+ tangent1Lon = 0;
668
+ tangent1Lat = dLat;
669
+ }
670
+ else {
671
+ const dLon = p1.longitude - p0.longitude;
672
+ tangent0Lon = dLon;
673
+ tangent0Lat = 0;
674
+ tangent1Lon = dLon;
675
+ tangent1Lat = 0;
676
+ }
677
+ }
678
+ else {
679
+ // Multi-point: use direction to neighbor
680
+ if (seg === 0) {
681
+ // First segment start: direction toward next
682
+ tangent0Lon = p1.longitude - p0.longitude;
683
+ tangent0Lat = p1.latitude - p0.latitude;
684
+ }
685
+ else {
686
+ // Average of incoming and outgoing
687
+ const prev = pathPoints[seg - 1];
688
+ tangent0Lon = p1.longitude - prev.longitude;
689
+ tangent0Lat = p1.latitude - prev.latitude;
690
+ }
691
+ if (seg === pathPoints.length - 2) {
692
+ // Last segment end: direction from previous
693
+ tangent1Lon = p1.longitude - p0.longitude;
694
+ tangent1Lat = p1.latitude - p0.latitude;
695
+ }
696
+ else {
697
+ const next = pathPoints[seg + 2];
698
+ tangent1Lon = next.longitude - p0.longitude;
699
+ tangent1Lat = next.latitude - p0.latitude;
700
+ }
701
+ }
702
+ // Normalize tangents and scale by cpd * segment distance
703
+ // Use cpdSource at the start of the first segment, cpdTarget at the end of the last
704
+ const segDist = Math.sqrt(Math.pow((p1.longitude - p0.longitude), 2) + Math.pow((p1.latitude - p0.latitude), 2));
705
+ const t0Len = Math.sqrt(Math.pow(tangent0Lon, 2) + Math.pow(tangent0Lat, 2));
706
+ const t1Len = Math.sqrt(Math.pow(tangent1Lon, 2) + Math.pow(tangent1Lat, 2));
707
+ const scale0 = segDist * (seg === 0 ? linkCpdSource : linkCpd);
708
+ const scale1 = segDist * (seg === pathPoints.length - 2 ? linkCpdTarget : linkCpd);
709
+ const cp0Lon = p0.longitude + (t0Len > 0 ? tangent0Lon / t0Len * scale0 : 0);
710
+ const cp0Lat = p0.latitude + (t0Len > 0 ? tangent0Lat / t0Len * scale0 : 0);
711
+ const cp1Lon = p1.longitude - (t1Len > 0 ? tangent1Lon / t1Len * scale1 : 0);
712
+ const cp1Lat = p1.latitude - (t1Len > 0 ? tangent1Lat / t1Len * scale1 : 0);
713
+ segments.push({
714
+ p0Lon: p0.longitude, p0Lat: p0.latitude,
715
+ p1Lon: p1.longitude, p1Lat: p1.latitude,
716
+ cp0Lon, cp0Lat, cp1Lon, cp1Lat
717
+ });
718
+ }
719
+ // Store for bullet positioning
720
+ this._bezierSegments.set(dataItem, segments);
721
+ this._computeCumulativeLengths(dataItem, segments);
722
+ // Sample all segments to build left/right edges
723
+ const leftEdge = [];
724
+ const rightEdge = [];
725
+ for (let seg = 0; seg < segments.length; seg++) {
726
+ const bp = segments[seg];
727
+ const startI = (seg === 0) ? 0 : 1; // avoid duplicate at joins
728
+ for (let i = startI; i <= resolution; i++) {
729
+ const t = i / resolution;
730
+ const s = 1 - t;
731
+ const lon = s * s * s * bp.p0Lon + 3 * s * s * t * bp.cp0Lon + 3 * s * t * t * bp.cp1Lon + t * t * t * bp.p1Lon;
732
+ const lat = s * s * s * bp.p0Lat + 3 * s * s * t * bp.cp0Lat + 3 * s * t * t * bp.cp1Lat + t * t * t * bp.p1Lat;
733
+ const dtLon = 3 * s * s * (bp.cp0Lon - bp.p0Lon) + 6 * s * t * (bp.cp1Lon - bp.cp0Lon) + 3 * t * t * (bp.p1Lon - bp.cp1Lon);
734
+ const dtLat = 3 * s * s * (bp.cp0Lat - bp.p0Lat) + 6 * s * t * (bp.cp1Lat - bp.cp0Lat) + 3 * t * t * (bp.p1Lat - bp.cp1Lat);
735
+ const tLen = Math.sqrt(dtLon * dtLon + dtLat * dtLat);
736
+ if (tLen === 0)
737
+ continue;
738
+ const perpLon = -dtLat / tLen;
739
+ const perpLat = dtLon / tLen;
740
+ leftEdge.push([lon + halfWidth * perpLon, lat + halfWidth * perpLat]);
741
+ rightEdge.push([lon - halfWidth * perpLon, lat - halfWidth * perpLat]);
742
+ }
743
+ }
744
+ // Simple closed polygon: leftEdge forward + rightEdge backward
745
+ const ring = [];
746
+ for (let i = 0; i < leftEdge.length; i++)
747
+ ring.push(leftEdge[i]);
748
+ for (let i = rightEdge.length - 1; i >= 0; i--)
749
+ ring.push(rightEdge[i]);
750
+ ring.push(leftEdge[0]);
751
+ // d3-geo requires clockwise winding for small polygons.
752
+ // If geoArea > 2*PI the winding is inverted — reverse the ring.
753
+ let geometry = {
754
+ type: "Polygon",
755
+ coordinates: [ring]
756
+ };
757
+ if (geoArea(geometry) > 2 * Math.PI) {
758
+ ring.reverse();
759
+ geometry = {
760
+ type: "Polygon",
761
+ coordinates: [ring]
762
+ };
763
+ }
764
+ dataItem.setRaw("geometry", geometry);
765
+ const mapPolygon = dataItem.get("mapPolygon");
766
+ if (mapPolygon) {
767
+ mapPolygon.set("geometry", geometry);
768
+ if (linkColorMode !== "solid") {
769
+ const nodeDataItem = linkColorMode === "source"
770
+ ? dataItem.get("sourceNode")
771
+ : dataItem.get("targetNode");
772
+ if (nodeDataItem) {
773
+ const nodeFill = nodeDataItem.get("fill");
774
+ if (nodeFill) {
775
+ mapPolygon.set("fill", nodeFill);
776
+ }
777
+ }
778
+ }
779
+ }
780
+ });
781
+ // Compute sums and generate geometries for node shapes
782
+ const nodeType = this.get("nodeType", "circle");
783
+ const nodeWidth = this.get("nodeWidth", 1);
784
+ $array.each(this.nodes.dataItems, (nodeDataItem) => {
785
+ const outgoing = nodeDataItem.get("outgoingLinks") || [];
786
+ const incoming = nodeDataItem.get("incomingLinks") || [];
787
+ const nodeKey = nodeDataItem.get("id");
788
+ // Compute sums and store on node data item for tooltip resolution
789
+ let sumOutgoing = 0;
790
+ $array.each(outgoing, (link) => { sumOutgoing += link.get("value") || 0; });
791
+ let sumIncoming = 0;
792
+ $array.each(incoming, (link) => { sumIncoming += link.get("value") || 0; });
793
+ nodeDataItem.setRaw("sumOutgoing", sumOutgoing);
794
+ nodeDataItem.setRaw("sumIncoming", sumIncoming);
795
+ nodeDataItem.setRaw("sum", sumOutgoing + sumIncoming);
796
+ const totalWidth = nodeRange.get(nodeKey) || 0;
797
+ const lon = nodeDataItem.get("longitude");
798
+ const lat = nodeDataItem.get("latitude");
799
+ if (lon == null || lat == null)
800
+ return;
801
+ let nodeGeo;
802
+ if (nodeType === "bar") {
803
+ const steps = 10;
804
+ const halfSpan = totalWidth / 2 + nodePadding;
805
+ const halfW = nodeWidth / 2;
806
+ const ring = [];
807
+ if (orientationSetting === "vertical") {
808
+ for (let i = 0; i <= steps; i++) {
809
+ const t = i / steps;
810
+ ring.push([lon - halfSpan + totalWidth * t, lat - halfW]);
811
+ }
812
+ for (let i = 0; i <= steps; i++) {
813
+ const t = i / steps;
814
+ ring.push([lon + halfSpan, lat - halfW + nodeWidth * t]);
815
+ }
816
+ for (let i = 0; i <= steps; i++) {
817
+ const t = i / steps;
818
+ ring.push([lon + halfSpan - totalWidth * t, lat + halfW]);
819
+ }
820
+ for (let i = 0; i <= steps; i++) {
821
+ const t = i / steps;
822
+ ring.push([lon - halfSpan, lat + halfW - nodeWidth * t]);
823
+ }
824
+ ring.push([lon - halfSpan, lat - halfW]);
825
+ }
826
+ else {
827
+ for (let i = 0; i <= steps; i++) {
828
+ const t = i / steps;
829
+ ring.push([lon - halfW, lat - halfSpan + totalWidth * t]);
830
+ }
831
+ for (let i = 0; i <= steps; i++) {
832
+ const t = i / steps;
833
+ ring.push([lon - halfW + nodeWidth * t, lat + halfSpan]);
834
+ }
835
+ for (let i = 0; i <= steps; i++) {
836
+ const t = i / steps;
837
+ ring.push([lon + halfW, lat + halfSpan - totalWidth * t]);
838
+ }
839
+ for (let i = 0; i <= steps; i++) {
840
+ const t = i / steps;
841
+ ring.push([lon + halfW - nodeWidth * t, lat - halfSpan]);
842
+ }
843
+ ring.push([lon - halfW, lat - halfSpan]);
844
+ }
845
+ nodeGeo = { type: "Polygon", coordinates: [ring] };
846
+ if (geoArea(nodeGeo) > 2 * Math.PI) {
847
+ ring.reverse();
848
+ nodeGeo = { type: "Polygon", coordinates: [ring] };
849
+ }
850
+ }
851
+ else {
852
+ const radius = totalWidth / 2 + nodePadding;
853
+ nodeGeo = getGeoCircle({ longitude: lon, latitude: lat }, radius);
854
+ }
855
+ const mapPolygon = nodeDataItem.get("mapPolygon");
856
+ if (mapPolygon) {
857
+ mapPolygon.set("geometry", nodeGeo);
858
+ // Reorder at display level so nodes render above bands.
859
+ // Avoids Children.moveValue which triggers markDirty() loop.
860
+ this._childrenDisplay.removeChild(mapPolygon._display);
861
+ this._childrenDisplay.addChild(mapPolygon._display);
862
+ }
863
+ });
864
+ }
865
+ }
866
+ MapSankeySeries.className = "MapSankeySeries";
867
+ MapSankeySeries.classNames = MapPolygonSeries.classNames.concat([MapSankeySeries.className]);
868
+ //# sourceMappingURL=MapSankeySeries.js.map