The data is from World Happiness Report 2018. This is an overview of the data. With the changes of X and Y axes, one can have a view on the correlation between indices.
Built with blockbuilder.org
forked from Curran's blog
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/d3@4.10.0/build/d3.js"></script>
<script src="https://unpkg.com/d3-component@3.0"></script>
<script src="https://unpkg.com/redux@3/dist/redux.min.js"></script>
<script src="https://unpkg.com/d3-tip@0.7.1"></script>
<link rel="stylesheet" href="https://unpkg.com/bootstrap@4.0.0-alpha.6/dist/css/bootstrap.min.css">
<style>
.point {
fill: currentColor;
stroke: currentColor;
fill-opacity: 0.3;
}
/* Tooltip styles copied from
https://bl.ocks.org/Caged/6476579 */
.d3-tip {
line-height: 1;
padding: 8px;
background: rgba(0, 0, 0, 0.6);
color: #fff;
font-size: 12px;
border-radius: 2px;
}
h4 {
font-size: 18px;
}
</style>
</head>
<body>
<script>
// This stateless component renders a static "wheel" made of circles,
// and rotates it depending on the value of props.angle.
var wheel = d3.component("g")
.create(function (selection){
var minRadius = 4,
maxRadius = 10,
numDots = 10,
wheelRadius = 40,
rotation = 0,
rotationIncrement = 3,
radius = d3.scaleLinear()
.domain([0, numDots - 1])
.range([maxRadius, minRadius]),
angle = d3.scaleLinear()
.domain([0, numDots])
.range([0, Math.PI * 2]);
selection
.selectAll("circle").data(d3.range(numDots))
.enter().append("circle")
.attr("cx", function (d){ return Math.sin(angle(d)) * wheelRadius; })
.attr("cy", function (d){ return Math.cos(angle(d)) * wheelRadius; })
.attr("r", radius);
})
.render(function (selection, d){
selection.attr("transform", "rotate(" + d + ")");
});
// This component with a local timer makes the wheel spin.
var spinner = (function (){
var timer = d3.local();
return d3.component("g")
.create(function (selection, d){
timer.set(selection.node(), d3.timer(function (elapsed){
selection.call(wheel, elapsed * d.speed);
}));
})
.render(function (selection, d){
selection.attr("transform", "translate(" + d.x + "," + d.y + ")");
})
.destroy(function(selection, d){
timer.get(selection.node()).stop();
return selection
.attr("fill-opacity", 1)
.transition().duration(3000)
.attr("transform", "translate(" + d.x + "," + d.y + ") scale(10)")
.attr("fill-opacity", 0);
});
}());
var axis = (function (){
var axisLocal = d3.local();
return d3.component("g")
.create(function (selection, d){
axisLocal.set(selection.node(), d3["axis" + d.type]());
selection
.attr("opacity", 0)
.call(axisLocal.get(selection.node())
.scale(d.scale)
.ticks(d.ticks || 10))
.transition("opacity").duration(2000)
.attr("opacity", 0.8);
})
.render(function (selection, d){
selection
.attr("transform", "translate(" + [
d.translateX || 0,
d.translateY || 0
] + ")")
.transition("ticks").duration(3000)
.call(axisLocal.get(selection.node()));
});
}());
// This component displays the visualization.
var scatterPlot = (function (){
var xScale = d3.scaleLinear(),
yScale = d3.scaleLinear(),
colorScale = d3.scaleLinear();
function render(selection, d){
var x = d.x,
y = d.y,
color = d.happycolor,
margin = d.margin,
innerWidth = d.width - margin.left - margin.right,
innerHeight = d.height - margin.top - margin.bottom;
var colorValue = d => d.happinessScore;
xScale
.domain(d3.extent(d.data, function (d){ return d[x]; }))
.range([0, innerWidth]);
yScale
.domain(d3.extent(d.data, function (d){ return d[y]; }))
.range([innerHeight, 0]);
colorScale
.domain(d3.extent(d.data, function(d){ return d[color]; }))
.interpolate(d3.interpolateHcl)
.range([d3.rgb("#007AFF"), d3.rgb('#FFF500')]);
//modify/translate selection
selection
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(axis, [
{
type: "Left",
scale: yScale,
translateX: -12
},
{
type: "Bottom",
scale: xScale,
translateY: innerHeight + 12,
ticks: 20
}
])
var circles = selection.selectAll(".point").data(d.data);
circles.exit().remove();
circles
.enter().append("circle")
.attr("class", "point")
.attr("r", 0)
.attr("cx", d.width / 2 - margin.left)
.attr("cy", d.height / 2 - margin.top)
.merge(circles)
.on("mouseover", d.show)
.on("mouseout", d.hide)
.transition()
.duration(2000)
.delay(function (d, i){ return i * 5; })
.attr("r", 10)
.attr("cx", function (d){ return xScale(d[x]); })
.attr("cy", function (d){ return yScale(d[y]); })
.attr("color", function (d){ return colorScale(d[color]); })
}
return d3.component("g")
.render(render);
}());
// Leverage the d3-tip library for tooltips.
var tooltip = (function (){
var tip = d3.tip()
.attr("class", "d3-tip")
.offset([-10, 0]);
return function (svgSelection, state){
// Wish we could use D3 here for DOM manipulation..
tip.html(function(d) {
return [
"<h4>" + d.country + " " + d.happinessScore + "</h4>",
"<div><strong>" + state.x + ": </strong>",
"<span>" + d[state.x] + "</span></div>",
"<div><strong>" + state.y + ": </strong>",
"<span>" + d[state.y] + "</span></div>"
].join("");
});
svgSelection.call(tip);
return {
show: tip.show,
hide: tip.hide
};
}
}());
// This component manages an svg element, and
// either displays a spinner or text,
// depending on the value of the `loading` state.
var svg = d3.component("svg")
.render(function (selection, d){
var svgSelection = selection
.attr("width", d.width)
.attr("height", d.height)
.call(spinner, !d.loading ? [] : {
x: d.width / 2,
y: d.height / 2,
speed: 0.2
});
var tipCallbacks = tooltip(svgSelection, d);
svgSelection
.call(scatterPlot, d.loading ? [] : d, tipCallbacks);
});
var label = d3.component("label", "col-sm-2 col-form-label")
.render(function (selection, d){
selection.text(d);
});
var option = d3.component("option")
.render(function (selection, d){
selection.text(d);
});
var select = d3.component("select", "form-control")
.render(function (selection, d){
selection
.call(option, d.columns)
.property("value", d.value)
.on("change", function (){
d.action(this.value);
})
});
var rowComponent = d3.component("div", "row");
var colSm10 = d3.component("div", "col-sm-10");
var menu = d3.component("div", "col-sm-4")
.render(function (selection, d){
var row = rowComponent(selection).call(label, d.label);
colSm10(row).call(select, d);
});
var menus = d3.component("div", "container-fluid")
.create(function (selection){
selection.style("opacity", 0);
})
.render(function (selection, d){
rowComponent(selection).call(menu, [
{
label: "X",
value: d.x,
action: d.setX,
columns: d.numericColumns
},
{
label: "Y",
value: d.y,
action: d.setY,
columns: d.numericColumns
}
], d);
if(!d.loading && selection.style("opacity") === "0"){
selection.transition().duration(2000)
.style("opacity", 1);
}
});
var app = d3.component("div")
.render(function (selection, d){
selection.call(menus, d).call(svg, d);
});
function loadData(actions){
var numericColumns = [
"rank",
"happinessScore",
"dystopiaResidual",
"GDP",
"socialSupport",
"lifeExpectancy",
"freedom",
"generosity",
"corruption"
];
setTimeout(function (){ // Show off the spinner for a few seconds ;)
d3.csv("WHR18fig22.csv", type, function (data){
actions.ingestData(data, numericColumns)
});
}, 2000);
function type(d){
return numericColumns.reduce(function (d, column){
d[column] = + d[column];
return d;
}, d);
}
}
function reducer (state, action){
var state = state || {
width: 960,
height: 500 - 38,
loading: true,
margin: {top: 12, right: 12, bottom: 40, left: 50},
happycolor: "happinessScore",
x: "rank",
y: "happinessScore",
color: "happinessScore"
};
switch (action.type) {
case "INGEST_DATA":
return Object.assign({}, state, {
loading: false,
data: action.data,
numericColumns: action.numericColumns
});
case "SET_X":
return Object.assign({}, state, { x: action.column });
case "SET_Y":
return Object.assign({}, state, { y: action.column });
case "SET_COLOR":
return Object.assign({}, state, { color: action.column });
default:
return state;
}
}
function actionsFromDispatch(dispatch){
return {
ingestData: function (data, numericColumns){
dispatch({
type: "INGEST_DATA",
data: data,
numericColumns: numericColumns
});
},
setX: function (column){
dispatch({ type: "SET_X", column: column });
},
setY: function (column){
dispatch({ type: "SET_Y", column: column });
},
setColor: function (column){
dispatch({ type: "SET_COLOR", column: column });
}
};
}
function main(){
var store = Redux.createStore(reducer),
actions = actionsFromDispatch(store.dispatch);
renderApp = function(){
d3.select("body").call(app, store.getState(), actions);
}
renderApp();
store.subscribe(renderApp);
loadData(actions);
}
main();
</script>
</body>
https://unpkg.com/d3@4.10.0/build/d3.js
https://unpkg.com/d3-component@3.0
https://unpkg.com/redux@3/dist/redux.min.js
https://unpkg.com/d3-tip@0.7.1