Визуализация данных
Начало работы с картами D3.js
Краткое руководство по созданию интерактивной карты Choropleth с помощью популярной библиотеки Javascript.
Для создания и анимации карт существует множество библиотек Javascript, таких как Leaflet.js и Highcharts. В этой статье я использую очень известную библиотеку Data Driven Documents (D3) (версия 5), которая представляет собой нечто большее, чем простая графическая библиотека.
D3 — это библиотека Javascript, которая позволяет манипулировать документами на основе данных.
В этом уроке я построю картографическую карту, показывающую население каждой страны мира. Я модифицировал исходный код, адаптировав его к D3 v5 и обогатив интерактивностью и аннотациями.
Полный код можно скачать с моего Github Repository.
Настраивать
Во-первых, я создаю базовую HTML-страницу, которая также включает javascript D3:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://d3js.org/d3.v5.min.js"></script> </head> <body> </body> </html>
D3 рисует график или карту внутри объекта SVG (или холста). Таким образом, я должен создать объект SVG в моем HTML-документе, а затем включить файл Javascript (map.js
), который будет содержать код для построения карты.
<body> <svg id="map"></svg> <script src="map.js"></script> </body>
карта.js
Я определяю размер и поля изображения SVG, которое будет содержать карту:
var margin = {top: 20, right: 10, bottom: 40, left: 100}, width = 600 - margin.left - margin.right, height = 400 - margin.top - margin.bottom;
и я получаю объект SVG из HTML, я устанавливаю его высоту и ширину с помощью функции attr()
, применяемой к объекту SVG. Затем я добавляю объект g
(группа) к объекту SVG и применяю преобразование, которое переводит элемент g
слева и сверху, чтобы соблюдать поля.
// The svg var svg = d3.select("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Теперь я строю проекцию. Проекция D3 — это преобразователь, который преобразует географические координаты (в виде долготы, широты) в пиксели для представления в изображении SVG. В D3 существует множество проекций. В этом примере я использую проекцию geoMercator()
. Я задаю масштаб карты и центр, затем перевожу его в середину SVG-объекта.
var projection = d3.geoMercator() .scale(70) .center([0,20]) .translate([width / 2 - margin.left, height / 2]);
Я определяю цветовую шкалу как d3.scaleThreshold()
, где домен находится в диапазоне от 100 до 500 мегапикселей и в диапазоне от темно-розового до светло-розового.
var domain = [100000000, 500000000] var labels = ["< 100 M", "100 M - 500 M", "> 500 M"] var range = ["#F8CAEE","#BF76AF","#852170"] var colorScale = d3.scaleThreshold() .domain(domain) .range(range);
1. Загрузка данных
D3 не поддерживает встроенные карты, поэтому форма карты должна быть загружена из других источников как объект GeoJson. В этом конкретном примере я загружаю данные из двух источников: первый источник содержит карту в формате GeoJSON, а второй — население в формате CSV. Файл населения выглядит следующим образом:
Для каждой страны мира дан ее код и численность населения. Код такой же, как и на карте GeoJson.
Я использую концепцию промисов Javascript, чтобы дождаться, пока все данные будут доступны.
var promises = [] var data = d3.map(); promises.push(d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson")) promises.push(d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world_population.csv", function(d) { data.set(d.code, +d.pop); })) myDataPromises = Promise.all(promises).then(function(my_data) { var topo = my_data[0] // do some stuff });
Обратите внимание, что я сохраняю содержимое CSV-файла во внешней переменной с именем data
. Карта доступна как первый элемент массива my_data
, переданного в качестве входных данных анонимной функции в обещании.
2. Нарисуйте карту
Теперь я готов нарисовать карту. Необработанную карту можно получить из GeoJson, который содержит линии, которые должны быть построены как пути SVG. Таким образом, для каждой функции GeoJSON я строю линию и применяю к ней projection
как атрибут d
. Я раскрашиваю каждую страну с помощью атрибута fill
с цветовой шкалой, применяемой к населению, связанному с этой страной. Я также установил события mouseover
и mouseleave
, которые будут использоваться для интерактивности карты.
svg.append("g") .selectAll("path") .data(topo.features) .enter() .append("path") .attr("class", "topo") // draw each country .attr("d", d3.geoPath() .projection(projection) ) // set the color of each country .attr("fill", function (d) { d.total = data.get(d.id) || 0; return colorScale(d.total); }) .style("opacity", .7) .on("mouseover", mouseOver ) .on("mouseleave", mouseLeave )
3. Интерактивность карты
Я определяю две функции mouseOver()
и mouseLeave()
, которые будут действовать как прослушиватели событий. Первая функция вызывается, когда пользователь проводит мышью по стране. В результате подсвечиваются границы страны, а также отображается население в виде всплывающей подсказки.
Чтобы управлять всплывающей подсказкой, я определяю дополнительный элемент div в исходном HTML-файле:
<body> <div id="tooltip"></div> <svg id="map"></svg> <script src="map.js"></script> </body>
В начале файла map.js
я также строю всплывающую подсказку с opacity = 0
, что означает, что всплывающая подсказка изначально скрыта.
Функция mouseOver()
устанавливает для атрибута opacity
значение 1 и выделяет границы.
let mouseOver = function(d) { d3.select(this) .transition() .duration(200) .style("opacity", 1) .style("stroke", "black")
Кроме того, он устанавливает всплывающую подсказку opacity
в 1. Таким образом, всплывающая подсказка отображается пользователю. Подсказка располагается рядом с мышью: координаты мыши берутся из d3.event.PageX
и d3.event.PageY
. Текст всплывающей подсказки устанавливается на значение населения.
d.total = data.get(d.id) || 0; tooltip .style("opacity", 0.8) .html(d.id + ": " + d3.format(",.2r")(d.total)) .style("left", (d3.event.pageX) + "px") .style("top", (d3.event.pageY - 28) + "px");
Функция mouseLeave()
выполняет обратные операции: она устанавливает границы страны равными transparent
, а непрозрачность всплывающей подсказки равной 0.
d3.selectAll(".topo") .transition() .duration(200) .style("stroke", "transparent") tooltip .style("opacity", 0)
4. Легенда
Рядом с картой рисую легенду. Во-первых, я задаю координаты легенды и добавляю элемент g
к основному элементу SVG. Я перевожу элемент g
определенных координат:
var legend_x = width - margin.left var legend_y = height - 30 svg.append("g") .attr("class", "legend") .attr("transform", "translate(" + legend_x + "," + legend_y+")");
Теперь я создаю объект d3.legendColor()
с ранее определенным colorScale
и рисую его через функцию call()
.
var legend = d3.legendColor() .labels(labels) .title("Population") .scale(colorScale) svg.select(".legend") .call(legend);
5. Аннотации
Наконец, я обогащаю карту некоторыми аннотациями. Я использую библиотеку d3-annotation, которая должна быть включена в заголовок моего HTML-файла:
<script src="https://rawgit.com/susielu/d3-annotation/master/d3-annotation.min.js"></script>
В конце map.js
я могу нарисовать аннотации. Я должен указать добавляемую заметку, цвет шрифта (color
) и координаты (x
и y
).
const annotations = [ { note: { label: "despite its great territorial extension Australia has only 20 million inhabitants.", title: "Australia Population", wrap: 150, // try something smaller to see text split in several lines padding: 10 // More = text lower }, color: ["#852170"], x: projection([150.916672,-31.083332])[0], y: projection([150.916672,-31.083332])[1], dy: -30, dx: 10 } ]
Теперь я могу нарисовать аннотацию, добавив ее к элементу SVG:
// Add annotation to the chart const makeAnnotations = d3.annotation() .annotations(annotations) svg.append("g") .style("opacity", 1) .attr("id", "annotation") .call(makeAnnotations) })
Резюме
В этом уроке я показал, как построить картограмму D3, обогащенную взаимодействиями и аннотациями.
D3.js изначально не поддерживает карты. Однако карты можно загрузить в формате GeoJSON, который можно добавить к объектам SVG.
Если вы хотите быть в курсе моих исследований и другой деятельности, вы можете подписаться на меня в Twitter, Youtube и Github.