Skip to content

Instantly share code, notes, and snippets.

@robcrock
Last active November 14, 2018 21:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robcrock/7fd6b9b32e99cb74a6ee11f9c55729ee to your computer and use it in GitHub Desktop.
Save robcrock/7fd6b9b32e99cb74a6ee11f9c55729ee to your computer and use it in GitHub Desktop.
net migration - slope chart
Country Kpi Migrants
Austria inflow 1492374
Austria outflow 575828
Belgium inflow 1387940
Belgium outflow 530990
Bulgaria inflow 102113
Bulgaria outflow 1176390
Croatia inflow 576883
Croatia outflow 865147
Czech Republic inflow 405093
Czech Republic outflow 932582
Denmark inflow 572520
Denmark outflow 250675
Estonia inflow 202348
Estonia outflow 198042
Finland inflow 315881
Finland outflow 295077
France inflow 7784418
France outflow 2145975
Germany inflow 12005690
Germany outflow 4045411
Greece inflow 1242514
Greece outflow 871643
Hungary inflow 449632
Hungary outflow 595937
Ireland inflow 746260
Ireland outflow 882022
Italy inflow 5788875
Italy outflow 2900924
Latvia inflow 263126
Latvia outflow 337678
Lithuania inflow 136036
Lithuania outflow 544120
Luxembourg inflow 249325
Luxembourg outflow 61058
Malta inflow 41442
Malta outflow 103392
Netherlands inflow 1979486
Netherlands outflow 981434
Poland inflow 619403
Poland outflow 4449789
Portugal inflow 837257
Portugal outflow 2306321
Romania inflow 226943
Romania outflow 3408118
Slovakia inflow 177190
Slovakia outflow 341620
Slovenia inflow 235966
Slovenia outflow 140490
Spain inflow 5852953
Spain outflow 1251004
Sweden inflow 1639771
Sweden outflow 333428
Ukraine inflow 4834898
Ukraine outflow 5825745
United Kingdom inflow 8543120
United Kingdom outflow 4917460
<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>
/*
* Skeleton V2.0.4
* Copyright 2014, Dave Gamache
* www.getskeleton.com
* Free to use under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
* 12/29/2014
*/
/* Table of contents
––––––––––––––––––––––––––––––––––––––––––––––––––
- Grid
- Base Styles
- Typography
- Links
- Buttons
- Forms
- Lists
- Code
- Tables
- Spacing
- Utilities
- Clearing
- Media Queries
*/
/* Grid
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.container {
position: relative;
width: 100%;
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box; }
.column,
.columns {
width: 100%;
float: left;
box-sizing: border-box; }
/* For devices larger than 400px */
@media (min-width: 400px) {
.container {
width: 85%;
padding: 0; }
}
/* For devices larger than 550px */
@media (min-width: 550px) {
.container {
width: 80%; }
.column,
.columns {
margin-left: 4%; }
.column:first-child,
.columns:first-child {
margin-left: 0; }
.one.column,
.one.columns { width: 4.66666666667%; }
.two.columns { width: 13.3333333333%; }
.three.columns { width: 22%; }
.four.columns { width: 30.6666666667%; }
.five.columns { width: 39.3333333333%; }
.six.columns { width: 48%; }
.seven.columns { width: 56.6666666667%; }
.eight.columns { width: 65.3333333333%; }
.nine.columns { width: 74.0%; }
.ten.columns { width: 82.6666666667%; }
.eleven.columns { width: 91.3333333333%; }
.twelve.columns { width: 100%; margin-left: 0; }
.one-third.column { width: 30.6666666667%; }
.two-thirds.column { width: 65.3333333333%; }
.one-half.column { width: 48%; }
/* Offsets */
.offset-by-one.column,
.offset-by-one.columns { margin-left: 8.66666666667%; }
.offset-by-two.column,
.offset-by-two.columns { margin-left: 17.3333333333%; }
.offset-by-three.column,
.offset-by-three.columns { margin-left: 26%; }
.offset-by-four.column,
.offset-by-four.columns { margin-left: 34.6666666667%; }
.offset-by-five.column,
.offset-by-five.columns { margin-left: 43.3333333333%; }
.offset-by-six.column,
.offset-by-six.columns { margin-left: 52%; }
.offset-by-seven.column,
.offset-by-seven.columns { margin-left: 60.6666666667%; }
.offset-by-eight.column,
.offset-by-eight.columns { margin-left: 69.3333333333%; }
.offset-by-nine.column,
.offset-by-nine.columns { margin-left: 78.0%; }
.offset-by-ten.column,
.offset-by-ten.columns { margin-left: 86.6666666667%; }
.offset-by-eleven.column,
.offset-by-eleven.columns { margin-left: 95.3333333333%; }
.offset-by-one-third.column,
.offset-by-one-third.columns { margin-left: 34.6666666667%; }
.offset-by-two-thirds.column,
.offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
.offset-by-one-half.column,
.offset-by-one-half.columns { margin-left: 52%; }
}
/* Base Styles
–––––––––––––––––––––––––––––––––––––––––––––––––– */
/* NOTE
html is set to 62.5% so that all the REM measurements throughout Skeleton
are based on 10px sizing. So basically 1.5rem = 15px :) */
html {
font-size: 62.5%; }
body {
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
line-height: 1.6;
font-weight: 400;
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #222; }
/* Typography
–––––––––––––––––––––––––––––––––––––––––––––––––– */
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 2rem;
font-weight: 300; }
h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
/* Larger than phablet */
@media (min-width: 550px) {
h1 { font-size: 5.0rem; }
h2 { font-size: 4.2rem; }
h3 { font-size: 3.6rem; }
h4 { font-size: 3.0rem; }
h5 { font-size: 2.4rem; }
h6 { font-size: 1.5rem; }
}
p {
margin-top: 0; }
/* Links
–––––––––––––––––––––––––––––––––––––––––––––––––– */
a {
color: #1EAEDB; }
a:hover {
color: #0FA0CE; }
/* Buttons
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.button,
button,
input[type="submit"],
input[type="reset"],
input[type="button"] {
display: inline-block;
height: 38px;
padding: 0 30px;
color: #555;
text-align: center;
font-size: 11px;
font-weight: 600;
line-height: 38px;
letter-spacing: .1rem;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 4px;
border: 1px solid #bbb;
cursor: pointer;
box-sizing: border-box; }
.button:hover,
button:hover,
input[type="submit"]:hover,
input[type="reset"]:hover,
input[type="button"]:hover,
.button:focus,
button:focus,
input[type="submit"]:focus,
input[type="reset"]:focus,
input[type="button"]:focus {
color: #333;
border-color: #888;
outline: 0; }
.button.button-primary,
button.button-primary,
input[type="submit"].button-primary,
input[type="reset"].button-primary,
input[type="button"].button-primary {
color: #FFF;
background-color: #33C3F0;
border-color: #33C3F0; }
.button.button-primary:hover,
button.button-primary:hover,
input[type="submit"].button-primary:hover,
input[type="reset"].button-primary:hover,
input[type="button"].button-primary:hover,
.button.button-primary:focus,
button.button-primary:focus,
input[type="submit"].button-primary:focus,
input[type="reset"].button-primary:focus,
input[type="button"].button-primary:focus {
color: #FFF;
background-color: #1EAEDB;
border-color: #1EAEDB; }
/* Forms
–––––––––––––––––––––––––––––––––––––––––––––––––– */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea,
select {
height: 38px;
padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
background-color: #fff;
border: 1px solid #D1D1D1;
border-radius: 4px;
box-shadow: none;
box-sizing: border-box; }
/* Removes awkward default styles on some inputs for iOS */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none; }
textarea {
min-height: 65px;
padding-top: 6px;
padding-bottom: 6px; }
input[type="email"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="text"]:focus,
input[type="tel"]:focus,
input[type="url"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus {
border: 1px solid #33C3F0;
outline: 0; }
label,
legend {
display: block;
margin-bottom: .5rem;
font-weight: 600; }
fieldset {
padding: 0;
border-width: 0; }
input[type="checkbox"],
input[type="radio"] {
display: inline; }
label > .label-body {
display: inline-block;
margin-left: .5rem;
font-weight: normal; }
/* Lists
–––––––––––––––––––––––––––––––––––––––––––––––––– */
ul {
list-style: circle inside; }
ol {
list-style: decimal inside; }
ol, ul {
padding-left: 0;
margin-top: 0; }
ul ul,
ul ol,
ol ol,
ol ul {
margin: 1.5rem 0 1.5rem 3rem;
font-size: 90%; }
li {
margin-bottom: 1rem; }
/* Code
–––––––––––––––––––––––––––––––––––––––––––––––––– */
code {
padding: .2rem .5rem;
margin: 0 .2rem;
font-size: 90%;
white-space: nowrap;
background: #F1F1F1;
border: 1px solid #E1E1E1;
border-radius: 4px; }
pre > code {
display: block;
padding: 1rem 1.5rem;
white-space: pre; }
/* Tables
–––––––––––––––––––––––––––––––––––––––––––––––––– */
th,
td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #E1E1E1; }
th:first-child,
td:first-child {
padding-left: 0; }
th:last-child,
td:last-child {
padding-right: 0; }
/* Spacing
–––––––––––––––––––––––––––––––––––––––––––––––––– */
button,
.button {
margin-bottom: 1rem; }
input,
textarea,
select,
fieldset {
margin-bottom: 1.5rem; }
pre,
blockquote,
dl,
figure,
table,
p,
ul,
ol,
form {
margin-bottom: 2.5rem; }
/* Utilities
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.u-full-width {
width: 100%;
box-sizing: border-box; }
.u-max-full-width {
max-width: 100%;
box-sizing: border-box; }
.u-pull-right {
float: right; }
.u-pull-left {
float: left; }
/* Misc
–––––––––––––––––––––––––––––––––––––––––––––––––– */
hr {
margin-top: 3rem;
margin-bottom: 3.5rem;
border-width: 0;
border-top: 1px solid #E1E1E1; }
/* Clearing
–––––––––––––––––––––––––––––––––––––––––––––––––– */
/* Self Clearing Goodness */
.container:after,
.row:after,
.u-cf {
content: "";
display: table;
clear: both; }
/* Media Queries
–––––––––––––––––––––––––––––––––––––––––––––––––– */
/*
Note: The best way to structure the use of media queries is to create the queries
near the relevant code. For example, if you wanted to change the styles for buttons
on small devices, paste the mobile query code up in the buttons section and style it
there.
*/
/* Larger than mobile */
@media (min-width: 400px) {}
/* Larger than phablet (also point when grid becomes active) */
@media (min-width: 550px) {}
/* Larger than tablet */
@media (min-width: 750px) {}
/* Larger than desktop */
@media (min-width: 1000px) {}
/* Larger than Desktop HD */
@media (min-width: 1200px) {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment