D3.js Drawing geojson incorrectly

I am trying to visualize russians regions. I got data from here, validate here and all was well - picture.

But when I try to draw it, I receive only one big black rectangle.

var width = 700, height = 400;

var svg = d3.select(".graph").append("svg")
        .attr("viewBox", "0 0 " + (width) + " " + (height))
        .style("max-width", "700px")
        .style("margin", "10px auto");


d3.json("83.json", function (error, mapData) {
    var features = mapData.features;

    var path = d3.geoPath().projection(d3.geoMercator());

    svg.append("g")
            .attr("class", "region")
            .selectAll("path")
            .data(features)
            .enter()
            .append("path")
            .attr("d", path)
});

Example - http://ustnv.ru/d3/index.html Geojson file - http://ustnv.ru/d3/83.json


Solution 1:

The issue is the winding order of the coordinates (see this block). Most tools/utilities/libraries/validators don't really care about winding order because they treat geoJSON as containing Cartesian coordinates. Not so with D3 - D3 uses ellipsoidal math - benefits of this is include being able to cross the antimeridian easily and being able to select an inverted polygon.

The consequence of using ellipsoidal coordinates is the wrong winding order will create a feature of everything on the planet that is not your target (inverted polygon). Your polygons actually contain a combination of both winding orders. You can see this by inspecting the svg paths:

enter image description here

Here one path appears to be accurately drawn, while another path on top of it covers the entire planet - except for the portion it is supposed to (the space it is supposed to occupy covered by other paths that cover the whole world).

This can be simple to fix - you just need to reorder the coordinates - but as you have features that contain both windings in the same collection, it'll be easier to use a library such as turf.js to create a new array of properly wound features:

    var fixed = features.map(function(feature) {
        return turf.rewind(feature,{reverse:true});
    })

Note the reverse winding order - through an odd quirk, D3, which is probably the most widespread platform where winding order matters actually doesn't follow the geoJSON spec (RFC 7946) on winding order, it uses the opposite winding order, see this comment by Mike Bostock:

I’m disappointed that RFC 7946 standardizes the opposite winding order to D3, Shapefiles and PostGIS. And I don’t see an easy way for D3 to change its behavior, since it would break all existing (spherical) GeoJSON used by D3. (source)

By rewinding each polygon we get a slightly more useful map:

enter image description here

An improvement, but the features are a bit small with these projection settings.

By adding a fitSize method to scale and translate we get a much better looking map (see block here):

enter image description here