container = {
const { lat, long, z } = mapConfig;
let container = DOM.element("div", {
id: "map",
style: `width:${width}px;height:${height}px`
});
yield container;
var osm = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "© OpenStreetMap"
});
let map = L.map("map", {
center: new L.LatLng(lat, long),
zoom: z,
layers: [osm],
zoomControl: true,
scrollWheelZoom: false
});
let osmLayer = L.tileLayer(
"https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}@2x.png",
{
attribution:
'Wikimedia maps beta | © <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}
).addTo(map);
L.svg().addTo(map);
const mapSelect = d3.select("#map");
mapSelect.style("cursor", "pointer");
const svg = d3.select("#map").select("svg");
const node = svg
.append("g")
.selectAll(".node")
.data(nodes)
.join("g")
.attr("class", "node")
.attr("transform", (d) => {
const { x, y } = map.latLngToLayerPoint([d.latitude, d.longitude]);
return `translate(${x}, ${y})`;
});
node
.style("pointer-events", "all")
.on("mouseover mousemove", function (event) {
const group = d3.select(this);
const text = group.select("text");
text.style("fill", "white");
})
.on("mouseout", function () {
const group = d3.select(this);
const text = group.select("text");
text.style("fill", "none");
})
.selectAll("circle")
.data((d) => [d])
.join("circle")
.attr("r", 3)
.attr("fill", "white");
// Text for story nodes
node
.selectAll("text")
.data((d) => [d])
.join("text")
.style("fill", "none")
.text(function (d) {
return d.slug;
});
const categories = uniqueCategories.map((category) => {
return { name: category };
});
const simulation = d3
.forceSimulation()
.nodes(categories)
.on("tick", simulationTick)
.on("end", simulationEnd);
simulation
.force(
"collide",
d3
.forceCollide()
.radius((d) => 10)
.strength(1)
)
.force("manyBody", d3.forceManyBody().strength(3));
const link = svg
.append("g")
.selectAll(".link")
.data(links)
.join("line")
.attr("class", "link")
.style("stroke", "white")
.style("opacity", 0.1);
const category = svg
.append("g")
.selectAll(".category")
.data(categories)
.join("g")
.attr("class", "category")
.attr("transform", (d) => `translate(${width * 0.5}, ${height * 0.5})`);
const circle = category
.selectAll("circle")
.data((d) => [d])
.join("circle")
.attr("r", 7)
.attr("fill", "orange");
function getOffset(width, height) {
return { x: width * 0.5, y: height * 0.5 };
}
function simulationTick() {
circle.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
update();
}
function simulationEnd() {
console.log("Simulation complete...");
}
// Update positions on map move or zoom
const update = () => {
node.attr("transform", (d) => {
return `translate(${
map.latLngToLayerPoint([d.latitude, d.longitude]).x
},${map.latLngToLayerPoint([d.latitude, d.longitude]).y})`;
});
// TODO: Don't repeat yourself DRY
link
.attr("x1", (d) => {
const foundNode = nodes.find((node) => node.slug === d.source);
if (!foundNode) return 0;
const { x, y } = map.latLngToLayerPoint([
foundNode.latitude,
foundNode.longitude
]);
return x;
})
.attr("y1", (d) => {
const foundNode = nodes.find((node) => node.slug === d.source);
if (!foundNode) return 0;
const { x, y } = map.latLngToLayerPoint([
foundNode.latitude,
foundNode.longitude
]);
return y;
})
.attr("x2", (d) => {
const foundNode = categories.find(
(category) => category.name === d.target
);
if (!foundNode) return 0;
const { x, y } = foundNode;
return x + getOffset(width, height).x;
})
.attr("y2", (d) => {
const foundNode = categories.find(
(category) => category.name === d.target
);
if (!foundNode) return 0;
const { x, y } = foundNode;
return y + getOffset(width, height).y;
});
};
map.on("moveend", update);
}