Published
Edited
Dec 3, 2020
2 forks
1 star
Insert cell
Insert cell
loop()
// placeCities()
Insert cell
chart = {
var svg = d3.create("svg")
.attr("viewBox", [0, 0, canvasWidth, canvasHeight])
let container = svg.append("g")
.attr("transform",`translate(${margin.left},${margin.top})`)
/////////////////////////////////////////////////////////////////////////
//////////////////////////// Set-up Map /////////////////////////////////
/////////////////////////////////////////////////////////////////////////

let path = d3.geoPath()
.projection(projection)
let map = container.append("g")
.attr("class","map");
//Initiate the world map
map.selectAll(".geo-path")
.data(countriesMap[0].features)
.enter().append("path")
.attr("class", function(d) {return "geo-path" + " " + d.id;})
.attr("id", function(d) {return d.properties.name;})
.attr("d", path)
.style("stroke-opacity", 1)
.style("fill-opacity", 0.5);
///////////////////////////////////////////////////////////////////////////
//////////////////////////////// Create filter ////////////////////////////
///////////////////////////////////////////////////////////////////////////
let defs = svg.append("defs");
var filter = defs.append("filter").attr("id", "gooeyCodeFilter");
filter.append("feGaussianBlur")
.attr("in", "SourceGraphic")
.attr("stdDeviation","10")
.attr("color-interpolation-filters","sRGB")
.attr("result","blur");
filter.append("feColorMatrix")
.attr("class", "blurValues")
.attr("in","blur")
.attr("mode","matrix")
.attr("values","1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -5")
.attr("result","gooey");
filter.append("feBlend")
.attr("in", "SourceGraphic")
.attr("in2","gooey")
.attr("operator","atop");
///////////////////////////////////////////////////////////////////////////
//////////////////////////////// Cities ///////////////////////////////////
///////////////////////////////////////////////////////////////////////////

//Put the city locations into the data itself
populations.forEach(function(d,i) {
d.radius = rScale(d.population);
d.x = projection([d.longitude, d.latitude])[0];
d.y = projection([d.longitude, d.latitude])[1];
});
//Wrapper for the cities
let cityWrapper = container.append("g")
.attr("class", "cityWrapper")
.style("filter", "url(#gooeyCodeFilter)");
let cities = cityWrapper.selectAll(".cities")
.data(populations)
.enter().append("circle")
.attr("class", "cities")
.attr("r", function(d) {return d.radius;})
//Place circles at null island
.attr("cx", projection([0,0])[0])
.attr("cy", projection([0,0])[1])
.style("opacity", 1)
//Circle over all others
cityWrapper.append("circle")
.attr("class", "cityCover")
.attr("r", coverCircleRadius)
.attr("cx", projection([0,0])[0])
.attr("cy", projection([0,0])[1])

///////////////////////////////////////////////////////////////////////////
/////////////////////////// Country Labels ////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// //Calculate the centers for each country in radial layout
// let centers = getCenters("country",[canvasWidth,canvasHeight]);
// centers.forEach(function(d) {
// d.y = d.y - 100;
// d.x = d.x + 0;
// });//centers forEach
//Wraper for the country lavels
let labelWrapper = container.append("g")
.attr("class", "labelWrapper");
//Append the country labels
labelWrapper.selectAll(".label")
.data(centers)
.enter().append("text")
.attr("class","label")
.style("opacity", 0)
.attr("transform", d => `translate(${d.x},${d.y-60})`)
.text(d => d.name)
///////////////////////////////////////////////////////////////////////////
/////////////////////////// Set-up the force //////////////////////////////
///////////////////////////////////////////////////////////////////////////
// let force = d3.forceSimulation(d3.selectAll(".cities"))
// .on("tick", tick(centers,"country"));
// force.stop();

return svg.node()

}
Insert cell
force = d3.forceSimulation()
.on("tick", tick(centers,"country"));
Insert cell
centers = {
let data = getCenters("country",[canvasWidth,canvasHeight]);
data.forEach(function(d) {
d.y = d.y - 100;
d.x = d.x + 0;
});
return data
}
Insert cell
// tick(centers,"country")()
Insert cell
// //Radial lay-out
tick = function(centers, varname) {
let foci = {};
for (var i=0; i <centers.length; i++) {
//Country names as keys
foci[centers[i].name] = centers[i];
}//for

//Move circles from geographic location to radial layout
return function() {
for (var i=0; i < populations.length; i++) {
let o = populations[i]; //data for city
let f = foci[o[varname]]; //centers data for city's country
//Move city's coords from actual location to the circle pack layout location
o.y += (f.y - o.y) * 0.1;
o.x += (f.x - o.x) * 0.1;
}//for
d3.selectAll(".cities")
.each(collide(0.5))
.attr("cx", function(d) {return d.x;})
.attr("cy", function(d) {return d.y;})
}//function
}//tick
Insert cell
loop = function() {
placeCities();
setTimeout(clusterCountry, 7000);
setTimeout(backToCenter, 12000);
}
Insert cell
//Move the cities from the center to their actual locations
// placeCities = function(force,projection) {
placeCities = function() {
//Stop the force layout (in case you move backward)
force.stop();
//Make the cover circle shrink
d3.selectAll(".cityCover")
.transition().duration(5000)
.attr("r", 0);
//Put the cities in their geo location
d3.selectAll(".cities")
.transition("move").duration(2000)
.delay(function(d,i) {return i*20;})
.attr("r", function(d) {return d.radius = rScale(d.population);}) //Is this necessary?
.attr("cx", function(d) {return d.x = projection([d.longitude, d.latitude])[0];})
.attr("cy", function(d) {return d.y = projection([d.longitude, d.latitude])[1];});
//Around the end of the transition above, make circles see-through a bit
d3.selectAll(".cities")
.transition("dim").duration(2000).delay(4000)
.style("opacity", 0.8);
//"Remove" gooey filter from cities during the transition
//..so at the end they do nto appear to melt together anymore
d3.selectAll(".blurValues")
.transition().duration(4000)
.attrTween("values", function() {
return d3.interpolateString("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -5",
"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 6 -5");
});
}
Insert cell
//Cluster all cities based on country
clusterCountry = function() {
//Start the force again
force.restart();
//Dim the map
d3.selectAll(".geo-path")
.transition().duration(1000)
.style("fill-opacity", 0);
//Show the labels
d3.selectAll(".label")
.transition().duration(500)
.style("opacity", 1);
d3.selectAll(".cities")
.transition().duration(1000)
.style("opactiy", 1);
//Reset the gooey filter values back to a visibile "gooey" effect
d3.selectAll(".blurValues")
.transition().duration(2000)
.attrTween("values", function() {
return d3.interpolateString("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 6 -5",
"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -5");
});
}
Insert cell
backToCenter = function() {
//Stop the force layout
force.stop();
//Hide labels
d3.selectAll(".label")
.transition().duration(500)
.style("opacity", 0);
//Show map
d3.selectAll(".geo-path")
.transition().duration(1000)
.style("fill-opacity", 0.5);
//Make the cover circles its true size again
d3.selectAll(".cityCover")
.transition().duration(3000).delay(500)
.attr("r", coverCircleRadius);
//Move the cities to the 0,0 coord
d3.selectAll(".cities")
.transition()
.duration(2000).delay((d,i) => i*10)
.attr("cx", projection([0,0])[0])
.attr("cy", projection([0,0])[1])
.style("opacity", 1);
d3.selectAll(".blurValues")
.transition().duration(1000).delay(1000)
.attrTween("values", function() {
return d3.interpolateString("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -6",
"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -5");
});
}
Insert cell
Insert cell
coverCircleRadius = 40;
Insert cell
projection = d3.geoMercator()
.scale(170)
.translate([480,230]);
Insert cell
canvasHeight = 600 - margin.top - margin.bottom
Insert cell
canvasWidth = width - margin.left - margin.right
Insert cell
margin = ({top: 100, right: 0, bottom: 0,left: 0})
Insert cell
maxRadius = d3.max(populations, d => d.radius)
Insert cell
padding = 0;
Insert cell
//Radius scale
rScale = d3.scaleSqrt()
.domain([0, d3.max(populations, function(d) {return d.population;})])
.range([0,14]);
Insert cell
Insert cell
//Radial layout
getCenters = function(vname, size) {
var centers = [],
mapping,
flags = [];
//Return unique list of countries
for (var i=0; i < populations.length; i++) {
if (flags[populations[i][vname]]) continue; //Skip if country has already been added
flags[populations[i][vname]] = true; //Flag that country has been added
centers.push({name: populations[i][vname], value: 1}); //Push new country to data
}
//Sort by name
centers.sort(function(a,b){return d3.ascending(a.name, b.name);});
// let pack = d3.pack()
// .size(size)
// (d3.hierarchy({children:centers})
// .sum(d => d.value)
// .sort(d => d[vname]))
// centers = pack.leaves()
mapping = d32.layout.pack()
.sort(d => d[vname])
.size(size);
mapping.nodes({children: centers});
return centers;
}
Insert cell
collide = function(alpha) {
var quadtree = d3.quadtree(populations);
return function (d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + padding;
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
Insert cell
Insert cell
Insert cell
countriesMap = FileAttachment("countriesMap2@1.js").json()
Insert cell
Insert cell
d3 = require('d3@5')
Insert cell
d32 = require('d3@3')
Insert cell
d32.geom.quadtree(populations)
Insert cell
d3.quadtree(populations, x => x, y => y)
Insert cell
quadtree = d3.quadtree(populations)
Insert cell
quadtree.visit(function(quad,x1,y1,x2,y2){
console.log(quad)
})
Insert cell
html`
<style>


.title {
font-size: 30px;
color: #4F4F4F;
}

.subtitle {
font-size: 14px;
color: #AAAAAA;
padding-bottom: 15px;
}

.geo-path {
stroke: white;
stroke-width: 0.5px;
stroke-opacity: 1;
fill: #C0AA91;
fill-opacity: 1;
}

.cities, .cityCover{
fill: #1A818C;
}

.label {
font-size: 18px;
text-anchor: middle;
fill: #707070;
font-family: 'Poiret One', cursive;
font-weight: 400;
}


</style>
`
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more