D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
robcrock
Full window
Github gist
net migration - slope chart
<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