Визуализация данных

Начало работы с картами 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.

Статьи по Теме