1
votes

I am following Chris Zetter's tutorial of creating a Voronoi overlay on Leaflet, but I have replaced the Mapbox tiles with Google Maps tiles (using the Google Mutant Leaflet plugin) and used my own data.

The voronoi overlay works, but does not update when the map is moved or the view is reset (moveend and viewreset events).

It seems that the "viewreset" event doesn't get fired, and the "moveend" event doesn't trigger the "drawWithLoading" function.

The error that comes up is: Uncaught TypeError: Cannot read property 'call' of undefined(…) which is on the indicated line of this function:

map.on('load', function() {

  d3.csv(url, function(csv) {
    points = csv;
    points.forEach(function(point) {
      pointTypes.set(point.type, {type: point.type, color: point.color});
    })
    drawPointTypeSelection();
    map.addLayer(mapLayer); //ERROR on this line
  })
});

Here is the mapLayer function it is refering to:

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

And here is the drawWithLoading function:

var drawWithLoading = function(e){

    console.log('drawWithLoading e', e); //TODO: delete later
    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);
  }

I'm quite new with javascript and have been trying to figure this out for the past few days.

Does anyone know why the mapLayer function error comes up, and why the map doesn't redraw on the "moveend" and "viewreset" events?

Edited to include entire code:

// Selects check boxes in selector menu //
showHide = function(selector) {
  d3.select(selector).select('.hide').on('click', function(){
    d3.select(selector)
      .classed('visible', false)
      .classed('hidden', true);
  });

  d3.select(selector).select('.show').on('click', function(){
    d3.select(selector)
      .classed('visible', true)
      .classed('hidden', false);
  });
}

// Draws Voronoi Map //
voronoiMap = function(map, url, initialSelections) {

  console.log('We got in here!');
  console.log('map', map);
  console.log('url', url);
  console.log('initialSelections', initialSelections);
  window.myMap = map;

  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)
        .attr('href', point.url)
        .attr('target', '_blank')
  }

  var drawPointTypeSelection = function() {
    showHide('#selections')
    labels = d3.select('#toggles').selectAll('input')
      .data(pointTypes.values())
      .enter().append("label");

    labels.append("input")
      .attr('type', 'checkbox')
      .property('checked', function(d) {
        return initialSelections === undefined || initialSelections.has(d.type)
      })
      .attr("value", function(d) { return d.type; })
      .on("change", drawWithLoading);

    labels.append("span")
      .attr('class', 'key')
      .style('background-color', function(d) { return '#' + d.color; });

    labels.append("span")
      .text(function(d) { return d.type; });
  }

  var selectedTypes = function() {
    return d3.selectAll('#toggles input[type=checkbox]')[0].filter(function(elem) {
      return elem.checked;
    }).map(function(elem) {
      return elem.value;
    })
  }

  var pointsFilteredToSelectedTypes = function() {
    var currentSelectedTypes = d3.set(selectedTypes());
    return points.filter(function(item){
      return currentSelectedTypes.has(item.type);
    });
  }

  var drawWithLoading = function(e){

    console.log('drawWithLoading e', e); //TODO: delete later
    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 = pointsFilteredToSelectedTypes().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; });

    console.log('map.getPanes()', map.getPanes());

    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")
        .attr('data-name', function(d) { return d.name } );

    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
          if (d.name <= 10) {return "ffffd9"}
                    else if (d.name <=15 && d.name >10) {return "edf8b1"}
                    else if (d.name <=20 && d.name >15) {return "7fcdbb"}
                    else if (d.name <=25 && d.name >20) {return "41b6c4"}
                    else if (d.name <=35 && d.name >25) {return "1d91c0"}
                    else if (d.name <=45 && d.name >35) {return "253494"}
                    else {return "081d58"}
      })
      .attr("opacity", 0.5)
      .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
        if (d.name <= 10) {return "ffffd9"}
                    else if (d.name <=15 && d.name >10) {return "edf8b1"}
                    else if (d.name <=20 && d.name >15) {return "7fcdbb"}
                    else if (d.name <=25 && d.name >20) {return "41b6c4"}
                    else if (d.name <=35 && d.name >25) {return "1d91c0"}
                    else if (d.name <=45 && d.name >35) {return "253494"}
                    else {return "081d58"} } )
      .attr("r", 1.4)
      .attr("opacity", 0.6);
  }


  var mapLayer = {
    onAdd: function(map) {
      console.log("onviewreset map", map); //TODO: Delete later
      map.on('viewreset moveend', drawWithLoading);
      drawWithLoading();
    },
  };

  showHide('#about');

  map.on('load', function() {
    console.log('map ready');
    d3.csv(url, function(csv) {
      points = csv;
      points.forEach(function(point) {
        pointTypes.set(point.type, {type: point.type, color: point.color});
      })
      drawPointTypeSelection();
      console.log("addLayer"); //TODO: Delete Later
      map.addLayer(mapLayer);
      console.log("addedLayer"); //TODO: Delete Later
    })
  });

  console.log('got here too');
}

// Draws Google Maps tiles using Road and Style tiles //
var map = L.map('map');

var roadMutant = L.gridLayer.googleMutant({
            maxZoom: 24,
            type:'roadmap'
}).addTo(map);

var styleMutant = L.gridLayer.googleMutant({
            styles: [
                {elementType: 'labels', stylers: [{visibility: 'off'}]},
                {featureType: 'water', stylers: [{color: '#444444'}]},
                {featureType: 'landscape', stylers: [{color: '#eeeeee'}]},
                {featureType: 'road', stylers: [{visibility: 'on'}]},
                {featureType: 'poi', stylers: [{visibility: 'off'}]},
                {featureType: 'transit', stylers: [{visibility: 'on'}]},
                {featureType: 'administrative', stylers: [{visibility: 'off'}]},
                {featureType: 'administrative.locality', stylers: [{visibility: 'off'}]}
            ],
            maxZoom: 24,
            type:'roadmap'
});

// Creates a styles selector menu //
L.control.layers({
  Roadmap: roadMutant,
  Styles: styleMutant
}, {}, {
  collapsed: false
}).addTo(map);



url = 'AllCoords_supertrial.csv';
initialSelection = d3.set(['1','2','3']); //Ideally would like to have all selected to start with or take away having to check boxes before drawing//
voronoiMap(map, url, initialSelection);
1
This is way to difficult to debug with the small snippets of code you've included. Can you reproduce it in a stack snippet, plunker or jsfiddle? - Mark
@Mark I have included the entire code. And here is the jsfiddle jsfiddle.net/62c6eadf/1 . I've never used it before but hope that helps, and do let me know if there's anything else that will make it easier. Thanks! - Heather

1 Answers

1
votes

Even though you have posted some code, and a fiddle, it's a bit difficult to know what's happening. Please try to update your question to provide a complete example.

I'm going to make a wild guess and point to this one line of code:

var mapLayer = {
   ...
};

If you read the Leaflet tutorials, particularly the ones about extending Leaflet to create new types of map layers, you'll realize that the way to create a subclass of Layer is:

var mapLayer = L.Layer.extend({
    onAdd: function(map) {...},
    ...
});

Leaflet expects map layers to be instances of L.Layer, implementing all the internal methods. If you try to add something that does not implement the L.Layer "interface", things will break.

If you don't understand subclassing and class interfaces and such, please refresh your knowledge of OOP.

This might not be the only problem in your code, but it's the only one I can see right now, without having a proper look at a complete example.