Invisible vectors? Combining d3.tile(), d3.zoom()

2019-02-26 14:54发布

问题:

I've made effective D3 maps using rasters (d3.tile and map libraries) and vectors (TopoJSON in SVG shapes). But I hit a bug when I combine them.

I followed Mike Bostock's raster-and-vector examples, especially his "Raster & Vector III", which changes the transform and stroke width to update how the vectors are displayed.

My map almost works perfectly. However, upon loading, only the raster tiles are displayed; the vectors are invisible:

But as soon as I trigger the d3.zoom event (by panning or zooming), the vectors are displayed:

I don't understand this, because I explicitly tell the browser, independently of the zoom event, to draw the vectors. This is the relevant snippet:

  // read in the topojson
  d3.json("ausElectorates.json", function(error, mapData) {
    if (error) throw error;

    var electorates = topojson.feature(mapData, mapData.objects.tracts);

    // apply a zoom transform equivalent to projection{scale,translate,center}
    map.call(zoom)
      .call(zoom.transform, d3.zoomIdentity
        .translate(mapWidth / 2, mapHeight / 2)
        .scale(1 << 12)
        .translate(-centre[0], -centre[1]));

    // draw the electorate vectors
    vector.selectAll("path")
      .data(electorates.features)
      .enter().append("path")
        .attr("class", "electorate")
        .attr("d", path);
  });

For some reason, that last line of the d3.json() function -- .attr("d", path") -- isn't visualising the vectors.

Click here to see the map. Click here to access the full code and the TopoJSON it uses.

I'd love advice on this one, which is baffling me!

(PS Apologies for omitting copyright attributions for the map tiles, D3.js library, etc - I'm just trying to minimise the code for this example.)

回答1:

It is drawing the vectors - however, you can't rely on solely scaling and translating your vector with the d3 geoProjection as when you zoom you apply the translate and scale to the path itself - not the projection:

  vector.selectAll("path")
    .attr("transform", "translate(" + [change.x, change.y] + ")scale(" + change.k + ")")
    .style("stroke-width", 1 / change.k);

Since you don't set scale and translate, when loading your vectors they just aren't drawn correctly. They are drawn very small - as your projection scale is 1/tau, with a translation of [0,0]. Inspecting the svg on page load shows that they are there, and they are tiny.

The solution is to draw your vectors prior to map.call("zoom") - this way you can apply the base transform (center, transform, and scale) on the path before manually zooming:

// read in the topojson
d3.json("ausElectorates.json", function(error, mapData) {
if (error) throw error;

var electorates = topojson.feature(mapData, mapData.objects.tracts);

// draw the electorate vectors
vector.selectAll("path")
  .data(electorates.features)
  .enter().append("path")
    .attr("class", "electorate")
    .attr("d", path);

// apply a zoom transform equivalent to projection{scale,translate,center}
map.call(zoom)
  .call(zoom.transform, d3.zoomIdentity
    .translate(mapWidth / 2, mapHeight / 2)
    .scale(1 << 12)
    .translate(-centre[0], -centre[1]));
   });