@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.
- package/content/map.md +564 -138
- 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.
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
|
257
|
+
**Pattern 2: Explicit coordinates**
|
|
260
258
|
|
|
261
|
-
When using `sourceLongitude`/`sourceLatitude`/`targetLongitude`/`targetLatitude`,
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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 —
|
|
1071
|
+
## Example 5: Map Sankey — Global Coffee Supply Chain (sourceId/targetId)
|
|
1078
1072
|
|
|
1079
|
-
Demonstrates: `MapSankeySeries` with
|
|
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 —
|
|
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
|
|
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
|
-
|
|
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: "
|
|
1102
|
-
projection: am5map.
|
|
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
|
-
//
|
|
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:
|
|
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
|
-
//
|
|
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:
|
|
1179
|
+
maxWidth: 2,
|
|
1119
1180
|
controlPointDistance: 0.4,
|
|
1120
|
-
|
|
1181
|
+
resolution: 60,
|
|
1182
|
+
nodePadding: 0.3
|
|
1121
1183
|
}));
|
|
1122
1184
|
|
|
1123
|
-
// Band appearance
|
|
1124
1185
|
sankeySeries.mapPolygons.template.setAll({
|
|
1125
|
-
fill:
|
|
1126
|
-
fillOpacity: 0.
|
|
1186
|
+
fill: mediumRoast,
|
|
1187
|
+
fillOpacity: 0.65,
|
|
1127
1188
|
strokeOpacity: 0,
|
|
1128
|
-
tooltipText: "{
|
|
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:
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
-
//
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
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 —
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
1191
|
-
|
|
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({
|
|
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:
|
|
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:
|
|
1210
|
-
controlPointDistance: 0.
|
|
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:
|
|
1216
|
-
|
|
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:
|
|
1223
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
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
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
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
|
-
|
|
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>
|