В качестве упражнения для изучения 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
Ваша функция масштабирования выполняет две функции: она изменяет проекцию и обновляет пути, используя измененную проекцию:
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);
});
Ах, я не видел, чтобы вы добавляли лишний круг: svg.append("circle");
, у него нет привязки данных. При масштабировании используются данные, привязанные к точкам (так же, как для путей, когда он их перерисовывает), я отредактирую. Хотя, если вы можете предоставить образец своих данных, я могу провести надлежащую демонстрацию.
Образец добавлен к основному вопросу
Спасибо, я надеюсь, что в обновленном ответе (с пример) мне ясно - еще рано или что-то еще, чтобы ясно говорить за меня.
Это настоящая демонстрация! Спасибо, я еще немного прочитаю добавление элементов, а также добавление и управление атрибутами класса.
«TypeError: Невозможно прочесть свойство 0 из undefined» Как данные, используемые для генерации моих очков, читаются вне функции d3.csv?