Reputation: 141
I am trying to get a world map to display similar to the U.S. map provided here (U.S. map), but I am running into some issues. Currently this is what I have so far for my world map code:
<!DOCTYPE html>
<style>
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script>
var svg = d3.select("svg");
var path = d3.geoPath();
d3.json("https://unpkg.com/world-atlas@1/world/50m.json", function(error, world)
{
if (error) throw error;
svg.append("g")
.selectAll("path")
.data(topojson.feature(world, world.objects.countries).features)
.enter().append("path")
.attr("d", path);
});
</script>
However, the map that it is showing is only taking up the top left portion of my window, and seems to look incorrect. Currently I'm a bit confused on what I may be doing wrong. If anyone has any insight I would appreciate it.
Thanks.
EDIT: I was actually able to get it working with the provided code here
<!DOCTYPE html>
<style>
.countries :hover
{
fill: red;
}
.country-borders
{
fill: none;
stroke: #FFF;
stroke-width: 0.5px;
stroke-linejoin: round;
stroke-linecap: round;
cursor: pointer;
}
</style>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var format = d3.format(",");
var margin = {top: 0, right: 0, bottom: 0, left: 0},
width = 1200 - margin.left - margin.right,
height = 900 - margin.top - margin.bottom;
var path = d3.geoPath();
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append('g')
.attr('class', 'map');
var projection = d3.geoMercator()
.scale(170)
.translate( [width / 2, height / 1.5]);
var path = d3.geoPath().projection(projection);
d3.json("https://unpkg.com/world-atlas@1/world/50m.json", function( error, world )
{
svg.append("g")
.attr("class", "countries")
.selectAll("path")
.data(topojson.feature( world, world.objects.countries ).features )
.enter().append("path")
.attr("d", path);
svg.append("path")
.attr( "class", "country-borders" )
.attr("d", path(topojson.mesh( world, world.objects.countries, function( a, b)
{
return a !== b;
})));
});
</script>
</body>
</html>
I believe i was doing my scaling wrong and not correctly doing the projection onto the map. If anyone has any comments though on how I did it please let me know.
Upvotes: 1
Views: 1343
Reputation: 38151
The US json that you reference is a special json file - it doesn't need to use a projection. Your path
variable uses a null projection (the default projection), converting the coordinate in the geojson (to which the topojson is converted) to pixel values without a transformation.
This file might have been created to allow simpler blocks showing d3 functionality by skipping projection details, but in any event it is relatively unique because of how it is projected.
Each vertex in that US json file has been converted from a latitude/longitude pair representing a point on a 3 dimensional earth to a cartesian x,y coordinate representing a plane with a width of 960 and a height of 500 (the width and height of a bl.ock's default view). Your world data still contains latitude longitude pairs and therefore needs to make use of a projection.
A give away that your data is unprojected is that it is upside down, which results when converting latitude longitude data to pixel coordinates in svg without the transform that defines a projection. The north pole is at 90 degrees north, the equator at 0 degrees. When working with SVG, a y coordinate of zero will be at the top of the screen, a y coordinate of 90 will be 90 pixels down. Thus, the north pole will be below the equator when converting latitude longitude points to x,y svg points with no conversion. Further, the western and southern hemispheres have negative x and/or y values, so they won't appear in your svg without a transform. This is why you see only points east of the prime meridian and north of the equator on your map.
How to fix this? You need to project your data. You could use any one projection, I will use a mercator as it is probably more recognizable than others (despite major distortion issues near the poles):
var projection = d3.geoMercator();
var path = d3.geoPath().projection(projection);
That's it (see here).
For reference, the default parameters of a Mercator projection are intended to show the earth across 960 pixels, if you want to focus on a specific area or show the map with a different size (scale), then you will need to set the projection parameters.
Upvotes: 1