在 D3 中从 Mapbox visible/invisible 制作背景地图的开关

Switch for making background map from Mapbox visible/invisible in D3

我正在构建受 http://chriszetter.com/blog/2014/06/15/building-a-voronoi-map-with-d3-and-leaflet/. I'd like to make an option to turn off the background map as the location of the data may not be relevant in all my use cases. Furthermore, it would be great if the visualization could work offline this way. After toggling the switch, the entire background would be white. The Voronoi overlap would be the same. How do I do that? Here's the code (zip-file contains the csv-files): https://www.dropbox.com/s/i8vtfh8mkxazfr0/voronoi-maps-master.zip?dl=0

启发的 Voronoi 地图

编辑: 原始代码中有两层变量,因为我试图将可视化分成两部分。但是,该尝试未成功,仅使用了 mapLayer。这在原问题中可能没有说清楚。

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="base.css" rel="stylesheet" />
<link href='https://api.tiles.mapbox.com/mapbox.js/v1.6.3/mapbox.css' rel='stylesheet' />
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <div id='map'>
  </div>
  <div id='loading'>
  </div>
  <!-- <div id='selected'>
    <h1>...</h1>
  </div> -->
  </div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"></script>
  <script src="https://api.tiles.mapbox.com/mapbox.js/v1.6.3/mapbox.js"></script>
  <!-- <script src="/voronoi-map/lib/voronoi_map.js"></script> -->
  <script type="text/javascript" src="voronoi_map.js"></script>
  <script>
    map = L.mapbox.map('map', 'zetter.i73ka9hn') // <- dur ikke!
      .fitBounds([[59.355596 , -9.052734], [49.894634 , 3.515625]]);

    url = 'supermarkets.csv';
    initialSelection = d3.set(['Tesco', 'Sainsburys']);
    voronoiMap(map, url, initialSelection);
  </script>
</body>
</html>

voronoi_map.js

voronoiMap = function(map, url, initialSelections) {
  var pointTypes = d3.map(),
      points = [],
      lastSelectedPoint;

  var voronoi = d3.geom.voronoi()
      .x(function(d) { return d.x; })
      .y(function(d) { return d.y; });

  var selectPoint = function() {
    d3.selectAll('.selected').classed('selected', false);

    var cell = d3.select(this),
        point = cell.datum();

    lastSelectedPoint = point;
    cell.classed('selected', true);

    d3.select('#selected h1')
      .html('')
      .append('a')
        .text( /*point.name*/ "8 interactions from this cell")
        /* .attr('href', point.url)
        .attr('target', '_blank') */
  }

  var drawWithLoading = function(e){
    d3.select('#loading').classed('visible', true);
    if (e && e.type == 'viewreset') {
      d3.select('#overlay').remove();
    }
    setTimeout(function(){
      draw();
      d3.select('#loading').classed('visible', false);
    }, 0);
  }

  var draw = function() {
    d3.select('#overlay').remove();

    var bounds = map.getBounds(),
        topLeft = map.latLngToLayerPoint(bounds.getNorthWest()),
        bottomRight = map.latLngToLayerPoint(bounds.getSouthEast()),
        existing = d3.set(),
        drawLimit = bounds.pad(0.4);

    filteredPoints = points.filter(function(d) {
      var latlng = new L.LatLng(d.latitude, d.longitude);

      if (!drawLimit.contains(latlng)) { return false };

      var point = map.latLngToLayerPoint(latlng);

      key = point.toString();
      if (existing.has(key)) { return false };
      existing.add(key);

      d.x = point.x;
      d.y = point.y;
      return true;
    });

    voronoi(filteredPoints).forEach(function(d) { d.point.cell = d; });

    var svg = d3.select(map.getPanes().overlayPane).append("svg")
      .attr('id', 'overlay')
      .attr("class", "leaflet-zoom-hide")
      .style("width", map.getSize().x + 'px')
      .style("height", map.getSize().y + 'px')
      .style("margin-left", topLeft.x + "px")
      .style("margin-top", topLeft.y + "px");

    var g = svg.append("g")
      .attr("transform", "translate(" + (-topLeft.x) + "," + (-topLeft.y) + ")");

    var svgPoints = g.attr("class", "points")
      .selectAll("g")
        .data(filteredPoints)
      .enter().append("g")
        .attr("class", "point");

    var buildPathFromPoint = function(point) {
      return "M" + point.cell.join("L") + "Z";
    }


    svgPoints.append("path")
      .attr("class", "point-cell")
      .attr("d", buildPathFromPoint)
      //.style('fill', function(d) { return '#' + d.color } )
      .on('click', selectPoint)
      .classed("selected", function(d) { return lastSelectedPoint == d} );

    /* svgPoints.append("circle")
      .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
      .style('fill', function(d) { return '#' + d.color } )
      .attr("r", 2); */
  }

/* function interactionGradient() {
    color = 

    if 
    return 
} */


  var mapLayer = {
    onAdd: function(map) {
      map.on('viewreset moveend', drawWithLoading);
      drawWithLoading();
    }
  };

  var voronoiLayer = {
    onAdd: function(map) {
      map.on('viewreset moveend', drawWithLoading);
      drawWithLoading();
    }
  };


  map.on('ready', function() {
    d3.csv(url, function(csv) {
      points = csv;
      points.forEach(function(point) {
        pointTypes.set(point.type, {type: point.type, color: point.color});
      })
      map.addLayer(mapLayer);
    })
  });
}

您需要单独加载 tileLayer,以便您可以创建对它的引用,然后您可以反过来使用它来创建可以轻松 enable/disable 层的图层控件:

L.mapbox.accessToken = 'pk.eyJ1IjoicGF1bC12ZXJob2V2ZW4iLCJhIjoiZ1BtMWhPSSJ9.wQGnLyl1DiuIQuS0U_PtiQ';

// Create the tileLayer.
var tileLayer = L.mapbox.tileLayer('examples.map-i86nkdio');

var map = L.mapbox.map('mapbox', null, { // Do not add as parameter
    'center': [0, 0],
    'zoom': 1,
    // Add here so it still gets added to the map initially
    // You could skip this so it won't be added and you can
    // turn it on via the layercontrol
    'layers': [tileLayer] 
});

// Create layer control
var layerControl = L.control.layers(null, {
    'Tilelayer': tileLayer // Add tile layer to overlays
}).addTo(map);

这是一个关于 Plunker 的工作示例:http://plnkr.co/edit/5re3o6qnyCwAqXNYXrkP?p=preview

L.mapbox.tileLayer参考:https://www.mapbox.com/mapbox.js/api/v2.1.5/l-mapbox-tilelayer/

L.control.layers参考:http://leafletjs.com/reference.html#control-layers

根据评论进行编辑:

您已经在使用单独的图层,tilelayer(背景)在地图初始化时添加 L.mapbox.map('map', 'zetter.i73ka9hn'),实际上调用:L.mapbox.tileLayer('zetter.i73ka9hn').addTo(map)。您需要这样做,因为您需要对图层的引用,以便可以将其添加到 L.control.layers,如上所示。您的 voronoi 层被添加到地图 ready 处理程序的 voronoiMap 方法中:map.addLayer(mapLayer);

如你所见,它们已经分开了。现在,如果您还希望能够在图层控件中切换 voronoi 图层,则需要将其添加到图层控件中:

map.on('ready', function() {
    d3.csv(url, function(csv) {
        points = csv;
        points.forEach(function(point) {
            pointTypes.set(point.type, {
                type: point.type,
                color: point.color
            });
        });
        map.addLayer(mapLayer);
        layerControl.addOverlay(mapLayer, 'Voronoi'); // Here
    })
});

但是在你的情况下这本身是不够的,因为你的图层没有 ILayer 接口规定的 onRemove 方法:

http://leafletjs.com/reference.html#ilayer

现在,如果我们像这样向您的层添加一个 onRemove 方法:

var mapLayer = {
    onAdd: function(map) {
        map.on('viewreset moveend', drawWithLoading);
        drawWithLoading();
    },
    onRemove: function (map) {
        d3.select('#overlay').remove();
    }
};

有效:http://plnkr.co/edit/3z3pCAo0gGuA7xqnqiqb?p=preview(注意我已经注释掉了 ready 处理程序,因为地图在函数调用之前就已准备就绪,因此它不会触发并更改了一些颜色以使事情变得更清楚。)希望这有帮助。