Как я могу предотвратить перезапись существующих элементов новыми элементами в d3.js ?

#javascript #d3.js #geolocation

#javascript #d3.js #геолокация

Вопрос:

Я играю с примером использования mbostock d3.js с помощью Google Maps я пытаюсь адаптировать его к 2d-массиву моих собственных данных.

Мои данные представляют собой массив объектов, каждый из которых имеет временную метку и массив геокоординат, Coordinates .

 [{"TimeStamp":"2014-06-18T22:18:07 04:30","Coordinates":[{"Latitude":40.416775,"Longitude":-3.70379},{"Latitude":40.415793,"Longitude":-3.707424},{"Latitude":40.414142,"Longitude":-3.707982}]}
,{"TimeStamp":"2014-06-18T22:23:07 04:30","Coordinates":[{"Latitude":40.411365,"Longitude":-3.708712},{"Latitude":40.411986,"Longitude":-3.705021},{"Latitude":40.406774,"Longitude":-3.711716}]}
,{"TimeStamp":"2014-06-18T22:28:07 04:30","Coordinates":[{"Latitude":40.401365,"Longitude":-3.720449},{"Latitude":40.388455,"Longitude":-3.731843},{"Latitude":40.383568,"Longitude":-3.738881}]}]
 

Я не смог найти способ перебора всех объектов координат, поэтому добавил цикл for для каждого элемента массива первого уровня:

 // Add the container when the overlay is added to the map.
overlay.onAdd = function() {
    var layer = d3.select(this.getPanes().overlayLayer).append("div")
        .attr("class", "stations");

    // Draw each marker as a separate SVG element.
    // We could use a single SVG, but what size would it have?
    overlay.draw = function() {
        var projection = this.getProjection(),
          padding = 10;
        for( var i = 0; i < data.length; i  )
        {
            var marker = layer.selectAll("svg")
              .data(data[i].Coordinates)
              .each(transform) // update existing markers
              .enter()
              .append("svg:svg")
              .each(transform)
              .attr("class", "marker");

            // Add a circle.
            marker.append("svg:circle")
              .attr("r", 4.5)
              .attr("cx", padding)
              .attr("cy", padding);

            // Add a label.
            marker.append("svg:text")
              .attr("x", padding   7)
              .attr("y", padding)
              .attr("dy", ".31em")
              .text(function(d, i){
                    return i;
                    });
        }

        function transform(d) {
                d = new google.maps.LatLng(d.Latitude, d.Longitude);
                d = projection.fromLatLngToDivPixel(d);
                return d3.select(this)
                    .style("left", (d.x - padding)   "px")
                    .style("top", (d.y - padding)   "px");
        }
    };
};
 

Однако d3 перезаписывает маркерные элементы svg на каждой итерации и в этом случае показывает только 3 маркера вместо 9.

Почему это?

Комментарии:

1. Здесь вам не нужен цикл. Используйте вложенные выборки .

2. Выбор @LarsKotthoff касается частей DOM, а не моего массива данных.

3. Ну да? У вас есть вложенные данные и должны быть вложенные элементы DOM, чтобы отразить это.

Ответ №1:

Чтобы ответить на вопрос напрямую, d3 перезаписывает элементы SVG в каждом цикле, потому что они индексируются по их положению в массиве. Вы должны предоставить функцию id, которая является вторым параметром data() функции. Код, которому нужно следовать.

Вложенные выборки не подходят для этого сценария, потому что вам не нужен вложенный html по причине, указанной в связанном коде: «Мы могли бы использовать один SVG, но какой размер он будет иметь?». Итак, мы хотим использовать цикл for в какой-то момент.

Обратите также внимание, что в цикле for должна быть включена только фаза ввода, хотя это не должно иметь большого значения, если вы также добавите туда рендеринг.

 var map = new google.maps.Map(d3.select("#map").node(), {
  zoom: 8,
  center: new google.maps.LatLng(40.411365, -3.708712),
  mapTypeId: google.maps.MapTypeId.ROADMAP
});

 // Load the station data. When the data comes back, create an overlay.
d3.json("soQuest.json", function(data) {
  var overlay = new google.maps.OverlayView();

  // Add the container when the overlay is added to the map.
  overlay.onAdd = function() {
    var layer = d3.select(this.getPanes().overlayLayer).append("div")
      .attr("class", "stations");

    // Draw each marker as a separate SVG element.
    // We could use a single SVG, but what size would it have?
    overlay.draw = function() {
      var projection = this.getProjection(),
        padding = 10,
        marker;


      for (i = 0; i < data.length; i  ) {
      marker = layer.selectAll("svg")
          .data(data[i].Coordinates, function(d, index){
            return data[i].TimeStamp   index   d.Latitude   d.Longitude;
          })
          .enter().append("svg");
      }

      marker = layer.selectAll("svg")
        .each(transform) // update existing markers
      .attr("class", "marker");

      // Add a circle.
      marker.append("svg:circle")
        .attr("r", 4.5)
        .attr("cx", padding)
        .attr("cy", padding);

      // Add a label.
      marker.append("svg:text")
        .attr("x", padding   7)
        .attr("y", padding)
        .attr("dy", ".31em")
        .text(function(d) {
          return "MARKER"; //d.key;
        });

      function transform(d) {
        d = new google.maps.LatLng(d.Latitude, d.Longitude);
        d = projection.fromLatLngToDivPixel(d);
        return d3.select(this)
          .style("left", (d.x - padding)   "px")
          .style("top", (d.y - padding)   "px");
      }
    };
  };

  // Bind our overlay to the map…
  overlay.setMap(map);
});
 

Комментарии:

1. Согласно вашим объяснениям и моим образцам данных, предоставленная вами функция map также не будет работать, потому что есть несколько точек с одинаковыми широтами / длинами.

2. Привет, СЭМ, вот плунжер . Это работает для предоставленных вами данных, но если у вас есть два маркера в одних и тех же координатах, вам нужно будет включить что-то еще в идентификатор, чтобы сделать их уникальными. Это не должно быть слишком сложной задачей.

3. Хорошо, изменили функцию id, чтобы она гарантированно возвращала уникальный идентификатор, но было бы намного приятнее, если бы ваши маркеры имели уникальное свойство, которое можно было бы использовать вместо этого. Теперь я ответил на вопрос?

Ответ №2:

Я бы посоветовал вам попробовать определить переменную marker вне цикла for . Дайте мне знать, если это решит проблему. 🙂

Ответ №3:

Вероятно, самый простой способ — просто сгладить массив,

 var flat_data = [];

for (var i=0; i<data.length; i  ){
   for (var j=0; j<data[i].length; j  ){
       flat_data.push(data[i][j])
   }
}
 

Затем пропустите цикл for и просто выполните

 var layer = d3.select(this.getPanes().overlayLayer).append("div")
        .attr("class", "stations");

    // Draw each marker as a separate SVG element.
    // We could use a single SVG, but what size would it have?
    overlay.draw = function() {
        var projection = this.getProjection(),
          padding = 10;

        var marker = layer.selectAll("svg")
           .data(flat_data)
           .each(transform) // update existing markers
           .enter()
           .append("svg:svg")
           .each(transform)
           .attr("class", "marker");

        // Add a circle.
        marker.append("svg:circle")
           .attr("r", 4.5)
           .attr("cx", padding)
           .attr("cy", padding);

        // Add a label.
        marker.append("svg:text")
           .attr("x", padding   7)
           .attr("y", padding)
           .attr("dy", ".31em")
           .text(function(d, i){
              return i;
           });
        }

    function transform(d) {
        d = new google.maps.LatLng(d.Latitude, d.Longitude);
        d = projection.fromLatLngToDivPixel(d);
           return d3.select(this)
              .style("left", (d.x - padding)   "px")
              .style("top", (d.y - padding)   "px");
        }
    };
};
 

Комментарии:

1. Выравнивание массива не является хорошим выбором, поскольку доступ к временным меткам, связанным с каждым элементом, требует грязной работы и затрудняет чтение кода.

2. Довольно просто заполнить эти данные в сплющенный массив.