@amcharts/amcharts5-mcp 1.1.7 → 1.1.8

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