|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
.links line { |
|
stroke: #999; |
|
stroke-opacity: 0.5; |
|
stroke-width:5px; |
|
} |
|
</style> |
|
<body> |
|
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> |
|
<script src="https://d3js.org/topojson.v2.min.js"></script> |
|
|
|
<script> |
|
|
|
var width = 900, |
|
height = 450; |
|
|
|
var vertices = []; |
|
|
|
var projection = d3.geoMercator().scale(250);//.scale(1000).center([175,65]);//.scale(1000).center([25,40]); |
|
|
|
var path = d3.geoPath(projection); |
|
|
|
var ports = { |
|
Lisbon: [-9,38.7], |
|
Luanda:[13,-8], |
|
Colombo: [79.9,6.9] |
|
}; |
|
|
|
var simulation = d3.forceSimulation() |
|
.force("link", d3.forceLink().distance(2) ) |
|
.force("charge", d3.forceManyBody().strength(function(d) { if(d.type == "land") { return -250; } else { return -30; } }).distanceMax(30) ) |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width) |
|
.attr("height", height) |
|
.style('background','#43a2ca'); |
|
|
|
// add two layers: |
|
var g1 = svg.append('g'); |
|
var g2 = svg.append('g'); |
|
|
|
d3.json("world.json", function(error, world) { |
|
//append the world: |
|
g1.selectAll(".world") |
|
.data(topojson.feature(world, world.objects.countries).features) |
|
.enter() |
|
.append('path') |
|
.attr("d", function(d) { return path(d); }) |
|
.attr('fill','#a8ddb5') |
|
.attr('stroke','#999'); |
|
|
|
// Append the three ports: |
|
g2.selectAll('.port') |
|
.data(d3.entries(ports)) |
|
.enter() |
|
.append('g') |
|
.attr('transform',function(d) { return 'translate('+projection(d.value)+')'; }) |
|
.append('circle') |
|
.attr('r',6) |
|
.attr('stroke','darkblue') |
|
.attr('stroke-width',2) |
|
.attr('fill','white') |
|
|
|
// Load up the force points covering land masses: |
|
d3.json("points.json", function(error, p) { |
|
var points = []; |
|
var links = []; |
|
var i = 0; |
|
p.forEach(function(d) { |
|
i++ |
|
var x = projection(d)[0]; |
|
var y = projection(d)[1]; |
|
points.push({"x":x,"y":y,"fx":x,"fy":y,"type":"land" }); |
|
}); |
|
|
|
// Draw routes: |
|
var routes = svg.selectAll('.route') |
|
.data([{source:"Colombo",target:"Luanda"},{source:"Luanda",target:"Lisbon"}]) |
|
.enter() |
|
.append('path') |
|
.attr('d', function(d) { |
|
return path ({ |
|
type:"LineString", |
|
coordinates: [ ports[d.source],ports[d.target] ] |
|
}); |
|
}) |
|
.attr('fill','none') |
|
.attr('stroke','yellow') |
|
.attr('stroke-width',0) |
|
|
|
// Add points along a route: |
|
routes.each(function(d,j) { |
|
var route = d3.select(this).node(); |
|
var totalLength = route.getTotalLength(); |
|
var spacing = 40; |
|
var n = totalLength/spacing; |
|
var i = 0; |
|
d.points = []; |
|
points.push({"x":projection(ports[d.source])[0],"fx":projection(ports[d.source])[0],"y":projection(ports[d.source])[1],"fy":projection(ports[d.source])[1],"type":"route","routeID":j,"fid":(j+"."+0)}) |
|
while ( i < 5 ) { |
|
points.push({"x":projection(ports[d.source])[0],"y":projection(ports[d.source])[1],"type":"route","routeID":j,"fid":(j+"."+i)}) |
|
links.push({"source":points.length-1,"target":points.length-2}); |
|
i++; |
|
} |
|
while ( i < n) { |
|
var xy = route.getPointAtLength( spacing * i + spacing /2 ) |
|
points.push({"x":xy.x,"y":xy.y,"type":"route","routeID":j,"routeID":j,"fid":(j+"."+(i+1))}) |
|
links.push({"source":points.length-1,"target":points.length-2}); |
|
|
|
|
|
i++; |
|
} |
|
while ( i < (n + 5) ) { |
|
points.push({"x":projection(ports[d.target])[0],"y":projection(ports[d.target])[1],"type":"route","routeID":j,"fid":(j+"."+i)}) |
|
links.push({"source":points.length-1,"target":points.length-2}); |
|
i++; |
|
} |
|
points.push({"x":projection(ports[d.target])[0],"fx":projection(ports[d.target])[0],"y":projection(ports[d.target])[1],"fy":projection(ports[d.target])[1],"type":"route","routeID":j,"fid":(j+"."+i)}) |
|
links.push({"source":points.length-1,"target":points.length-2}); |
|
|
|
i= 0; |
|
}); |
|
|
|
|
|
// draw the temporary lines to show the force at work: |
|
var link = g1.append("g") |
|
.attr("class", "links") |
|
.selectAll("line") |
|
.data(links) |
|
.enter().append("line"); |
|
|
|
// do the simulation: |
|
simulation |
|
.nodes(points) |
|
.on("tick", ticked); |
|
|
|
simulation.force("link") |
|
.links(links); |
|
|
|
var j = 0; |
|
|
|
function ticked() { |
|
j++; |
|
if (j > 100) {simulation.stop(); |
|
// draw the final route: |
|
var splineLine = d3.line().curve(d3.curveCardinal).x(function(d) { return d.x}).y(function(d) { return d.y; }); |
|
var spliner = d3.selectAll('.spline') |
|
.data(points) |
|
.enter() |
|
.filter(function(d) { return d.type == "route"}); |
|
|
|
var spline = g1.append('path').attr('d',splineLine(spliner.data())).attr('fill',"none").attr('stroke-width',4).attr('stroke','black'); |
|
|
|
var totalLength = spline.node().getTotalLength(); |
|
|
|
spline.attr('stroke-dasharray',(totalLength + 4) + ", " + (totalLength + 4)) |
|
.attr('stroke-dashoffset',totalLength + 4 ) |
|
|
|
spline.transition() |
|
.attr('stroke-dashoffset',0) |
|
.duration(3000); |
|
|
|
} |
|
else { |
|
link |
|
.attr("x1", function(d) { return d.source.x }) |
|
.attr("y1", function(d) { return d.source.y}) |
|
.attr("x2", function(d) { return d.target.x; }) |
|
.attr("y2", function(d) { return d.target.y; }); |
|
} |
|
} |
|
}); |
|
}); |
|
|
|
</script> |