D3.js для построения диаграмм

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

Сейчас модно рассказывать про Data-Driven Design, так вот, библиотека D3 как раз расшифровывается как Data-Driven Documents, идея которого в связке данных с DOM. Сразу поговорим об ограничениях. D3 не поддерживает старые браузеры, работает на клиенте, так что спрятать оригинальные данные не выйдет. Если данные нужно прятать, то рисуйте картинку или используйте Flash (что не так уж плохо, как звучит). В остальном, D3 позволяет создавать потрясающую интерактивную инфографику. Доступные методы Bundle, Chord, Cluster, Force, Histogram, Pack, Partition, Pie, Stack, Tree, Treemap. Это не все существующие типы графиков, которые могут понадобиться, но этого достаточно для решения большинства задач.

Для начала работы нужно скачать D3 и перекинуть библиотеку в подпапку проекта. Либо использовать легкую версию библиотеки. Теперь самое время читать документацию. Без utf-8 браузер не сможет парсить данные для D3, поэтому убедитесь, что с кодировкой везде все хорошо и вы не потеряли <meta charset="utf-8">.

Структура иерархии папок должна получиться такой:

project-folder/
   d3/
      d3.v3.js
      d3.v3.min.js (optional)
   index.html

Так как мы визуализируем реальные данные, то первый вопрос: «где эти данные брать?». Должен быть JSON или GeoJSON с данными. Но сгодится и CSV файл. Для визуализации привязываем входящие данные к элементам DOM (D3 работает с JS DOM), используя d3.select("body"). Можно использовать  d3.selectall("span"), отличается он тем, что возвращает все совпадения для указанного элемента. Так, первый код вернет лишь один body, второй код вернет все элементы span, даже если их 1000. Если на странице не найдется ни одного элемента <span>, будет возвращено пустое значение. Для загрузки CSV используется код:

d3.csv("data.csv", function(data) {
    console.log(data);
});

d3.csv() это глобальный объект. Пустая функция является функцией обратного вызова, служит для передачи кода в качестве одного из параметров другого кода и срабатывает после загрузки CSV файла в память. Это позволяет быть уверенным, что d3.csv() существует к моменту обратного вызова.

Абсолютно аналогично подгружается и обрабатывается JSON файл.

d3.json("waterfallVelocities.json", function(json) { console.log(json); //Log output to console });

Итак, данные подгружены, осталось их использовать. В D3.js используется подход «fluent interface». Это кода пишется как цепочка методов, где каждый метод вызывается на объекте, который вернул предыдущий метод. В коде ниже видно, что каждый вызов располагается на отдельной строчке. Нужно выбрать определенный набор данных, это можно сделать следующим способом:

var dataset = [ 5, 10, 15, 20, 25 ];
 
			d3.select("body").selectAll("p")
				.data(dataset)
				.enter()
				.append("p")
				.text(function(d) { return d; });

Теперь мы имеем переменные данные, для которых создаем элементы DOM, удаляя и добавляя элементы в зависимости от количества полученных данных, если данные меняются. Самое интересное здесь это .enter(), служит для создания нового элемента, связанного с данными. Если у нас 20 человечков в данных, то будет создано 20 элементов DOM. Следующим этапом .append(«p») берет свежесозданных человечков и добавляет их в P. Функции select(), append(), classes() являются методами объекта selectAll. Результатом выполнения возвращается объект типа select.

Дополнительно важно знать про update(), exit(). Exit() нужен для идентификации и сопоставления и элементами, для которых нет данных, и эти элементы должны быть удалены. Update() нужен для идентификации элементов DOM, для которых уже есть данные.

Получается достаточно стандартный подход: применяем к div класс и меняем в классе свойства CSS. Для этого используется метод selection.attr(). При этом важно понимать разницу между attr() и style(). Последний применяет изменения в CSS напрямую к элементу, а attr() применяет изменения к атрибутам DOM. Это важно понимать, так как CSS штука понятная и учится за 15 минут, но на поиск удобного способа организации CSS уходят годы. На помощь приходит Styled components, или CSS in JS.

Работать с Canvas интересно, но куда интереснее работать с SVG. Мы можем вместо создания div создавать rect, сформировав SVG. Для выбора всех rect в SVG будет использоваться selectall. Добавляем rect в DOM, учитывая что он обязан содержать атрибуты xywidth, и height.  Вместо «left» и «top» для HTML-элементов задаются координаты «x» и «y» для SVG.

Так, код позволяет указать следующие размеры: прямоугольник 150px по высоте, 60px в ширину, отступ слева 60 и 10 отступ сверху. Цвет по умолчанию черный. Разумеется, в дальнейшем понадобится менять эти параметры в зависимости от данных, которые мы будем подгружать.

.attr("height","150")
.attr("width","60")
.attr("x", "60"})
.attr("y","10");

svg.append("rect").attr({“x”:”60px”, “y”:”10px”, "width":"60px", "height":"150px"});

Итак, разберем минимальный пример, основываясь на имеющихся у нас знаниях.

var circles = svgContainer.selectAll("circle") // выбираем все SVG-шные круги
                   .data(circleRadii)  //применяем данные к выбранным кружкам
                   .enter()  //выбираем виртуальные недостающие элементы
                   .append("circle");  //добавляем элементы

See the Pen D3 Max Tsvetkov by Maxim (@Yourscorpion) on CodePen.


Мы нарисовали одинокий векторный квадратик, что довольно скучно. SVG может содержать не только квадратики, но и круги (circle), линии (path) и текст (text). Перейдем к более интересным возможностям, украсив наш график и визуализировав данные. В данном случае мы возьмем не готовые данные, а генерируем их каждый раз с нуля.

See the Pen D3 Max Tsvetkov by Maxim (@Yourscorpion) on CodePen.

Уже не плохо, вполне сгодится в качестве инфографики в веб-издание средней руки. 

Можно возразить, сказав, что такой график можно нарисовать в любой графической программе. В том же Illustrator есть Graph Tool, которым можно нарисовать нечто похожее за 1 минуту. Но кодом можно нарисовать диаграммы, которые в графическом редакторе попросту невозможно изобразить. Например, chord diagram. Основывается на квадратной матрице, показывает отношение между двумя наборами данных. Такая диаграмма может наглядно показать, что 14% пользователей устройств Samsung раньше использовали телефоны от Apple, и 21% владельцев телефонов Apple раньше использовали телефоны Samsung.

See the Pen Сhord diagram test by Maxim (@Yourscorpion) on CodePen.

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

See the Pen Chord Max Tsvetkov by Maxim (@Yourscorpion) on CodePen.