function createMap({ facts, invalidation, visibility }) {
const height = (Math.min(WIDTH, 954) * 0.55) | 0;
projection.fitExtent(
[
[margin, margin],
[WIDTH - margin, height - margin]
],
{
type: "Sphere"
}
);
const dim = dimCountries,
group = dim.group().reduceCount(),
values = group
.all()
.slice()
.sort((a, b) => d3.descending(a.value, b.value)),
id = dim.id();
invalidation &&
invalidation.then(() => {
dim.filterAll().dispose();
facts.dispatch.on("redraw.view" + id, null).on("reset.view" + id, null);
});
facts.dispatch.on("redraw.view" + id, redraw).on("reset.view" + id, reset);
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, WIDTH, height])
.style("overflow", "visible")
.attr("id", "map"),
path = d3.geoPath().projection(projection);
let _circles,
_ghost_circles,
_links,
_labels,
_legend,
_sphere,
_countries,
_activeCountries;
let filter = null;
const ghost_values = new Map(
crossfilter(facts.all())
.dimension(dim.accessor, MULTIVALUED)
.group()
.reduceCount() // not reducer
.all()
.map((d) => [d.key, d.value])
),
active = [...centroids.values()]
.filter((d) => (d.total = ghost_values.get(d.iso) || 0))
.sort((a, b) => d3.descending(a.total, b.total));
// CREATE
function render() {
svg
.html("")
.append("defs")
.append("style")
.attr("type", "text/css")
.text(styleSvg);
_sphere = svg
.append("g")
.attr("id", "sphere")
.append("path")
.datum({ type: "Sphere" });
_countries = svg
.append("g")
.attr("id", "land")
.append("path")
.datum(countries)
.attr("d", path);
_activeCountries = svg
.append("g")
.attr("id", "activeCountries")
.selectAll("path")
.data(
countries.features.filter((d) => allCountries.has(d.properties.adm0_a3))
)
.join("path");
_links = svg.append("g").attr("id", "links");
const onClick = function (event, d) {
filter = filter && filter.includes(d.iso) ? null : [d.iso];
dim.filter(filter && ((d) => filter.includes(d)));
facts.dispatch.call("redraw");
d3.select(this).classed("selected", !!filter);
svg.select("#tooltip").attr("transform", `translate(-1000,-1000)`);
},
onMouseover = function () {
d3.select(this).classed("hover", true);
},
onMouseout = function () {
d3.select(this).classed("hover", false);
};
_ghost_circles = svg
.append("g")
.attr("id", "ghostCircles")
.selectAll("path")
.data(active)
.join("path");
_circles = svg
.append("g")
.attr("id", "circles")
.selectAll("path")
.data(active)
.join("path");
_circles
.merge(_ghost_circles)
.on("click", onClick)
.on("mouseover", onMouseover)
.on("mouseout", onMouseout);
_labels = svg
.append("g")
.attr("id", "labels")
.attr("fill", "white")
.selectAll("text")
.data(active)
.join("text");
_legend = svg.append("g").attr("id", "legend");
svg.call(addTooltip, _circles, _links);
svg.call(
zoom(projection)
.filter(() => viewof zoomButton.value)
.on("zoom.render", () => {
project();
redraw(true);
})
.on("end.render", () => {
project();
redraw(true);
})
);
project();
redraw();
}
// UPDATE PROJECTION
function project() {
_sphere.attr("d", path);
_countries.attr("d", path);
_activeCountries.attr("d", path);
}
// UPDATE
function redraw(immediate) {
// We group "by hand"
const trialCountries = facts.allFiltered().map(dim.accessor),
values = d3.rollup(
trialCountries.flat(),
(v) => v.length,
(d) => d
);
r.range([2, baseRadius * Math.sqrt(projection.scale())]);
_circles
.transition()
.duration(immediate ? 0 : 500)
.attr("d", (d) =>
path.pointRadius(values.get(d.iso) ? r(values.get(d.iso)) : 0)(d)
);
const ghost_values = d3.rollup(
facts.all().map(dim.accessor).flat(),
(v) => v.length,
(d) => d
);
_ghost_circles
.transition()
.duration(immediate ? 0 : 500)
.attr("d", (d) => path.pointRadius(r(ghost_values.get(d.iso) || 0))(d));
// links
const links = getLinks(trialCountries);
_links
.selectAll("path")
.data(links)
.join("path")
.attr("stroke", fillLinks)
.attr("fill", "none")
.attr("stroke-width", (d) => d.value / 8)
.attr("d", (d) =>
path({
type: "LineString",
coordinates: [
centroids.get(d.source).centroid,
centroids.get(d.target).centroid
]
})
);
_labels
.text((d) => {
d.trials = values.get(d.iso) || 0;
return r(d.trials) > 14 ? `${d.iso}: ${d.trials}` : d.trials;
})
.attr("transform", (d) => `translate(${projection(d.centroid)})`)
.style("opacity", (d) => (r(d.trials) > 6 ? 1 : 0));
//
_legend.call(legend, { width: WIDTH, height });
}
function reset() {
dim.filter((filter = null));
redraw();
}
render();
return html`<details open><summary class=h4>Map</summary>${svg.node()}`;
}