xxxxxxxxxx
<html>
<head>
<style>
h1 {
font-family: "Noto Sans";
}
#tooltip {
display: none;
height: 125px;
width: 192px;
border-radius: 5px;
background-color: rgb(250, 250, 250);
box-shadow: 1px 2px 4px 0 rgba(0,0,0,0.4);
font-family: Roboto;
font-size: 12px;
}
.flex {
display: flex;
justify-content: space-between;
}
.flex .left {
padding-top: 5px;
margin-left: 25px;
}
.flex .right {
padding-top: 5px;
margin-right: 25px;
}
</style>
<link rel="stylesheet" href="skeleton.css">
<link href="https://fonts.googleapis.com/css?family=Noto+Sans:700|Raleway" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
</head>
<body>
<div class="container" style="max-width: 1000px;"></div>
<h1>net migration in the E.U.</h1>
<p>Population Migration by Country in 2015</p>
<div class="chart"></div>
<div id="tooltip">
<h3></h3>
<div id="percent-diff"></div>
<div id="outflow" class="flex">
<div class="left" id="outflow"></div>
<div class="right" id="outflow"></div>
</div>
<div id="inflow" class="flex">
<div class="left"></div>
<div class="right"></div>
</div>
</div>
<div class="row">
<p class="six columns">Source: UN Data</p>
<button class="six columns scale">Change to Log Scale</button>
</div>
</div>
</body>
<script>
class Chart {
constructor(opts) {
// load in arguments from config object
this.element = opts.element;
this.data = opts.originalData;
this.tData = opts.transformedData;
// create the chart
this.draw();
}
draw() {
// Set the margin or outer spacing
this.margin = {top: 0, right: 10, bottom: 50, left: 10},
// Set the padding or innter spacing
this.padding = {top: 10, right: 50, bottom: 100, left: 50},
// Select the screen width and height
this.outerWidth = document.querySelector(".chart").clientWidth,
this.outerHeight = 600,
// Subtract the margins to create space for the svg container
this.innerWidth = this.outerWidth - (this.margin.left + this.margin.right),
this.innerHeight = this.outerHeight - (this.margin.top + this.margin.bottom),
// Subtract the padding to create space for the chart
this.width = this.innerWidth - (this.padding.right + this.padding.left),
this.height = this.innerHeight - (this.padding.top + this.padding.bottom);
console.log(this.height);
this.element.innerHTML = '';
const svg = d3.select(this.element).append('svg');
svg
.attr("width", this.innerWidth)
.attr("height", this.innerHeight);
this.plot = svg.append('g')
.attr("transform", `translate(${this.margin.left},${this.margin.top})`);
// create the other stuff
this.createScales();
this.addAxes();
this.addBarbells();
this.addLabels();
}
createScales() {
this.yScale = d3.scaleLinear()
.domain(d3.extent(this.data, d => d.Migrants))
.range([this.height, this.margin.top + this.padding.top]);
this.cScale = d3.scaleLinear()
.domain([d3.min(this.tData, d => d.percentDiff), 0, d3.max(this.tData, d => d.percentDiff)])
.range(["#9C454C", "#E5E7E6", "#4577A3"]);
}
addAxes() {
this.yAxis = d3.axisRight()
.scale(this.yScale)
.tickFormat(d3.format(".2s"))
.tickSize( (this.margin.left + this.padding.left) - this.width);
this.yAxisG = this.plot
.append("g")
.attr("class", "y-axis")
.attr("transform", `translate(${this.width}, 0 )`)
.call(this.yAxis);
this.yAxisG
.selectAll("text")
.style("font-family", "Roboto")
.style("font-weight", "400")
.style("font-size", "12px")
.style("color", "#CECECE")
this.yAxisG
.selectAll("path")
.style("display", "none")
this.yAxisG
.selectAll("line")
.style("stroke", "#F1F1F1")
}
addBarbells() {
let tooltip = d3.select("#tooltip");
this.barbellG = this.plot.selectAll("g")
.data(this.tData, d => d)
.enter()
.append("g")
.attr("class", "barbell")
.style("opacity", .5)
.on("mouseover.highlight", function(d) {
d3.select(this)
.style("opacity", 1);
})
.on("mouseout.highlight", function(d) {
d3.select(this)
.style("opacity", .5);
})
.on("mouseover.tooltip", function() {
tooltip.style("display", null);
})
.on("mousemove.tooltip", function(d) {
tooltip.transition().duration(200)
.style("display", "block")
.style("opacity", 0.9);
const percentFormat = d3.format(",.1%");
const commaFormat = d3.format(",");
// position the tooltip
tooltip
.style("position", "fixed")
.style("left", d3.event.clientX + 20)
.style("top", d3.event.clientY + 20);
// Add tooltip text
tooltip.select("h3")
.html(d.country)
.style("font-size", "20px")
.style("font-family", "Noto Sans")
.style("text-align", "center")
.style("margin-top", 10);
tooltip.select("#percent-diff")
.text(percentFormat(d.percentDiff))
.style("color", "#5A6872")
.style("font-family", "Roboto")
.style("font-size", "12px")
.style("text-align", "center")
.style("margin-top", -10);
tooltip.select("#outflow").select(".left")
.text("Outflow")
tooltip.select("#outflow").select(".right")
.text(commaFormat(d.outflow))
tooltip.select("#inflow").select(".left")
.text("Inflow")
tooltip.select("#inflow").select(".right")
.text(commaFormat(d.inflow))
})
.on("mouseout.tooltip", function() {
tooltip.style("display", "none");
});
this.lines = this.barbellG
.append("line")
.attr("class", "line")
.attr("x1", this.width * .25)
.attr("x2", this.width * .75)
.attr("y1", d => this.yScale(d.outflow))
.attr("y2", d => this.yScale(d.inflow))
.attr("stroke", d => this.cScale(d.percentDiff))
.style("stroke-width", 3);
this.circlesOut = this.barbellG
.append("circle")
.attr("r", 4)
.attr("cx", this.width * .25)
.attr("cy", d => this.yScale(d.outflow))
.style("fill", d => this.cScale(d.percentDiff));
this.circlesIn = this.barbellG
.append("circle")
.attr("r", 4)
.attr("cx", this.width * .75)
.attr("cy", d => this.yScale(d.inflow))
.style("fill", d => this.cScale(d.percentDiff));
}
addLabels() {
const outflowLabel = this.plot
.append("text")
.attr("x", this.width * .25)
.attr("y", this.height)
.attr("dy", 30)
.attr("text-anchor", "middle")
.text("OUTFLOW")
.style("font-family", "Roboto")
.style("font-size", "12px")
.style("font-weight", "400")
.style("fill", "#a2a2a2");
const inflowLabel = this.plot
.append("text")
.attr("x", this.width * .75)
.attr("y", this.height)
.attr("dy", 30)
.attr("text-anchor", "middle")
.text("INFLOW")
.style("font-family", "Roboto")
.style("font-size", "12px")
.style("font-weight", "400")
.style("fill", "#a2a2a2");
}
updateScales() {
let scale = "";
if (document.querySelector("button.scale").textContent === "Change to Log Scale") {
scale = d3.scaleLog();
document.querySelector("button.scale").textContent = "Change to Linear Scale";
} else {
scale = d3.scaleLinear();
document.querySelector("button.scale").textContent = "Change to Log Scale";
}
const t = d3.transition()
.duration(1000);
this.yScale = scale
.domain(d3.extent(this.data, d => d.Migrants))
.range([this.height, this.margin.top + this.padding.top]);
/*
UPDATE AXIS
*/
this.yAxis = d3.axisRight()
.scale(this.yScale)
.tickFormat(d3.format(".2s"))
.tickSize( (this.margin.left + this.padding.left) - this.width);
this.yAxisG
.transition(t)
.call(this.yAxis);
this.yAxisG
.selectAll("text")
.style("font-family", "Roboto")
.style("font-weight", "400")
.style("font-size", "12px")
.style("color", "#CECECE")
this.yAxisG
.selectAll("path")
.style("display", "none")
this.yAxisG
.selectAll("line")
.style("stroke", "#F1F1F1")
/*
UPDATE ALL MARKS
*/
this.lines
.transition(t)
.attr("y1", d => this.yScale(d.outflow))
.attr("y2", d => this.yScale(d.inflow));
this.circlesOut
.transition(t)
.attr("cy", d => this.yScale(d.outflow));
this.circlesIn
.transition(t)
.attr("cy", d => this.yScale(d.inflow));
}
}
d3.csv('data.csv').then(function(data) {
// Convert migrant from a string to an integer
data.forEach(d => {
d.Migrants = +d.Migrants;
})
// Nest data to remove redundant country rows
const nested = d3.nest()
.key(d => d.Country)
.entries(data);
// Initialize a new data arraw to use in our visualization
let tData = [];
nested.forEach(function(d) {
tData.push({
country: d.key,
outflow: d.values[1].Migrants,
inflow: d.values[0].Migrants,
percentDiff: (d.values[0].Migrants - d.values[1].Migrants) / d.values[0].Migrants
})
})
const chart = new Chart({
element: document.querySelector('.chart'),
originalData: data,
transformedData: tData
});
d3.selectAll('button').on('click', () => {
chart.updateScales();
});
d3.select(window).on('resize', () => chart.draw() );
});
</script>
</html>
https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js