Notebooks 2.0 is here.

Published
Edited
Jun 3, 2021
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
/*
This block sets up all the attributes needed to run this simulation. All initial variables and constants are defined here along with any other one-time setups.
*/
init = {
const margin = { top: 100, right: 0, bottom: 0, left: 0 };
const svg = d3
.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);

initGooeyFilter(svg);
let g = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

//Wrapper for background elements
g
.append("g")
.attr("class", "backgroundWrapper")
//Wrapper for the teams
const teamWrapper = g
.append("g")
.attr("class", "teamWrapper")
.style("filter", "url(#gooeyCodeFilter)");
return svg.node();
};
Insert cell
visualizations = {
const preparedData = prepareData(populations)
let dataSel = d3.selectAll(".teamWrapper")
.selectAll("circle")
.data(preparedData, (d)=> d.index)
console.log('viz here');
let svg = d3.select('svg');
const mapViz = MapViz(svg, preparedData, dataSel);
mapViz.init();

const clusterViz = ClusterViz(svg, preparedData, dataSel);
clusterViz.init();
return {mapViz, clusterViz};
}
Insert cell
Insert cell
/*
Depending on what mode is selected by the user will trigger activation of a new visualization.
If a transition is currently in progress no new mode is available yet. Observable does some kind of magic behind the scenes to make this possible. Simply copying this out into its own file will not maintain this functionality.
*/
{
//visualizations.clusterViz.stop();
//visualizations.mapViz.stop();
switch(mode) {
case '' :
break;
case 'map' :
visualizations.mapViz.render();
break;
case 'cluster' :
visualizations.clusterViz.render();
break;
}
}
Insert cell
Insert cell
md` ### Map Visualization
This visualization splits the team into smaller circles and places them on the map`
Insert cell
Insert cell
/*
This visualization splits the team across various cities in the world based on Lat / Long values stored in the dataset.
*/

MapViz = (sel, data, dataSel) => {
const callback = new Object();
let projection

callback.init = () => {
projection = initProjection(sel.select('.backgroundWrapper'));
sel.selectAll(".geo-path")
.style("fill-opacity", 0);
};
const enter = (s) => {
s.append("circle")
.transition().duration(1000)
.style("fill", d => color(d.cluster))
//.attr("class", d => d.city)
.attr("r", (d, i) => d.radius)
.attr("cx", (d, i) => (d.x = projection([d.longitude, d.latitude])[0]))
.attr("cy", (d, i) => (d.y = projection([d.longitude, d.latitude])[1]))
}
const update = (s) => {
return s.call(s => s.transition().duration(1000)
.attr("r", (d, i) => d.radius)
.attr("cx", (d, i) => (d.x = projection([d.longitude, d.latitude])[0]))
.attr("cy", (d, i) => (d.y = projection([d.longitude, d.latitude])[1]))
);
}
const exit = (s) => {
s.remove();
}

callback.render = () => {
sel.selectAll(".geo-path")
.transition().duration(1000)
.style("fill-opacity", 0.5);
//Put the teams in their geo location
dataSel.join(enter, update, exit)

//"Remove" gooey filter from teams during the transition
//So at the end they do not appear to melt together anymore
sel
.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"
);
});
};
callback.stop = () => {
sel.selectAll(".geo-path")
.transition().duration(1000)
.style("fill-opacity", 0.0);
}

return callback;
};
Insert cell
md` ### Cluster Visualization`
Insert cell
ClusterViz = (sel, data, dataSel) => {
console.log('init cluster viz');
let callback = new Object();
let node, simulation;

callback.init = () => {
};

callback.render = () => {
const g = sel.selectAll('.teamWrapper');
sel
.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"
);
});
////////////////////////////////////////////////////////////
//////////////////////// Draw nodes ////////////////////////
////////////////////////////////////////////////////////////
const node = dataSel
.join("circle")
.attr("r", d => d.radius)
.style("fill", d => color(d.cluster))

////////////////////////////////////////////////////////////
////////////////////// Run simulation //////////////////////
////////////////////////////////////////////////////////////

simulation = d3.forceSimulation()
.force("forceInABox",
forceInABox()
.strength(0.1) // Strength to cluster center
.template("treemap") // Either treemap or force
.groupBy("country") // Node attribute to group
.forceNodeSize(d => d.radius + 3)
.size([width, height])
)
.force("collide", d3.forceCollide().radius(d => d.radius + 1) .strength(0.8));
simulation
.nodes(data)
.on("tick", ticked)
function ticked() {
node
.attr("cx", d => d.x)
.attr("cy", d => d.y)
}
};

callback.stop = () => {
if(typeof simulation !== 'undefined') {
simulation.stop();
}
};

return callback;
};
Insert cell
color = d3.scaleSequential(d3.interpolateSinebow).domain([0,10-1])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`** Map Visualization Dependencies **`
Insert cell
land = FileAttachment("land-50m.json").json().then(topology => topojson.feature(topology, topology.objects.land))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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