Skip to content

Instantly share code, notes, and snippets.

@vigorousnorth
Last active March 22, 2019 02:49
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 vigorousnorth/e95a867b10de1239ab3a to your computer and use it in GitHub Desktop.
Save vigorousnorth/e95a867b10de1239ab3a to your computer and use it in GitHub Desktop.
Arc map with scaled arrowheads

Arc map with scaled arrowheads

Render arcs between two points as Bezier-curve arrows, whose sizes are scaled according to a data source (in this case, international trade data).

var svg,
width = 900,
height = 700;
var projection = d3.geo.equirectangular()
.rotate([-10,0]).scale(height/5).translate([width / 2, height / 2]).clipExtent([[0,0.1*height],[width,height*0.85]]);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var path = d3.geo.path()
.projection(projection);
d3.json("world-110m.json", function(error, world) {
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
drawarcs(svg);
d3.select(self.frameElement).style("height", height + "px");
});
var tradedata = [
{
destination: {latitude: -23.3, longitude: 132.2},
name: 'Australia',
trade: 5
},{
destination: { latitude: -28.5, longitude: 24.7 },
name: 'South Africa',
trade : 6
},{
destination: { latitude: 31.7, longitude: 106.2 },
name: 'China',
trade : 16
},{
destination: { latitude: 36.1, longitude: 127.7 },
name: 'S. Korea',
trade: 8
},{
destination: { latitude: 53.6, longitude: -2.3},
name: 'Great Britain',
trade: 12
},{
destination: { latitude: 61.2, longitude: 9.7144087 },
name: 'Norway',
trade: 2
},{
destination: { latitude: 61.6, longitude: 15.4 },
name: 'Sweden',
trade: 5
},{
destination: { latitude: 64.93, longitude: -19.02},
name: 'Iceland',
trade: 15
},{
destination: { latitude: 20.9, longitude: -101.5 },
name: 'Mexico',
trade: 15
},{
destination: { latitude: -14.0, longitude: -47.643501 },
name: 'Brazil',
trade: 12
},{
destination: { latitude: 55.86, longitude: -112.1 },
name: 'Canada',
trade: 32
}
];
function drawarcs(svg) {
var arcs = svg.append("g").selectAll('path.datamaps-arc').data( tradedata, JSON.stringify );
arcs.enter()
.append('path')
.attr('class','arc')
.attr('d', function(datum) {
var origin = projection([-69.445469,45.253783]);
var dest = projection([datum.destination.longitude, datum.destination.latitude]);
var mid = [ (origin[0] + dest[0]) / 2, (origin[1] + dest[1]) / 2];
//define handle points for Bezier curves. Higher values for curveoffset will generate more pronounced curves.
var curveoffset = 20,
midcurve = [mid[0]+curveoffset, mid[1]-curveoffset]
// the scalar variable is used to scale the curve's derivative into a unit vector
scalar = Math.sqrt(Math.pow(dest[0],2) - 2*dest[0]*midcurve[0]+Math.pow(midcurve[0],2)+Math.pow(dest[1],2)-2*dest[1]*midcurve[1]+Math.pow(midcurve[1],2));
// define the arrowpoint: the destination, minus a scaled tangent vector, minus an orthogonal vector scaled to the datum.trade variable
arrowpoint = [
dest[0] - ( 0.5*datum.trade*(dest[0]-midcurve[0]) - datum.trade*(dest[1]-midcurve[1]) ) / scalar ,
dest[1] - ( 0.5*datum.trade*(dest[1]-midcurve[1]) - datum.trade*(-dest[0]+midcurve[0]) ) / scalar
];
// move cursor to origin
return "M" + origin[0] + ',' + origin[1]
// smooth curve to offset midpoint
+ "S" + midcurve[0] + "," + midcurve[1]
//smooth curve to destination
+ "," + dest[0] + "," + dest[1]
//straight line to arrowhead point
+ "L" + arrowpoint[0] + "," + arrowpoint[1]
// straight line towards original curve along scaled orthogonal vector (creates notched arrow head)
+ "l" + (0.3*datum.trade*(-dest[1]+midcurve[1])/scalar) + "," + (0.3*datum.trade*(dest[0]-midcurve[0])/scalar)
// smooth curve to midpoint
+ "S" + (midcurve[0]) + "," + (midcurve[1])
//smooth curve to origin
+ "," + origin[0] + "," + origin[1]
});
arcs.exit().transition()
.style('opacity', 0)
.remove();
}
<!DOCTYPE html>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="arcs.js"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
<body>
<div id='map'>
</div>
<style type="text/css">
.stroke {
fill: none;
stroke: #000;
stroke-width: 3px;
}
.fill {
fill: #fff;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
.land {
fill: #aaa;
}
.boundary {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.arc {
stroke: #000;
fill: rgba(250,0,0,0.5);
stroke-width: 1;
}
</style>
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