r = require.alias({
maplibregl: "maplibre-gl@2.1.9/dist/maplibre-gl.js",
h3: {},
deck: "deck.gl@8.9.35/dist.min.js"
});
maplibregl = r("maplibregl").catch(() => window["maplibregl"]);
deck = r("deck")
Click to copy this into your story
r = require.alias({
maplibregl: "maplibre-gl@2.1.9/dist/maplibre-gl.js",
h3: {},
deck: "deck.gl@8.9.35/dist.min.js"
});
maplibregl = r("maplibregl").catch(() => window["maplibregl"]);
deck = r("deck")
filterSummary = filteredTransferCount > 0 ?
html`<strong>${filteredTransferCount}</strong> transfers selected totalling at least <strong>$${filteredTransferSum > 1000 ? (filteredTransferSum / 1000).toFixed(1) + " billion" : filteredTransferSum + " million" }</strong> TIV` :
html`No transfers selected`;
html`<a href="#" data-micromodal-trigger="modal-about" style="font-size:smaller"><i class="bi bi-info-circle-fill"></i> What is TIV?</a>`
viewof selectedCountry = Inputs.text({
label: "Who",
placeholder: "Country or group name",
width: "100%" });
// i've added some extra css styling on this one by targeting
// .inputForm input[type="number"]
viewof selectedYear = Inputs.range([1950, 2021], {
label: "Year",
step: 1,
value: 2021,
width: "100%"
});
widthScale =
d3.scaleLinear()
.domain([0, 4500])
.range([0.25, 30]);
transferArcs = new deck.MapboxLayer({
id: "transferArcs",
type: deck.ArcLayer,
data: [],
getSourcePosition:
d => [Number(d.supplier_lon), Number(d.supplier_lat)],
getTargetPosition:
d => [Number(d.recipient_lon), Number(d.recipient_lat)],
getSourceColor: d => [13, 8, 135, 180],
getTargetColor: d => [224, 20, 85, 180],
getWidth: d => widthScale(d.value),
getTilt: d => Number(d.supplier_lon) > Number(d.recipient_lon) ? 0 : -5,
pickable: true,
autoHighlight: true,
highlightColor: [255, 200, 0, 255]
})
/* this is a bit different to regular mapbox/maplibre instantiation
it lets have the map react to other values in the document, like
a button or a timer, without reinstantiating!
(based on https://observablehq.com/@tmcw/using-mapbox-gl-js) */
viewof map = {
let container = html`<div style="position: absolute; left: 0; top: 0; height: 100vh; width: 100%;" />`;
// Give the container dimensions.
yield container;
// Create the \`map\` object with the mapboxgl.Map constructor, referencing
// the container div
let map = new maplibregl.Map({
container,
bounds: [[-175, -80], [175, 85]],
pitch: 30,
antialias: true,
style: "style.json"
});
// on map load:
// - dispatch its value back to ojs
// - add the deck.gl layer to the map
// - add a prop to the layer that adds/removes a popup from the map
// (we can't do this on initial layer def because the map isn't ready yet)
map.on("load", () => {
container.value = map;
container.dispatchEvent(new CustomEvent("input"));
map.addLayer(transferArcs);
function updatePopup(info, event) {
if (info && info.object) {
// TODO - set coordinates with event.center?
// format the amount
const tivText =
info.object.value > 1000 ?
"$" + (info.object.value / 1000).toFixed(1) + " B" :
info.object.value > 0 ?
"$" + info.object.value + " M" :
"< $0.5 M";
popup
.setLngLat(info.coordinate)
.setHTML(
`<span class="title">${info.object.supplier} → ${info.object.recipient}</span></br>
TIV ${tivText} in ${info.object.year}</br>
<a href="#" data-micromodal-trigger="modal-about"><i class="bi bi-info-circle-fill"></i> What is TIV?</a>`)
.addTo(map);
} else {
popup.remove();
}
}
// attach our hover updater to the layer (can't do this until after the
// layer is itself attached)
transferArcs.setProps({ onHover: updatePopup });
// also configure the automatically-create deck instance
transferArcs.deck.setProps({ pickingRadius: 10 });
});
}
allTransfers = FileAttachment("sipri-exports-processed.csv")
.csv({ typed: true });
/* the dataset is filtered first by selected year, then by search term
(we use a separate text input instead of the search's because we want to
retain the search term when the year changes) */
filteredTransfers = allTransfers.filter(
t => t.year == Number(selectedYear) &&
(t.supplier.toLowerCase().includes(selectedCountry.toLowerCase()) ||
t.recipient.toLowerCase().includes(selectedCountry.toLowerCase())));
filteredTransferCount =
filteredTransfers.map(d => d.value).length;
filteredTransferSum = {
const tivsToSum = filteredTransfers.map(d => d.value);
if (tivsToSum.length > 0) {
return tivsToSum.reduce((x, y) => x + y);
} else {
return 0;
}
}
transferArcs.setProps({ data: filteredTransfers });
// TODO - maybe update map bounds based on bounds of currentYearTransfers?
micro = require("micromodal@0.4.10")
micro.init({
awaitOpenAnimation: true,
awaitCloseAnimation: true
});
This map, as well as the analyses that underpin them, are available under a Creative Commons Attribution 4.0 licence.
Please acknowledge 360info and our data sources when you use these charts and data.
Copy and the following code and paste it into your story editor using the Embed button:
<iframe allow="fullscreen; clipboard-write self https://360info-armstrade.pages.dev/" allowfullscreen="true" src="https://360info-armstrade.pages.dev/importexportmap" title="Interactive map: who supplies and receives arms?" style="width:100%; height:500px; border:none; background-color: white;" scrolling="no"></iframe>
This content is subject to 360info’s Terms of Use.
Visit the GitHub repository to:
Trend Indicator Value (TIV) is a kind of price that SIPRI, the provider of this dataset, puts on arms transfers. It doesn’t measure the financial value of the arms transferred but rather the military capability those arms provide.
This is important because while the financial value of different arms can change for many reasons, the lethality of a weapon doesn’t typically change. The TIV helps us determine which countries are contributing or benefitting the most from arms transfers, and how a country’s transfers have changed over time.
To learn more about how these numbers are calculated, read SIPRI’s factsheet.
Transfers include air defence systems, armoured vehicles, artillery, engines, missiles, naval weapons, satellites, sensors and other equipment. For a more detailed breakdown of the types of equipment transferred, refer to SIPRI’s Arms Transfers Database, which allows imports and exports to be broken down by equipment type.
Group placements on the map have been moved in some cases to make transfers clearer. 360info is neutral on the subject of disputed territories.
The data underpinning this map comes from the Stockholm International Peace Research Institute, which licensed data from its Arms Industry Database under Creative Commons 4.0 International.
Vector map tiles are provided by MapLibre under the BSD 3-Clause licence.