Last active
December 18, 2018 16:15
-
-
Save yellowcap/8723f81abc6357875ee83d208a3a2ac3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<title>spatialsankey.js - sankey diagrams on a map</title> | |
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" /> | |
<style> | |
body { | |
position: absolute; | |
width:100%; | |
height: 100%; | |
margin: 0px; | |
font-family: sans-serif; | |
} | |
#map { | |
top:0px; | |
left:0px; | |
right:0px; | |
height: 100%; | |
} | |
path { | |
fill: none; | |
stroke: #4682B4; | |
stroke-opacity: 0.6; | |
stroke-linecap: round; | |
cursor: pointer; | |
} | |
path:hover { | |
stroke-opacity: 0.8; | |
stroke: #315B7E; | |
} | |
.curvesettings { | |
position: absolute; | |
right: 10px; | |
top:6px; | |
} | |
.box { | |
border: 1px solid #EEE; | |
margin: 3px; | |
padding: 5px; | |
background-color: white; | |
font-family: sans-serif; | |
font-size: 12px; | |
} | |
.title { | |
font-weight: 600; | |
} | |
.source { | |
position: absolute; | |
width: 50%; | |
top: 6px; | |
left: 50px; | |
} | |
</style> | |
<body> | |
<div id="map"></div> | |
<form class="curvesettings"> | |
<div class="box"> | |
<div class="title">Curve settings</div> | |
<div>Hover over nodes<br> to see links.</div> | |
<div>Change styles using<br> radio buttons.</div> | |
</div> | |
<div class="box"> | |
<div class="title">Curve shape</div> | |
<input type="radio" name="use_arcs" value="0" checked>Beziers<br> | |
<input type="radio" name="use_arcs" value="1">Arcs<br> | |
</div> | |
<div class="box"> | |
<div class="title">Flip at horizontal axis</div> | |
<input type="radio" name="flip" value="1" checked>Flip<br> | |
<input type="radio" name="flip" value="0">NoFlip<br> | |
</div> | |
<div class="box"> | |
<div class="title">Set curve steepness X</div> | |
<div>(bezier control point)</div> | |
<input type="radio" name="xshift" value="0.1">xshift 0.1<br> | |
<input type="radio" name="xshift" value="0.4" checked>xshift 0.4<br> | |
<input type="radio" name="xshift" value="0.8">xshift 0.8<br> | |
<input type="radio" name="xshift" value="1.6">xshift 1.6<br> | |
</div> | |
<div class="box"> | |
<div class="title">Set curve steepness Y</div> | |
<div>(bezier control point)</div> | |
<input type="radio" name="yshift" value="0.1" checked>yshift 0.1<br> | |
<input type="radio" name="yshift" value="0.4">yshift 0.4<br> | |
<input type="radio" name="yshift" value="0.8">yshift 0.8<br> | |
<input type="radio" name="yshift" value="1.6">yshift 1.6<br> | |
</div> | |
</form> | |
<div class="source box"> | |
<div class="title">European energy imports</div> | |
This graph shows how much petroleum products in thousands of tonnes are imported by each EU28 country. The <a href="http://appsso.eurostat.ec.europa.eu/nui/show.do?dataset=nrg_123a&lang=en">datasource</a> is the Eurostat database table nrg123a and for the year 2012. | |
</div> | |
</body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script> | |
<script src="http://rawgit.com/geodesign/spatialsankey/master/spatialsankey.js"></script> | |
<script type="text/javascript"> | |
// Set leaflet map | |
var map = new L.map('map', { | |
center: new L.LatLng(50,15), | |
zoom: 4, | |
layers: [ | |
new L.tileLayer('http://{s}tile.stamen.com/toner-lite/{z}/{x}/{y}.png', { | |
subdomains: ['','a.','b.','c.','d.'], | |
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>' | |
}) | |
] | |
}); | |
// Initialize the SVG layer | |
map._initPathRoot() | |
// Setup svg element to work with | |
var svg = d3.select("#map").select("svg"), | |
linklayer = svg.append("g"), | |
nodelayer = svg.append("g"); | |
// Load data asynchronosuly | |
d3.json("https://gist.githubusercontent.com/yellowcap/03cd4a6c72f661377f7e/raw/c146f7d256a1d070d63e346d61164f284f8fa73a/nodes.geojson", function(nodes) { | |
d3.csv("https://gist.githubusercontent.com/yellowcap/03cd4a6c72f661377f7e/raw/c146f7d256a1d070d63e346d61164f284f8fa73a/links.csv", function(links) { | |
// Setup spatialsankey object | |
var spatialsankey = d3.spatialsankey() | |
.lmap(map) | |
.nodes(nodes.features) | |
.links(links); | |
var mouseover = function(d){ | |
// Get link data for this node | |
var nodelinks = spatialsankey.links().filter(function(link){ | |
return link.source == d.id; | |
}); | |
// Add data to link layer | |
var beziers = linklayer.selectAll("path").data(nodelinks); | |
link = spatialsankey.link(options); | |
// Draw new links | |
beziers.enter() | |
.append("path") | |
.attr("d", link) | |
.attr('id', function(d){return d.id}) | |
.style("stroke-width", spatialsankey.link().width()); | |
// Remove old links | |
beziers.exit().remove(); | |
// Hide inactive nodes | |
var circleUnderMouse = this; | |
circs.transition().style('opacity',function () { | |
return (this === circleUnderMouse) ? 0.7 : 0; | |
}); | |
}; | |
var mouseout = function(d) { | |
// Remove links | |
linklayer.selectAll("path").remove(); | |
// Show all nodes | |
circs.transition().style('opacity', 0.7); | |
}; | |
// Draw nodes | |
var node = spatialsankey.node() | |
var node_data = spatialsankey.nodes(); | |
var circs = nodelayer.selectAll("circle") | |
.data(node_data) | |
.enter() | |
.append("circle") | |
.attr("cx", node.cx) | |
.attr("cy", node.cy) | |
.attr("r", node.r) | |
.style("fill", node.fill) | |
.attr("opacity", 0.7) | |
.on('mouseover', mouseover) | |
.on('mouseout', mouseout); | |
// Add labels | |
var y_shift = -30; | |
var x_shift = 0; | |
var text_size = 20; | |
var text_padding = 5; | |
var corner_rounding = 5; | |
var bg_color = 'white' | |
var bg_stroke = 'black' | |
var font_color = 'black' | |
var label_cx = function(d) { return node.cx(d) + x_shift; } | |
var label_cy = function(d) { return node.cy(d) + y_shift; } | |
var labels_bg_width = 100 + 2 * text_padding; | |
var labels_bg_height = text_size + 2 * text_padding; | |
var label_bg_cx = function(d) { return node.cx(d) + x_shift - labels_bg_width / 2; } | |
var label_bg_cy = function(d) { return node.cy(d) + y_shift - labels_bg_height / 2 - text_padding; } | |
// Background for labels. | |
var labels_bg = nodelayer.selectAll("rect") | |
.data(spatialsankey.nodes()) | |
.enter() | |
.append("rect") | |
.attr("width", function (node) { return node.properties.aggregate_outflows == 0 ? 0 : labels_bg_width; }) // Only return if flow value is not zero.100) | |
.attr("height", function (node) { return node.properties.aggregate_outflows == 0 ? 0 : labels_bg_height; }) // Only return if flow value is not zero.100) | |
.attr("x", label_bg_cx) | |
.attr("y", label_bg_cy) | |
.attr("rx", corner_rounding) | |
.attr("ry", corner_rounding) | |
.style("fill", bg_color) | |
.style("stroke", bg_stroke); | |
// Labels texts showing total outflows. | |
var labels = nodelayer.selectAll(".text") | |
.data(node_data) | |
.enter() | |
.append("text") | |
.attr("text-anchor", "middle") | |
.attr("x", label_cx) | |
.attr("y", label_cy) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", text_size) | |
.attr("fill", font_color) | |
.text( function (node) { return node.properties.aggregate_outflows == 0 ? '' : node.properties.aggregate_outflows; }); // Only return if flow value is not zero. | |
// Adopt size of drawn objects after leaflet zoom reset | |
var zoomend = function(event){ | |
// Log zoom (could be used for adaptive label sizing.) | |
console.log('Currently at zoom', event.target._zoom) | |
linklayer.selectAll("path").attr("d", spatialsankey.link()); | |
// Update node location. | |
circs.attr("cx", node.cx) | |
.attr("cy", node.cy); | |
// Update label location. | |
labels.attr("x", label_cx) | |
.attr("y", label_cy); | |
// Update label bg location. | |
labels_bg.attr("x", label_bg_cx) | |
.attr("y", label_bg_cy); | |
}; | |
map.on("zoomend", zoomend); | |
}); | |
}); | |
var options = {'use_arcs': false, 'flip': false}; | |
d3.selectAll("input").forEach(function(x){ | |
options[x.name] = parseFloat(x.value); | |
}) | |
d3.selectAll("input").on("click", function(){ | |
options[this.name] = parseFloat(this.value); | |
}); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment