Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@yellowcap
Last active May 10, 2023 08:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save yellowcap/03cd4a6c72f661377f7e to your computer and use it in GitHub Desktop.
Save yellowcap/03cd4a6c72f661377f7e to your computer and use it in GitHub Desktop.
spatialsankey.js - visualizing flows on a leaflet map
<!DOCTYPE html>
<meta charset="utf-8">
<title>spatialsankey.js - sankey diagrams on a map</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin="" />
<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="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js" integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" crossorigin=""></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("nodes.geojson", function(nodes) {
d3.csv("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 circs = nodelayer.selectAll("circle")
.data(spatialsankey.nodes())
.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);
// Adopt size of drawn objects after leaflet zoom reset
var zoomend = function(){
linklayer.selectAll("path").attr("d", spatialsankey.link());
circs.attr("cx", node.cx)
.attr("cy", node.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>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment