@amcharts/amcharts5-mcp 1.1.7 → 1.1.9

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 (2) hide show
  1. package/content/map.md +564 -138
  2. package/package.json +1 -1
package/content/map.md CHANGED
@@ -243,22 +243,20 @@ Two data patterns — polygon IDs or explicit coordinates.
243
243
 
244
244
  **Pattern 1: `sourceId` / `targetId` (country codes)**
245
245
 
246
- IDs use ISO 3166-1 alpha-2 codes matching the geodata. **CRITICAL:** Data MUST be set inside `polygonSeries.events.once("datavalidated", ...)` — the polygon series needs to finish parsing geoJSON before MapSankeySeries can resolve country IDs to centroids. Without this wrapper, `getDataItemById()` returns null and flows silently don't appear (no errors, no bands).
246
+ IDs use ISO 3166-1 alpha-2 codes matching the geodata. Set data directly — `MapSankeySeries` auto-resolves `sourceId`/`targetId` once the referenced `polygonSeries` finishes loading, so data can be set at any time:
247
247
 
248
248
  ```js
249
- polygonSeries.events.once("datavalidated", function() {
250
- sankeySeries.data.setAll([
251
- { sourceId: "BR", targetId: "DE", value: 350 },
252
- { sourceId: "BR", targetId: "US", value: 450 },
253
- { sourceId: "DE", targetId: "FR", value: 150 },
254
- { sourceId: "DE", targetId: "PL", value: 100 }
255
- ]);
256
- });
249
+ sankeySeries.data.setAll([
250
+ { sourceId: "BR", targetId: "DE", value: 350 },
251
+ { sourceId: "BR", targetId: "US", value: 450 },
252
+ { sourceId: "DE", targetId: "FR", value: 150 },
253
+ { sourceId: "DE", targetId: "PL", value: 100 }
254
+ ]);
257
255
  ```
258
256
 
259
- **Pattern 2: Explicit coordinates (no timing needed)**
257
+ **Pattern 2: Explicit coordinates**
260
258
 
261
- When using `sourceLongitude`/`sourceLatitude`/`targetLongitude`/`targetLatitude`, data can be set immediately — no `datavalidated` wrapper required:
259
+ When using `sourceLongitude`/`sourceLatitude`/`targetLongitude`/`targetLatitude`, centroid lookup is skipped entirely:
262
260
  ```js
263
261
  sankeySeries.data.setAll([
264
262
  {
@@ -271,17 +269,15 @@ sankeySeries.data.setAll([
271
269
 
272
270
  **Waypoints** — route flows through intermediate points:
273
271
  ```js
274
- polygonSeries.events.once("datavalidated", function() {
275
- sankeySeries.data.setAll([
276
- {
277
- sourceId: "GB", targetId: "JP", value: 40,
278
- waypoints: [
279
- { longitude: 30, latitude: 65 },
280
- { longitude: 90, latitude: 55 }
281
- ]
282
- }
283
- ]);
284
- });
272
+ sankeySeries.data.setAll([
273
+ {
274
+ sourceId: "GB", targetId: "JP", value: 40,
275
+ waypoints: [
276
+ { longitude: 30, latitude: 65 },
277
+ { longitude: 90, latitude: 55 }
278
+ ]
279
+ }
280
+ ]);
285
281
  ```
286
282
 
287
283
  **WARNING:** Never call `polygonSeries.data.setAll()` with custom objects — this replaces all geoJSON-derived data items, breaking MapSankeySeries centroid resolution. To set per-country properties (like colors), iterate existing data items after `datavalidated` instead:
@@ -405,16 +401,14 @@ Bullets automatically hide on the back side of the globe (orthographic projectio
405
401
  The same country can be both target and source — incoming and outgoing bands share unified stacking at intermediate nodes:
406
402
 
407
403
  ```js
408
- polygonSeries.events.once("datavalidated", function() {
409
- sankeySeries.data.setAll([
410
- // Level 1: Producers > Hubs
411
- { sourceId: "BR", targetId: "DE", value: 350 },
412
- { sourceId: "VN", targetId: "DE", value: 200 },
413
- // Level 2: Hubs > Consumers
414
- { sourceId: "DE", targetId: "FR", value: 150 },
415
- { sourceId: "DE", targetId: "PL", value: 100 }
416
- ]);
417
- });
404
+ sankeySeries.data.setAll([
405
+ // Level 1: Producers > Hubs
406
+ { sourceId: "BR", targetId: "DE", value: 350 },
407
+ { sourceId: "VN", targetId: "DE", value: 200 },
408
+ // Level 2: Hubs > Consumers
409
+ { sourceId: "DE", targetId: "FR", value: 150 },
410
+ { sourceId: "DE", targetId: "PL", value: 100 }
411
+ ]);
418
412
  ```
419
413
 
420
414
  ### Animating bullets along lines (positionOnLine)
@@ -1074,220 +1068,652 @@ Demonstrates: orthographic projection, single-segment lines for per-segment anim
1074
1068
  </html>
1075
1069
  ```
1076
1070
 
1077
- ## Example 5: Map Sankey — trade flows between countries
1071
+ ## Example 5: Map Sankey — Global Coffee Supply Chain (sourceId/targetId)
1078
1072
 
1079
- Demonstrates: `MapSankeySeries` with geographic flow bands, node circles, tooltips, and multi-level flows.
1073
+ Demonstrates: `MapSankeySeries` with `sourceId`/`targetId` data (country codes), multi-level flows (producers > hubs > consumers), custom theme, country highlighting, animated coffee bean bullets, Globe/Map projection toggle, and auto-rotation. Based on the `map-sankey` shared example.
1080
1074
 
1081
1075
  ```html
1082
1076
  <!DOCTYPE html>
1083
1077
  <html>
1084
1078
  <head>
1085
1079
  <meta charset="UTF-8">
1086
- <title>Map Sankey — Trade Flows</title>
1080
+ <title>Map Sankey — Global Coffee Supply Chain</title>
1087
1081
  <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
1088
1082
  <script src="https://cdn.amcharts.com/lib/5/map.js"></script>
1089
1083
  <script src="https://cdn.amcharts.com/lib/5/geodata/worldLow.js"></script>
1090
1084
  <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
1091
- <style>#chartdiv { width: 100%; height: 600px; }</style>
1085
+ <style>body { margin: 0; background: #f0e6d6; } #chartdiv { width: 100%; height: 600px; }</style>
1092
1086
  </head>
1093
1087
  <body>
1094
1088
  <div id="chartdiv"></div>
1095
1089
  <script>
1096
1090
  var root = am5.Root.new("chartdiv");
1097
- root.setThemes([am5themes_Animated.new(root)]);
1091
+
1092
+ // Coffee theme — warm earth tones
1093
+ var coffeeTheme = am5.Theme.new(root);
1094
+ coffeeTheme.rule("InterfaceColors").setAll({
1095
+ primaryButton: am5.color(0x8b5e3c),
1096
+ primaryButtonHover: am5.color(0x5c3a1e),
1097
+ primaryButtonDown: am5.color(0x3c1e0e),
1098
+ primaryButtonActive: am5.color(0xc4956a),
1099
+ primaryButtonText: am5.color(0xf5ece0),
1100
+ secondaryButton: am5.color(0xe8d5b7),
1101
+ secondaryButtonHover: am5.color(0xd4c4a8),
1102
+ secondaryButtonDown: am5.color(0xc4956a),
1103
+ secondaryButtonText: am5.color(0x3c1e0e),
1104
+ background: am5.color(0xe8d5b7),
1105
+ text: am5.color(0x3c1e0e)
1106
+ });
1107
+ root.setThemes([am5themes_Animated.new(root), coffeeTheme]);
1108
+
1109
+ // Grainy paper background
1110
+ root.container.set("background", am5.Rectangle.new(root, {
1111
+ fill: am5.color(0xf0e6d6),
1112
+ fillPattern: am5.GrainPattern.new(root, {
1113
+ density: 0.4,
1114
+ maxOpacity: 0.07,
1115
+ colors: [am5.color(0x000000)]
1116
+ })
1117
+ }));
1118
+
1119
+ // Coffee palette
1120
+ var espresso = am5.color(0x3c1e0e);
1121
+ var darkRoast = am5.color(0x5c3a1e);
1122
+ var mediumRoast = am5.color(0x8b5e3c);
1123
+ var lightRoast = am5.color(0xc4956a);
1124
+ var crema = am5.color(0xe8d5b7);
1125
+ var cream = am5.color(0xf5ece0);
1098
1126
 
1099
1127
  var chart = root.container.children.push(am5map.MapChart.new(root, {
1100
1128
  panX: "rotateX",
1101
- panY: "translateY",
1102
- projection: am5map.geoNaturalEarth1()
1129
+ panY: "rotateY",
1130
+ projection: am5map.geoOrthographic(),
1131
+ rotationX: -15,
1132
+ rotationY: -20,
1133
+ minZoomLevel: 0.5,
1134
+ zoomLevel: 0.9
1103
1135
  }));
1104
1136
 
1105
- // Background countries
1137
+ // Ocean / globe background
1138
+ var bgSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {}));
1139
+ bgSeries.mapPolygons.template.setAll({
1140
+ fill: am5.color(0xede4d4), fillOpacity: 1, strokeOpacity: 0
1141
+ });
1142
+ bgSeries.data.push({ geometry: am5map.getGeoRectangle(90, 180, -90, -180) });
1143
+
1144
+ // Graticule
1145
+ var graticuleSeries = chart.series.push(am5map.GraticuleSeries.new(root, {}));
1146
+ graticuleSeries.mapLines.template.setAll({
1147
+ stroke: mediumRoast, strokeOpacity: 0.15, strokeWidth: 0.5
1148
+ });
1149
+
1150
+ // Countries
1106
1151
  var polygonSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {
1107
1152
  geoJSON: am5geodata_worldLow
1108
1153
  }));
1109
1154
  polygonSeries.mapPolygons.template.setAll({
1110
- fill: am5.color(0xdddddd),
1111
- stroke: am5.color(0xffffff),
1112
- strokeWidth: 0.5
1155
+ fill: cream, stroke: lightRoast, strokeWidth: 0.5, strokeOpacity: 0.5
1113
1156
  });
1114
1157
 
1115
- // Sankey flow series
1158
+ // Highlight producer, hub, and consumer countries
1159
+ var producerIds = ["BR", "VN", "CO", "ET", "ID", "HN"];
1160
+ var hubIds = ["DE", "BE", "IT", "US"];
1161
+ var consumerIds = ["FR", "PL", "SE", "RU", "GB", "NL", "GR", "AT", "CA", "JP"];
1162
+
1163
+ polygonSeries.events.on("datavalidated", function() {
1164
+ am5.array.each(polygonSeries.dataItems, function(di) {
1165
+ var id = di.get("id");
1166
+ if (id && producerIds.indexOf(id) !== -1) {
1167
+ di.get("mapPolygon").setAll({ fill: am5.color(0x8fae7e) });
1168
+ } else if (id && hubIds.indexOf(id) !== -1) {
1169
+ di.get("mapPolygon").setAll({ fill: am5.color(0xc4a878) });
1170
+ } else if (id && consumerIds.indexOf(id) !== -1) {
1171
+ di.get("mapPolygon").setAll({ fill: am5.color(0xddc8a0) });
1172
+ }
1173
+ });
1174
+ });
1175
+
1176
+ // MapSankeySeries — coffee supply chain
1116
1177
  var sankeySeries = chart.series.push(am5map.MapSankeySeries.new(root, {
1117
1178
  polygonSeries: polygonSeries,
1118
- maxWidth: 4,
1179
+ maxWidth: 2,
1119
1180
  controlPointDistance: 0.4,
1120
- autoSort: true
1181
+ resolution: 60,
1182
+ nodePadding: 0.3
1121
1183
  }));
1122
1184
 
1123
- // Band appearance
1124
1185
  sankeySeries.mapPolygons.template.setAll({
1125
- fill: am5.color(0xff6b35),
1126
- fillOpacity: 0.5,
1186
+ fill: mediumRoast,
1187
+ fillOpacity: 0.65,
1127
1188
  strokeOpacity: 0,
1128
- tooltipText: "{sourceId} > {targetId}: {value}B"
1189
+ tooltipText: "{sourceNode.name} > {targetNode.name}\n{value}k tonnes"
1129
1190
  });
1130
1191
 
1131
- // Node appearance
1132
1192
  sankeySeries.nodes.mapPolygons.template.setAll({
1133
- fill: am5.color(0x8b5e3c),
1134
- fillOpacity: 0.9,
1135
- stroke: am5.color(0xffffff),
1136
- strokeWidth: 1.5
1193
+ fill: espresso,
1194
+ stroke: crema,
1195
+ strokeWidth: 1.5,
1196
+ fillOpacity: 0.95,
1197
+ strokeOpacity: 1,
1198
+ tooltipText: "{name}\n{sum}k tonnes"
1137
1199
  });
1138
1200
 
1139
- // Set data AFTER polygonSeries parses geoJSON (required for sourceId/targetId)
1140
- polygonSeries.events.once("datavalidated", function() {
1141
- sankeySeries.data.setAll([
1142
- // Producers > Trade hubs
1143
- { sourceId: "BR", targetId: "DE", value: 350 },
1144
- { sourceId: "BR", targetId: "US", value: 450 },
1145
- { sourceId: "CN", targetId: "DE", value: 500 },
1146
- { sourceId: "CN", targetId: "US", value: 600 },
1147
- { sourceId: "AU", targetId: "JP", value: 200 },
1148
- { sourceId: "AU", targetId: "CN", value: 300 },
1149
- // Hubs > Consumers
1150
- { sourceId: "DE", targetId: "FR", value: 250 },
1151
- { sourceId: "DE", targetId: "GB", value: 300 },
1152
- { sourceId: "US", targetId: "CA", value: 400 },
1153
- { sourceId: "JP", targetId: "KR", value: 100 }
1154
- ]);
1201
+ // Animated coffee bean bullets
1202
+ sankeySeries.bullets.push(function() {
1203
+ return am5.Bullet.new(root, {
1204
+ locationX: 0,
1205
+ autoRotate: true,
1206
+ sprite: am5.Graphics.new(root, {
1207
+ svgPath: "M-4,-2.5 C-4,-5 -1.5,-6.5 1,-6.5 C3.5,-6.5 5,-4.5 5,-2 C5,1 3,3.5 0.5,5 C-0.5,5.7 -1.5,5.7 -2.5,5 C-5,3.5 -6,1 -4,-2.5 Z M-1,-5 C-1,-1 -1,2 -0.5,4.5",
1208
+ fill: espresso,
1209
+ stroke: darkRoast,
1210
+ strokeWidth: 0.5,
1211
+ centerX: am5.p50,
1212
+ centerY: am5.p50,
1213
+ scale: 0.35,
1214
+ visible: false
1215
+ })
1216
+ });
1217
+ });
1218
+
1219
+ // Set data directly — MapSankeySeries auto-resolves sourceId/targetId
1220
+ // once polygonSeries finishes loading.
1221
+ sankeySeries.data.setAll([
1222
+ // Producers > Hubs
1223
+ { sourceId: "BR", targetId: "DE", value: 350 },
1224
+ { sourceId: "BR", targetId: "US", value: 450 },
1225
+ { sourceId: "BR", targetId: "IT", value: 200 },
1226
+ { sourceId: "VN", targetId: "DE", value: 200 },
1227
+ { sourceId: "VN", targetId: "BE", value: 150 },
1228
+ { sourceId: "CO", targetId: "US", value: 250 },
1229
+ { sourceId: "CO", targetId: "DE", value: 80 },
1230
+ { sourceId: "ET", targetId: "DE", value: 60 },
1231
+ { sourceId: "ET", targetId: "BE", value: 40 },
1232
+ { sourceId: "ID", targetId: "US", value: 80 },
1233
+ { sourceId: "HN", targetId: "DE", value: 60 },
1234
+ { sourceId: "HN", targetId: "BE", value: 40 },
1235
+ // Hubs > Consumer Markets
1236
+ { sourceId: "DE", targetId: "FR", value: 150 },
1237
+ { sourceId: "DE", targetId: "PL", value: 100 },
1238
+ { sourceId: "DE", targetId: "SE", value: 80 },
1239
+ { sourceId: "DE", targetId: "RU", value: 120 },
1240
+ { sourceId: "BE", targetId: "GB", value: 100 },
1241
+ { sourceId: "BE", targetId: "NL", value: 80 },
1242
+ { sourceId: "IT", targetId: "GR", value: 50 },
1243
+ { sourceId: "IT", targetId: "AT", value: 40 },
1244
+ { sourceId: "US", targetId: "CA", value: 120 },
1245
+ { sourceId: "US", targetId: "JP", value: 80 }
1246
+ ]);
1247
+
1248
+ // Set country names on auto-created nodes and animate bullets
1249
+ var countryNames = {
1250
+ BR: "Brazil", VN: "Vietnam", CO: "Colombia", ET: "Ethiopia",
1251
+ ID: "Indonesia", HN: "Honduras", DE: "Germany", BE: "Belgium",
1252
+ IT: "Italy", US: "United States", FR: "France", PL: "Poland",
1253
+ SE: "Sweden", RU: "Russia", GB: "United Kingdom", NL: "Netherlands",
1254
+ GR: "Greece", AT: "Austria", CA: "Canada", JP: "Japan"
1255
+ };
1256
+
1257
+ sankeySeries.events.on("datavalidated", function() {
1258
+ am5.array.each(sankeySeries.nodes.dataItems, function(di) {
1259
+ var id = di.get("id");
1260
+ if (id && countryNames[id]) {
1261
+ di.set("name", countryNames[id]);
1262
+ }
1263
+ });
1264
+
1265
+ am5.array.each(sankeySeries.dataItems, function(dataItem) {
1266
+ var bullets = dataItem.bullets;
1267
+ if (bullets) {
1268
+ am5.array.each(bullets, function(bullet) {
1269
+ var randomDur = 3000 + Math.random() * 3000;
1270
+ var delay = Math.random() * randomDur;
1271
+ setTimeout(function() {
1272
+ var sprite = bullet.get("sprite");
1273
+ if (sprite) sprite.set("visible", true);
1274
+ bullet.animate({
1275
+ key: "locationX",
1276
+ from: 0,
1277
+ to: 1,
1278
+ duration: randomDur,
1279
+ easing: am5.ease.linear,
1280
+ loops: Infinity
1281
+ });
1282
+ }, delay);
1283
+ });
1284
+ }
1285
+ });
1286
+ });
1287
+
1288
+ // Title
1289
+ var titleCont = chart.children.push(am5.Container.new(root, {
1290
+ layout: root.verticalLayout,
1291
+ x: am5.p50, centerX: am5.p50,
1292
+ y: am5.p100, centerY: am5.p100,
1293
+ position: "absolute", paddingBottom: 16
1294
+ }));
1295
+ titleCont.children.push(am5.Label.new(root, {
1296
+ text: "Global Coffee Supply Chain",
1297
+ fontSize: 18, fontWeight: "600", fill: espresso,
1298
+ x: am5.p50, centerX: am5.p50
1299
+ }));
1300
+ titleCont.children.push(am5.Label.new(root, {
1301
+ text: "(Thousands of tonnes)",
1302
+ fontSize: 11, fill: mediumRoast,
1303
+ x: am5.p50, centerX: am5.p50
1304
+ }));
1305
+
1306
+ // Globe / Map toggle
1307
+ var switchCont = chart.children.push(am5.Container.new(root, {
1308
+ layout: root.horizontalLayout, x: 20, y: 40
1309
+ }));
1310
+ switchCont.children.push(am5.Label.new(root, {
1311
+ centerY: am5.p50, text: "Globe", fill: espresso, fontSize: 13
1312
+ }));
1313
+ var switchButton = switchCont.children.push(am5.Button.new(root, {
1314
+ themeTags: ["switch"], centerY: am5.p50,
1315
+ icon: am5.Circle.new(root, { themeTags: ["icon"] })
1316
+ }));
1317
+
1318
+ var easing = am5.ease.inOut(am5.ease.cubic);
1319
+ var duration = 1500;
1320
+ var fadeDuration = 300;
1321
+
1322
+ function zoomToGlobe() {
1323
+ chart.set("projection", am5map.geoOrthographic());
1324
+ chart.set("panX", "rotateX");
1325
+ chart.set("panY", "rotateY");
1326
+ chart.animate({ key: "rotationX", to: -15, duration: duration, easing: easing });
1327
+ chart.animate({ key: "rotationY", to: -20, duration: duration, easing: easing });
1328
+ bgSeries.mapPolygons.template.set("fillOpacity", 1);
1329
+ chart.set("minZoomLevel", 0.9);
1330
+ chart.animate({ key: "zoomLevel", to: 0.9, duration: duration, easing: easing });
1331
+ }
1332
+
1333
+ function zoomToMap() {
1334
+ chart.set("projection", am5map.geoMercator());
1335
+ chart.set("panX", "translateX");
1336
+ chart.set("panY", "translateY");
1337
+ chart.animate({ key: "rotationX", to: 0, duration: duration, easing: easing });
1338
+ chart.animate({ key: "rotationY", to: 0, duration: duration, easing: easing });
1339
+ bgSeries.mapPolygons.template.set("fillOpacity", 0);
1340
+ chart.set("minZoomLevel", 1);
1341
+ chart.animate({ key: "zoomLevel", to: 1.7, duration: duration, easing: easing });
1342
+ }
1343
+
1344
+ switchButton.on("active", function() {
1345
+ chart.goHome(duration);
1346
+ setTimeout(function() {
1347
+ chart.seriesContainer.animate({ key: "opacity", to: 0, duration: fadeDuration });
1348
+ }, duration - fadeDuration);
1349
+ setTimeout(function() {
1350
+ if (switchButton.get("active")) { zoomToMap(); } else { zoomToGlobe(); }
1351
+ chart.seriesContainer.animate({ key: "opacity", to: 1, duration: fadeDuration });
1352
+ }, duration);
1353
+ });
1354
+
1355
+ switchCont.children.push(am5.Label.new(root, {
1356
+ centerY: am5.p50, text: "Map", fill: espresso, fontSize: 13
1357
+ }));
1358
+
1359
+ // Zoom controls
1360
+ var zoomControl = chart.set("zoomControl", am5map.ZoomControl.new(root, {}));
1361
+ zoomControl.homeButton.set("visible", true);
1362
+
1363
+ // Auto-rotate globe until user interaction
1364
+ var rotationAnimation = chart.animate({
1365
+ key: "rotationX", from: -15, to: -15 + 360,
1366
+ duration: 120000, loops: Infinity, easing: am5.ease.linear
1367
+ });
1368
+ chart.chartContainer.events.on("pointerdown", function() {
1369
+ if (rotationAnimation) { rotationAnimation.stop(); rotationAnimation = null; }
1155
1370
  });
1156
1371
 
1157
- chart.set("zoomControl", am5map.ZoomControl.new(root, {}));
1158
1372
  chart.appear(1000, 100);
1159
1373
  </script>
1160
1374
  </body>
1161
1375
  </html>
1162
1376
  ```
1163
1377
 
1164
- ## Example 6: Map Sankey — explicit coordinates with waypoints
1378
+ ## Example 6: Map Sankey — Oil Export Flows with Waypoints (explicit coordinates)
1379
+
1380
+ Demonstrates: `MapSankeySeries` with explicit `sourceLongitude`/`sourceLatitude` coordinates, maritime waypoints for realistic shipping routes, oil drop SVG bullets with proportional animation speed, dark theme, country highlighting, and Globe/Map projection toggle. Based on the `map-sankey-waypoints` shared example.
1165
1381
 
1166
- Demonstrates: `MapSankeySeries` with explicit `sourceLongitude`/`sourceLatitude` coordinates, waypoints for routing, and animated bullets. No `datavalidated` timing needed when using explicit coordinates.
1382
+ **IMPORTANT — waypoints must use `longitude`/`latitude` keys** (not shorthand like `lon`/`lat`). Source/target location objects can use any keys because the `flow()` helper maps them to `sourceLongitude`/`sourceLatitude`, but waypoints are read directly by the series via `wp.longitude` and `wp.latitude`.
1167
1383
 
1168
1384
  ```html
1169
1385
  <!DOCTYPE html>
1170
1386
  <html>
1171
1387
  <head>
1172
1388
  <meta charset="UTF-8">
1173
- <title>Map Sankey — Oil Flows</title>
1389
+ <title>Map Sankey — Oil Export Flows</title>
1174
1390
  <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
1175
1391
  <script src="https://cdn.amcharts.com/lib/5/map.js"></script>
1176
1392
  <script src="https://cdn.amcharts.com/lib/5/geodata/worldLow.js"></script>
1177
1393
  <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
1178
- <style>body { background: #1a2a3a; margin: 0; } #chartdiv { width: 100%; height: 600px; }</style>
1394
+ <style>body { margin: 0; background: #0a0e12; } #chartdiv { width: 100%; height: 600px; }</style>
1179
1395
  </head>
1180
1396
  <body>
1181
1397
  <div id="chartdiv"></div>
1182
1398
  <script>
1183
1399
  var root = am5.Root.new("chartdiv");
1184
- root.setThemes([am5themes_Animated.new(root)]);
1400
+
1401
+ // Oil/dark theme
1402
+ var oilTheme = am5.Theme.new(root);
1403
+ oilTheme.rule("InterfaceColors").setAll({
1404
+ primaryButton: am5.color(0x1e2824),
1405
+ primaryButtonHover: am5.color(0x3a5040),
1406
+ primaryButtonDown: am5.color(0x0a0e12),
1407
+ primaryButtonActive: am5.color(0xc8890a),
1408
+ primaryButtonText: am5.color(0xf5d090),
1409
+ secondaryButton: am5.color(0x1e2824),
1410
+ secondaryButtonHover: am5.color(0x3a5040),
1411
+ secondaryButtonDown: am5.color(0x141a1a),
1412
+ secondaryButtonText: am5.color(0xf5d090),
1413
+ background: am5.color(0x0a0e12),
1414
+ text: am5.color(0xf5d090)
1415
+ });
1416
+ root.setThemes([am5themes_Animated.new(root), oilTheme]);
1417
+
1418
+ // Palette
1419
+ var deepCrude = am5.color(0x141a1a);
1420
+ var slick = am5.color(0x1e2824);
1421
+ var pipeline = am5.color(0x3a5040);
1422
+ var amber = am5.color(0xc8890a);
1423
+ var sulfur = am5.color(0xa89050);
1424
+ var flare = am5.color(0xf5d090);
1185
1425
 
1186
1426
  var chart = root.container.children.push(am5map.MapChart.new(root, {
1187
1427
  panX: "rotateX",
1188
1428
  panY: "rotateY",
1189
1429
  projection: am5map.geoOrthographic(),
1190
- rotationX: -54,
1191
- rotationY: -25
1430
+ homeGeoPoint: { longitude: 0, latitude: 0 },
1431
+ homeRotationX: 0,
1432
+ homeRotationY: 0,
1433
+ homeZoomLevel: 1,
1434
+ minZoomLevel: 0.8,
1435
+ zoomLevel: 1
1192
1436
  }));
1193
1437
 
1438
+ // Ocean
1194
1439
  var bgSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {}));
1195
- bgSeries.mapPolygons.template.setAll({ fill: am5.color(0x111d2d), fillOpacity: 1, strokeOpacity: 0 });
1440
+ bgSeries.mapPolygons.template.setAll({
1441
+ fill: deepCrude, fillOpacity: 1, strokeOpacity: 0
1442
+ });
1196
1443
  bgSeries.data.push({ geometry: am5map.getGeoRectangle(90, 180, -90, -180) });
1197
1444
 
1445
+ // Graticule
1446
+ var graticuleSeries = chart.series.push(am5map.GraticuleSeries.new(root, {}));
1447
+ graticuleSeries.mapLines.template.setAll({
1448
+ stroke: pipeline, strokeOpacity: 0.25, strokeWidth: 0.5
1449
+ });
1450
+
1451
+ // Countries
1198
1452
  var polygonSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {
1199
1453
  geoJSON: am5geodata_worldLow
1200
1454
  }));
1201
1455
  polygonSeries.mapPolygons.template.setAll({
1202
- fill: am5.color(0x2c4a6a),
1203
- stroke: am5.color(0x1a2a3a),
1204
- strokeWidth: 0.5
1456
+ fill: slick, stroke: pipeline, strokeWidth: 0.5, strokeOpacity: 0.6
1205
1457
  });
1206
1458
 
1459
+ // Highlight producer & importer countries
1460
+ var producerIds = ["SA", "IQ", "AE", "KW", "IR", "QA"];
1461
+ var importerIds = ["CN", "JP", "KR", "IN", "SG", "IT", "GR", "NL"];
1462
+ polygonSeries.events.on("datavalidated", function() {
1463
+ am5.array.each(polygonSeries.dataItems, function(di) {
1464
+ var id = di.get("id");
1465
+ if (producerIds.indexOf(id) !== -1) {
1466
+ di.get("mapPolygon").setAll({ fill: am5.color(0x6b5a10), tooltipText: "{name}" });
1467
+ } else if (importerIds.indexOf(id) !== -1) {
1468
+ di.get("mapPolygon").setAll({ fill: am5.color(0x2a4030), tooltipText: "{name}" });
1469
+ }
1470
+ });
1471
+ });
1472
+
1473
+ // MapSankeySeries
1207
1474
  var sankeySeries = chart.series.push(am5map.MapSankeySeries.new(root, {
1208
1475
  polygonSeries: polygonSeries,
1209
- maxWidth: 3,
1210
- controlPointDistance: 0.3,
1211
- resolution: 60
1476
+ maxWidth: 0.8,
1477
+ controlPointDistance: 0.4,
1478
+ resolution: 60,
1479
+ nodePadding: 0.1
1212
1480
  }));
1213
1481
 
1214
1482
  sankeySeries.mapPolygons.template.setAll({
1215
- fill: am5.color(0xd4a017),
1216
- fillOpacity: 0.6,
1217
- strokeOpacity: 0,
1218
- tooltipText: "{source} > {target}: {value}M barrels/day"
1483
+ fill: amber, fillOpacity: 0.4, strokeOpacity: 0,
1484
+ tooltipText: "{source} > {target}\n{value}k bbl/day"
1219
1485
  });
1220
1486
 
1221
1487
  sankeySeries.nodes.mapPolygons.template.setAll({
1222
- fill: am5.color(0xd4a017),
1223
- stroke: am5.color(0xf0e6d0),
1224
- strokeWidth: 1.5,
1225
- fillOpacity: 0.9,
1226
- tooltipText: "{name}"
1488
+ fill: amber, stroke: flare, strokeWidth: 1.5,
1489
+ fillOpacity: 0.95, strokeOpacity: 1, tooltipText: "{name}"
1227
1490
  });
1228
1491
 
1229
- // Helper for explicit-coordinate data with optional waypoints
1230
- function flow(src, srcName, tgt, tgtName, val, wp) {
1231
- var result = {
1232
- sourceLongitude: src.lon, sourceLatitude: src.lat,
1233
- targetLongitude: tgt.lon, targetLatitude: tgt.lat,
1234
- source: srcName, target: tgtName, value: val
1235
- };
1236
- if (wp) result.waypoints = wp;
1237
- return result;
1238
- }
1239
-
1240
- var saudiPort = { lon: 50.2, lat: 26.6 };
1241
- var hormuz = { lon: 56.3, lat: 26.6 };
1242
- var india = { lon: 72.8, lat: 19.1 };
1243
- var china = { lon: 121.5, lat: 31.2 };
1244
- var japan = { lon: 139.7, lat: 35.7 };
1245
- var europe = { lon: 9.5, lat: 44.4 };
1246
- var suez = { lon: 32.3, lat: 30.0 };
1247
-
1248
- // No datavalidated wrapper needed — explicit coordinates resolve immediately
1249
- sankeySeries.data.setAll([
1250
- flow(saudiPort, "Saudi Arabia", china, "China", 1.8,
1251
- [hormuz, { lon: 65, lat: 20 }, { lon: 80, lat: 15 }, { lon: 105, lat: 20 }]),
1252
- flow(saudiPort, "Saudi Arabia", japan, "Japan", 1.2,
1253
- [hormuz, { lon: 70, lat: 22 }, { lon: 100, lat: 18 }, { lon: 125, lat: 28 }]),
1254
- flow(saudiPort, "Saudi Arabia", india, "India", 0.9,
1255
- [hormuz, { lon: 62, lat: 22 }]),
1256
- flow(saudiPort, "Saudi Arabia", europe, "Europe", 1.0,
1257
- [{ lon: 43, lat: 28 }, suez, { lon: 20, lat: 36 }])
1258
- ]);
1259
-
1260
- // Animated bullets along flow paths
1492
+ // Animated oil drop bullets
1261
1493
  sankeySeries.bullets.push(function() {
1262
1494
  return am5.Bullet.new(root, {
1263
1495
  locationX: 0,
1264
1496
  autoRotate: true,
1265
- sprite: am5.Circle.new(root, {
1266
- radius: 2,
1267
- fill: am5.color(0xd4a017)
1497
+ autoRotateAngle: -90,
1498
+ sprite: am5.Graphics.new(root, {
1499
+ svgPath: "M0,-7 C2,-4 5,0 5,3 C5,6 3,8 0,8 C-3,8 -5,6 -5,3 C-5,0 -2,-4 0,-7 Z",
1500
+ fill: amber, stroke: am5.color(0x8b6914), strokeWidth: 0.5,
1501
+ centerX: am5.p50, centerY: am5.p50, scale: 0.45, visible: false
1268
1502
  })
1269
1503
  });
1270
1504
  });
1271
1505
 
1272
1506
  sankeySeries.events.on("datavalidated", function() {
1507
+ // Scale animation duration proportionally to path length
1508
+ var maxLength = 0;
1509
+ am5.array.each(sankeySeries.dataItems, function(dataItem) {
1510
+ var len = sankeySeries.getPathLength(dataItem);
1511
+ if (len > maxLength) maxLength = len;
1512
+ });
1513
+
1514
+ var baseDuration = 8000;
1515
+ var minDuration = 2000;
1516
+
1273
1517
  am5.array.each(sankeySeries.dataItems, function(dataItem) {
1518
+ var pathLength = sankeySeries.getPathLength(dataItem) || maxLength;
1519
+ var dur = maxLength > 0
1520
+ ? Math.max(minDuration, (pathLength / maxLength) * baseDuration)
1521
+ : baseDuration;
1274
1522
  var bullets = dataItem.bullets;
1275
1523
  if (bullets) {
1276
1524
  am5.array.each(bullets, function(bullet) {
1277
- bullet.animate({
1278
- key: "locationX",
1279
- from: 0, to: 1,
1280
- duration: 3000 + Math.random() * 3000,
1281
- easing: am5.ease.linear,
1282
- loops: Infinity
1283
- });
1525
+ var randomDur = dur * (0.8 + Math.random() * 0.4);
1526
+ var delay = Math.random() * randomDur;
1527
+ setTimeout(function() {
1528
+ var sprite = bullet.get("sprite");
1529
+ if (sprite) sprite.set("visible", true);
1530
+ bullet.animate({
1531
+ key: "locationX", from: 0, to: 1,
1532
+ duration: randomDur, easing: am5.ease.linear, loops: Infinity
1533
+ });
1534
+ }, delay);
1284
1535
  });
1285
1536
  }
1286
1537
  });
1287
1538
  });
1288
1539
 
1289
- chart.set("zoomControl", am5map.ZoomControl.new(root, {}));
1540
+ // Gulf export terminals (source/target objects can use any key names
1541
+ // because the flow() helper maps them to sourceLongitude/sourceLatitude)
1542
+ var rasTanura = { lon: 50.17, lat: 26.64 };
1543
+ var basra = { lon: 48.80, lat: 29.69 };
1544
+ var fujairah = { lon: 56.33, lat: 25.12 };
1545
+ var minaAhmadi = { lon: 48.17, lat: 29.08 };
1546
+ var khargIsland = { lon: 50.32, lat: 29.23 };
1547
+ var rasLaffan = { lon: 51.56, lat: 25.93 };
1548
+
1549
+ // Import terminals
1550
+ var ningbo = { lon: 121.97, lat: 29.87 };
1551
+ var qingdao = { lon: 120.38, lat: 36.07 };
1552
+ var yokohama = { lon: 139.64, lat: 35.44 };
1553
+ var chiba = { lon: 140.10, lat: 35.60 };
1554
+ var ulsan = { lon: 129.38, lat: 35.50 };
1555
+ var jamnagar = { lon: 69.66, lat: 22.42 };
1556
+ var mumbai = { lon: 72.88, lat: 19.08 };
1557
+ var singapore = { lon: 103.84, lat: 1.26 };
1558
+ var trieste = { lon: 13.78, lat: 45.65 };
1559
+ var piraeus = { lon: 23.63, lat: 37.94 };
1560
+ var rotterdam = { lon: 4.12, lat: 51.95 };
1561
+
1562
+ function flow(src, srcName, tgt, tgtName, value, wp) {
1563
+ var result = {
1564
+ sourceLongitude: src.lon, sourceLatitude: src.lat,
1565
+ targetLongitude: tgt.lon, targetLatitude: tgt.lat,
1566
+ source: srcName, target: tgtName, value: value
1567
+ };
1568
+ if (wp) result.waypoints = wp;
1569
+ return result;
1570
+ }
1571
+
1572
+ // IMPORTANT: waypoints must use { longitude: ..., latitude: ... }
1573
+ // (NOT shorthand like { lon, lat }) — the series reads wp.longitude / wp.latitude directly
1574
+ var wpHormuz = { longitude: 58, latitude: 24 };
1575
+ var wpArabianSea = { longitude: 64, latitude: 18 };
1576
+ var wpSouthIndia = { longitude: 78, latitude: 6 };
1577
+ var wpMalacca = { longitude: 101, latitude: 3 };
1578
+ var wpAden = { longitude: 47, latitude: 12 };
1579
+ var wpSuez = { longitude: 34, latitude: 29 };
1580
+
1581
+ // Route templates
1582
+ var viaJapan = [{ longitude: 58, latitude: 26 }, { longitude: 80, latitude: 9 }, { longitude: 104, latitude: 7 }];
1583
+ var viaKorea = [{ longitude: 58, latitude: 24 }, { longitude: 78, latitude: 6 }, { longitude: 101, latitude: 3 }];
1584
+ var viaChina = [{ longitude: 58, latitude: 22 }, { longitude: 76, latitude: 3 }, { longitude: 99, latitude: 1 }];
1585
+ var viaChinaNorth = [{ longitude: 58, latitude: 22 }, { longitude: 76, latitude: 3 }, { longitude: 99, latitude: 1 }, { longitude: 124, latitude: 32 }];
1586
+ var viaIndiaNear = [wpHormuz];
1587
+ var viaIndiaFar = [wpHormuz, wpArabianSea];
1588
+ var viaSingapore = [wpHormuz, wpSouthIndia];
1589
+ var viaSuez = [wpHormuz, wpAden, wpSuez];
1590
+ var wpSouthGreece = { longitude: 22, latitude: 35 };
1591
+ var wpOtranto = { longitude: 18.5, latitude: 40 };
1592
+ var viaSuezItaly = [wpHormuz, wpAden, wpSuez, wpSouthGreece, wpOtranto];
1593
+ var wpSicily = { longitude: 13, latitude: 37 };
1594
+ var wpPortugal = { longitude: -10, latitude: 39 };
1595
+ var viaAtlantic = [wpHormuz, wpAden, wpSuez, wpSicily, wpPortugal];
1596
+
1597
+ sankeySeries.data.setAll([
1598
+ // Ras Tanura (Saudi Arabia)
1599
+ flow(rasTanura, "Ras Tanura", ningbo, "Ningbo", 1100, viaChina),
1600
+ flow(rasTanura, "Ras Tanura", qingdao, "Qingdao", 600, viaChinaNorth),
1601
+ flow(rasTanura, "Ras Tanura", yokohama, "Yokohama", 700, viaJapan),
1602
+ flow(rasTanura, "Ras Tanura", chiba, "Chiba", 300, viaJapan),
1603
+ flow(rasTanura, "Ras Tanura", ulsan, "Ulsan", 800, viaKorea),
1604
+ flow(rasTanura, "Ras Tanura", jamnagar, "Jamnagar", 500, viaIndiaNear),
1605
+ flow(rasTanura, "Ras Tanura", mumbai, "Mumbai", 300, viaIndiaFar),
1606
+ flow(rasTanura, "Ras Tanura", rotterdam, "Rotterdam", 350, viaAtlantic),
1607
+ // Basra Oil Terminal (Iraq)
1608
+ flow(basra, "Basra", ningbo, "Ningbo", 700, viaChina),
1609
+ flow(basra, "Basra", qingdao, "Qingdao", 400, viaChinaNorth),
1610
+ flow(basra, "Basra", jamnagar, "Jamnagar", 600, viaIndiaNear),
1611
+ flow(basra, "Basra", mumbai, "Mumbai", 400, viaIndiaFar),
1612
+ flow(basra, "Basra", ulsan, "Ulsan", 400, viaKorea),
1613
+ flow(basra, "Basra", trieste, "Trieste", 350, viaSuezItaly),
1614
+ flow(basra, "Basra", piraeus, "Piraeus", 250, viaSuez),
1615
+ // Fujairah (UAE) — already outside Hormuz
1616
+ flow(fujairah, "Fujairah", yokohama, "Yokohama", 400, viaJapan.slice(1)),
1617
+ flow(fujairah, "Fujairah", chiba, "Chiba", 200, viaJapan.slice(1)),
1618
+ flow(fujairah, "Fujairah", jamnagar, "Jamnagar", 300),
1619
+ flow(fujairah, "Fujairah", mumbai, "Mumbai", 200, [wpArabianSea]),
1620
+ flow(fujairah, "Fujairah", ningbo, "Ningbo", 250, viaChina.slice(1)),
1621
+ flow(fujairah, "Fujairah", ulsan, "Ulsan", 350, viaKorea.slice(1)),
1622
+ flow(fujairah, "Fujairah", singapore, "Singapore", 250, [wpSouthIndia]),
1623
+ // Mina Al Ahmadi (Kuwait)
1624
+ flow(minaAhmadi, "Mina Al Ahmadi", ulsan, "Ulsan", 400, viaKorea),
1625
+ flow(minaAhmadi, "Mina Al Ahmadi", yokohama, "Yokohama", 300, viaJapan),
1626
+ flow(minaAhmadi, "Mina Al Ahmadi", ningbo, "Ningbo", 300, viaChina),
1627
+ flow(minaAhmadi, "Mina Al Ahmadi", jamnagar, "Jamnagar", 200, viaIndiaNear),
1628
+ // Kharg Island (Iran)
1629
+ flow(khargIsland, "Kharg Island", ningbo, "Ningbo", 400, viaChina),
1630
+ flow(khargIsland, "Kharg Island", qingdao, "Qingdao", 200, viaChinaNorth),
1631
+ flow(khargIsland, "Kharg Island", jamnagar, "Jamnagar", 200, viaIndiaNear),
1632
+ flow(khargIsland, "Kharg Island", piraeus, "Piraeus", 100, viaSuez),
1633
+ // Ras Laffan (Qatar)
1634
+ flow(rasLaffan, "Ras Laffan", yokohama, "Yokohama", 300, viaJapan),
1635
+ flow(rasLaffan, "Ras Laffan", ulsan, "Ulsan", 200, viaKorea),
1636
+ flow(rasLaffan, "Ras Laffan", jamnagar, "Jamnagar", 150, viaIndiaNear),
1637
+ flow(rasLaffan, "Ras Laffan", singapore,"Singapore", 120, viaSingapore)
1638
+ ]);
1639
+
1640
+ // Title
1641
+ var titleCont = chart.children.push(am5.Container.new(root, {
1642
+ layout: root.verticalLayout,
1643
+ x: am5.p50, centerX: am5.p50, y: 0,
1644
+ position: "absolute", paddingTop: 16
1645
+ }));
1646
+ titleCont.children.push(am5.Label.new(root, {
1647
+ text: "Strait of Hormuz — Oil Export Flows",
1648
+ fontSize: 18, fontWeight: "600", fill: amber,
1649
+ x: am5.p50, centerX: am5.p50
1650
+ }));
1651
+ titleCont.children.push(am5.Label.new(root, {
1652
+ text: "Gulf Producers > Major Importers (thousands of barrels/day)",
1653
+ fontSize: 11, fill: sulfur,
1654
+ x: am5.p50, centerX: am5.p50
1655
+ }));
1656
+
1657
+ // Globe / Map toggle
1658
+ var switchCont = chart.children.push(am5.Container.new(root, {
1659
+ layout: root.horizontalLayout, x: 20, y: 40
1660
+ }));
1661
+ switchCont.children.push(am5.Label.new(root, {
1662
+ centerY: am5.p50, text: "Globe", fill: flare, fontSize: 13
1663
+ }));
1664
+ var switchButton = switchCont.children.push(am5.Button.new(root, {
1665
+ themeTags: ["switch"], centerY: am5.p50,
1666
+ icon: am5.Circle.new(root, { themeTags: ["icon"] })
1667
+ }));
1668
+
1669
+ var easing = am5.ease.inOut(am5.ease.cubic);
1670
+ var duration = 1500;
1671
+ var fadeDuration = 300;
1672
+
1673
+ function zoomToGlobe() {
1674
+ chart.set("projection", am5map.geoOrthographic());
1675
+ chart.set("panX", "rotateX");
1676
+ chart.set("panY", "rotateY");
1677
+ chart.animate({ key: "rotationX", to: -74, duration: duration, easing: easing });
1678
+ chart.animate({ key: "rotationY", to: -30, duration: duration, easing: easing });
1679
+ chart.animate({ key: "zoomLevel", to: 1.6, duration: duration, easing: easing });
1680
+ bgSeries.mapPolygons.template.set("fillOpacity", 1);
1681
+ }
1682
+
1683
+ function zoomToMap() {
1684
+ chart.set("projection", am5map.geoMercator());
1685
+ chart.set("panX", "translateX");
1686
+ chart.set("panY", "translateY");
1687
+ chart.animate({ key: "rotationY", to: 0, duration: duration, easing: easing });
1688
+ setTimeout(function() {
1689
+ chart.zoomToGeoPoint({ longitude: 67, latitude: 32 }, 3.5, true, duration);
1690
+ }, 100);
1691
+ bgSeries.mapPolygons.template.set("fillOpacity", 0);
1692
+ }
1693
+
1694
+ switchButton.on("active", function() {
1695
+ chart.goHome(duration);
1696
+ setTimeout(function() {
1697
+ chart.seriesContainer.animate({ key: "opacity", to: 0, duration: fadeDuration });
1698
+ }, duration - fadeDuration);
1699
+ setTimeout(function() {
1700
+ if (switchButton.get("active")) { zoomToMap(); } else { zoomToGlobe(); }
1701
+ chart.seriesContainer.animate({ key: "opacity", to: 1, duration: fadeDuration });
1702
+ }, duration);
1703
+ });
1704
+
1705
+ switchCont.children.push(am5.Label.new(root, {
1706
+ centerY: am5.p50, text: "Map", fill: flare, fontSize: 13
1707
+ }));
1708
+
1709
+ // Zoom controls
1710
+ var zoomControl = chart.set("zoomControl", am5map.ZoomControl.new(root, {}));
1711
+ zoomControl.homeButton.set("visible", true);
1712
+
1290
1713
  chart.appear(1000, 100);
1714
+
1715
+ // Initial animation: rotate to globe position
1716
+ setTimeout(function() { zoomToGlobe(); }, 1000);
1291
1717
  </script>
1292
1718
  </body>
1293
1719
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amcharts/amcharts5-mcp",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "mcpName": "io.github.amcharts/amcharts5",
5
5
  "description": "On-demand access to amCharts 5 docs, code examples, and API reference",
6
6
  "type": "module",