Добавление поведения масштабирования к пользовательским точкам карты

В качестве упражнения для изучения D3 я использовал набор данных из предыдущего проекта о расположении и названиях аэропортов по всему миру. Я загружаю это на свою веб-страницу с помощью D3.csv и рисую точки на карте с помощью topojson.

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

Как ни странно, я могу заставить поведение Zoom работать со странами, но я не уверен, как заставить его работать с кругами, которые я нарисовал. Если я увеличиваю карту с помощью колеса прокрутки, карта увеличивается, но круги остаются на месте.

<script src = "http://d3js.org/d3.v3.min.js" charset = "utf-8"></script>
<script src = "http://d3js.org/topojson.v1.min.js"></script>
<script src = "http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<style type = "text/css">
    .feature {
        fill: none;
        stroke: grey;
        stroke-width: 1px;
        stroke-linejoin: round;
    }
    .mesh {
        fill: none;
        stroke: lightgrey;
        stroke-width: 2px;
        stroke-linejoin: round;
    }
    h1 {
        font-family: sans-serif;
    }

    svg {
  background: #eee;
}

.sphere {
  fill: #fff;
}

.land {
  fill: #000;
}

.boundary {
  fill: none;
  stroke: #fff;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.overlay {
  fill: none;
  pointer-events: all;
}

circle{
  fill: steelblue;
  stroke-width: 1.5px;  
}

.d3-tip {
  line-height: 1;
  font-weight: bold;
  padding: 12px;
  background: rgba(0, 0, 0, 0.8);
  color: #fff;
  border-radius: 2px;
}

/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
  box-sizing: border-box;
  display: inline;
  font-size: 10px;
  width: 100%;
  line-height: 1;
  color: rgba(0, 0, 0, 0.8);
  content: "\25BC";
  position: absolute;
  text-align: center;
}

/* Style northward tooltips differently */
.d3-tip.n:after {
  margin: -1px 0 0 0;
  top: 100%;
  left: 0;
}
</style>
</head>
<body>
    <h1>Lots of airports across the world</h1>

<script type = "text/javascript">
var width = 950,
    height = 550;
    scale0 = (width - 1) / 2 / Math.PI;

var projection = d3.geo.mercator();

var zoom = d3.behavior.zoom()
    .translate([width / 2, height / 2])
    .scale(scale0)
    .scaleExtent([scale0, 8 * scale0])
    .on("zoom", zoomed);

var path = d3.geo.path()
    .projection(projection);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
  .append("g");

var g = svg.append("g");

var circle = svg.append("circle");

svg.append("rect")
    .attr("class", "overlay")
    .attr("width", width)
    .attr("height", height);

svg
    .call(zoom)
    .call(zoom.event);

var tip = d3.tip()
    .attr("class", "d3-tip")
    .offset([-10, 0])
    .html(function(d) {
        return "Name" + ": " + d[2] + "<br>" + "Location" + ": " + d[3];
    });

svg.call(tip);

d3.json("world-110m.v1.json", function(error, world) {
  if (error) throw error;

  g.append("g")
      .attr("d", path)
      .on("click", clicked)
      .on("zoom", zoomed);

  g.append("path")
      .datum({type: "Sphere"})
      .attr("class", "sphere")
      .attr("d", path);

  g.append("path")
      .datum(topojson.merge(world, world.objects.countries.geometries))
      .attr("class", "land")
      .attr("d", path);

  g.append("path")
      .datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
      .attr("class", "boundary")
      .attr("d", path)
      .on("click", clicked);

    d3.csv("output.csv",
        function(data) {return {name: data.Airport_name, location: data.Location_served, 
            long : +data.Longitude, lat : +data.Latitude}},
        function(data) {
        var new_array = data.map(function (d) {return [d.long, d.lat, d.name, d.location]});
        console.info("new", new_array)

        svg.selectAll("circle")
            .data(new_array)
            .enter()
            .append("circle")
            .attr("cx", function (d) { return projection(d)[0]; })
            .attr("cy", function (d) { return projection(d)[1]; })
            .attr("r", "2px")
            .on("mouseover", tip.show)
            .on("mouseout", tip.hide);
        });

    }) //closes the json, do not move.

// begin click-zoom listeners
function clicked(d) {
    console.info("d:",d)
  var centroid = path.centroid(d),
      translate = projection.translate();

  projection.translate([
    translate[0] - centroid[0] + width / 2,
    translate[1] - centroid[1] + height / 2
  ]);

  zoom.translate(projection.translate());

  g.selectAll("path").transition()
      .duration(700)
      .attr("d", path);
}

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale);
  g.selectAll("path").attr("d", path);
}
</script>    
</body>

Итак, что начинает выглядеть так Добавление поведения масштабирования к пользовательским точкам карты

заканчивается так при увеличении Добавление поведения масштабирования к пользовательским точкам карты

Мне бы хотелось, чтобы круги двигались так же, как и страны.

Образец CSV:

Airport_name,DST,IATA,ICAO,Location_served,Time,Latitude,Longitude
Anaa Airport,,AAA,NTGA,"Anaa, Tuamotus, French Polynesia",UTC?10:00,-16.9419074,-144.8646172
Arrabury Airport,,AAB,YARY,"Arrabury, Queensland, Australia",UTC+10:00,-26.7606354,141.0269959
El Arish International Airport,,AAC,HEAR,"El Arish, Egypt",UTC+02:00,31.1272509,33.8045859
Adado Airport,,AAD,,"Adado (Cadaado), Galguduud, Somolia",UTC+03:00,9.56045635,31.65343724
Rabah Bitat Airport (Les Salines Airport),,AAE,DABB,"Annaba, Algeria",UTC+01:00,36.8970249,7.7460806
Apalachicola Regional Airport,Mar-Nov,AAF,KAAF,"Apalachicola, Florida, United States",UTC?05:00,29.7258675,-84.9832278
Arapoti Airport,Oct-Feb,AAG,SSYA,"Arapoti, Paraná, Brazil",UTC?03:00,-24.1458941,-49.8228117
Merzbrück Airport,Mar-Oct,AAH,EDKA,"Aachen, North Rhine-Westphalia, Germany",UTC+01:00,50.776351,6.083862
Arraias Airport,,AAI,SWRA,"Arraias, Tocantins, Brazil",UTC?03:00,-12.9287788,-46.9437231
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
188
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваша функция масштабирования выполняет две функции: она изменяет проекцию и обновляет пути, используя измененную проекцию:

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale); // modify the projection
  g.selectAll("path").attr("d", path);   // update the paths
}

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

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale); // modify the projection
  g.selectAll("path").attr("d", path);   // update the paths

  // update the circles/points:
  svg.selectAll("circle")
    .attr("cx", function (d) { return projection(d)[0]; })
    .attr("cy", function (d) { return projection(d)[1]; })
  });
}

Однако это не совсем работает, нам нужно посмотреть, как вы добавляете круги:

    svg.selectAll("circle")
        .data(new_array)
        .enter()
        .append("circle")
        .attr("cx", function (d) { return projection(d)[0]; })
        .attr("cy", function (d) { return projection(d)[1]; })

Это замечательно, если в svg уже нет кружка, но он есть, вы добавили его сюда:

var circle = svg.append("circle");

Это означает, что первый аэропорт в массиве не будет добавлен, поскольку в svg уже есть кружок для этого элемента в массиве данных. Нулевой выбор (d3.selectAll (null)) гарантирует, что элемент будет введен для каждого элемента в массиве данных.

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

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

Это выглядит так:

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale);
  g.selectAll("path").attr("d", path);

 svg.selectAll(".airport")
   .attr("cx", function (d) { return projection(d)[0]; })
   .attr("cy", function (d) { return projection(d)[1]; }) 
}

При входе:

    svg.selectAll()   // selectAll() is equivilant to selectAll(null)
        .data(new_array)
        .enter()
        .append("circle")
        .attr("class","airport")
        .attr("cx", function (d) { return projection(d)[0]; })
        .attr("cy", function (d) { return projection(d)[1]; })
        .attr("r", "6px")
        .on("mouseover", tip.show)
        .on("mouseout", tip.hide);
    });

«TypeError: Невозможно прочесть свойство 0 из undefined» Как данные, используемые для генерации моих очков, читаются вне функции d3.csv?

DCUFan7 01.05.2018 20:58

Ах, я не видел, чтобы вы добавляли лишний круг: svg.append("circle");, у него нет привязки данных. При масштабировании используются данные, привязанные к точкам (так же, как для путей, когда он их перерисовывает), я отредактирую. Хотя, если вы можете предоставить образец своих данных, я могу провести надлежащую демонстрацию.

Andrew Reid 01.05.2018 21:23

Образец добавлен к основному вопросу

DCUFan7 01.05.2018 21:26

Спасибо, я надеюсь, что в обновленном ответе (с пример) мне ясно - еще рано или что-то еще, чтобы ясно говорить за меня.

Andrew Reid 01.05.2018 21:43

Это настоящая демонстрация! Спасибо, я еще немного прочитаю добавление элементов, а также добавление и управление атрибутами класса.

DCUFan7 02.05.2018 14:23

Другие вопросы по теме