D3, сокращенно от Data-Driven Documents, — это библиотека JavaScript, которая позволяет отображать данные самыми разными способами и содействует проверке и управлению элементами DOM.
D3 не относится к разряду типичных библиотек для построения графиков и диаграмм. Она обладает намного большей гибкостью и усилена возможностями Recharts, C3js и Raw graphs. Ее преимущество очевидно: там, где обычная библиотека терпит фиаско, когда вы слишком увлекаетесь индивидуальными настройками, D3 продолжает уверенно работать
В статье мы познакомимся с D3 на основе конкретных примеров. Общий процесс сводится к следующим этапам: загрузка данных — выборка элементов — привязка данных — создание/изменение/ удаление элементов.
Но прежде немного порисуем на веб-странице и попрактикуемся в применении селекторов и фигур разных цветов.
Рисование базовых фигур
Как же конкретно рисовать с D3? Прежде всего создаем HTML-страницу, содержащую пустой элемент SVG (аббревиатура расшифровывается как язык разметки масштабируемой векторной графики). Для тех кто не знает, SVG — это изображение, состоящее из основных геометрических фигур, благодаря чему оно становится хорошо масштабируемым. Как раз для рисования таких фигур и понадобится D3.
Сначала для импорта D3 создаем HTML-документ, с который мы будем работать на протяжении всей статьи. Редактироваться будет только код между выделенными тегами скрипта.
В строке 4 импортируем D3. Если у вас frontend-фреймворк с Node.js, то вы можете использовать NPM. В строке 7 определяем элемент SVG, в котором будем рисовать фигуры.
Напишем первые строки кода D3:
В строке 2 задействуем метод d3.select()
. Он будет искать первый элемент с CSS-селектором, который вы передали в функцию. В рассматриваемом примере на странице только один тег <svg>
, поэтому он вернет единственно имеющийся элемент SVG.
Позже обратимся к другому методу, а именно d3.selectAll()
, который вернет не один, а все элементы, соответствующие CSS-селектору.
В строке 4 к элементу SVG добавляем “circle” (круг). Помимо него в нашем распоряжении есть еще множество разных фигур. В строке 5 применяем цепочку методов D3, что позволяет поочередно вызывать несколько функций и избавляет от необходимости писать svg.function(...)
в каждой отдельной строке.
В строках 5, 6, 7 и 8 устанавливаем атрибуты с помощью функции d3.attr
, которые потребуются для визуализации фигуры. cx
и cy
— координаты x и y центра круга; r
— радиус; fill
— цвет заливки. Это фиксированные атрибуты. С примерами их использования для основных геометрических форм вы можете ознакомиться на freeCopeCamp.
Итак, в браузере увидим следующий результат:
Кто-то может подумать, что здесь нет ничего особенного. Однако позволю себе не согласиться. Даже самые сложные графические изображения складываются из нескольких простых фигур.
Теперь дополним изображение квадратом:
Добавляем фигуру rect
и устанавливаем нужные атрибуты для ее отображения. Затем выполняем код и получаем следующий результат:
С основными принципами добавления фигур разобрались, переходим к вопросу загрузки данных и графическим изображениям на их основе.
Загрузка данных
Для получения разных типов данных в D3 существует множество функций. Начнем с самых интересных: d3.json('...')
, d3.csv('...')
и d3.xml('...')
.
Для вышеуказанной цели все из них предусматривают применение Fetch
. CSV, в отличие от JSON и XML, не допускает вложенных объектов.
В последующих разделах мы будем работать с файлом данных (socialnetwork.json
) из вымышленной социальной сети и стилями, представленными ниже:
Сначала построим гистограмму для библиотеки. По горизонтали отобразим годы (years), а по вертикали — количество комментариев (comments). Для этой цели воспользуемся соответственно d3.axisBottom()
и d3.axisLeft()
. В коде загрузка данных и рисование осей происходят следующим образом:
Как много кода! Без паники, рассмотрим каждую строку по отдельности.
В строке 3 получаем JSON. Поскольку d3.json()
возвращает промис, нам не обойтись без .then()
или await
, чтобы дождаться окончания процедуры. Как только это происходит, в строках 5 и 10 приступаем к созданию функций масштабирования.
Функции масштабирования
Функции масштабирования играют важную роль в отрисовке данных. Допустим, мы рассматриваем временной промежуток с 2015 по 2020 год при разрешении 500 х 500 пикселей. При отрисовке номера года на оси x необходимо сопоставить 2015–2020 в 0–500.
Самое время для функций масштабирования. Одна из них, .scaleLinear()
, линейно преобразует один диапазон чисел (domain) в другой (range). В строке 7 мы передаем этой функции диапазон (range)[100,800]
, поскольку нам нужно начать отрисовку с 100-го пикселя по оси x, а закончить на 800-м.
После этого происходит передача диапазона входных значений (domain), которые надлежит преобразовать. .domain()
ожидает массив с минимальным и максимальным значениями. К счастью, у D3 есть функции d3.extent()
, d3.max()
и d3.min()
. В строке 8 устанавливаем минимальное значение minimum of years — 1
и максимальное maximum of years + 1
. Это позволит в дальнейшем отцентрировать столбцы гистограммы.
В строке 13 делаем немного по-другому, поскольку ось должна начинаться с 0, а заканчиваться максимальным числом комментариев. Наша задача — отобразить диапазон от 0 до 150, а не от 50 до 150.
d3.extent()
вернет массив из максимального и минимального значений [max, min]
, избавляя от необходимости вызывать их по отдельности.
Приступаем к рисованию оси: в строке 15 добавляем ось x. В целях стилизации присваиваем ей класс и затем перемещаем элемент g, поскольку ось x всегда рисуется из начала координат, расположенного в верхнем левом углу. Поэтому сдвигаем g
на горизонталь, после чего выполняем .call(d3.axisBottom(x).tickFormat(d3.format("d"))
. Функция .axisBottom
рисует ось. (Обратите внимание, что мы передали ей функцию range
, поэтому она знает, в каком диапазоне range/domain нужно выполняться). Затем дополнительная функция .tickFormat()
сообщает D3, как отображать деления на оси. В данном случае используем "d"
для обозначения простых чисел в интервале от 2014 до 2021.
Далее практически то же самое мы делаем для отрисовки оси y, только меняем атрибут transform
, поскольку необходимо переместить эту ось горизонтально для отображения делений.
Полученный результат:
Выборка элементов и привязка данных
На этом этапе нужно нарисовать прямоугольники для каждого года в зависимости от числа комментариев. С этой целью проведем выборку SVG и привязку данных. Пишем следующий код:
Сначала воспользуемся функцией d3.select()
для получения SVG. (Обратите внимание, что в предыдущих фрагментах кода мы ее сохранили в переменной svg
). Затем с помощью селектора CSS selector .barchartrectangle
проводим поиск и отбираем все существующие прямоугольники гистограммы. Это связано с процессами обновления: если массив изменится, мы будем выбирать имеющиеся прямоугольники для их корректировки либо удаления.
Далее вызываем функцию .data()
, которая содержит один обязательный аргумент, представляющий data
, которые нужно отрисовать. При этом второй аргумент является необязательным и определяет ключ. Для этого мы передаем ему функцию, принимающую элемент (item) и индекс (index). В данном примере возвращается индекс.
Однако, как мы увидим в следующем разделе, возможно применение поля элемента для выявления тех элементов, которые подлежат изменению/удалению/вставке. При наличии свойства id
в датасете можно написать что-то подобное (item, index)=> item.id
.
Итак, привязывать данные мы научились, переходим к добавлению прямоугольников.
Создание, изменение и удаление элементов
Когда происходит изменение данных в массиве (например, путем фильтрации), D3 сравнивает массивы и решает, что делать.
Здесь мы рассмотрим только 2 наиболее важные функции: добавление .enter()
и удаление .exit()
.
Понять логику поможет следующий код:
Рассмотрим использование этого массива в качестве данных [1,2,3]
при первом выполнении кода.
Селектор .barchartrectangle
вернет 0 элементов, поэтому D3 в курсе, что элементы в массиве являются тремя новыми фрагментами данных. Следовательно, она задействует функции, измененные на инструкцию .enter()
. В ней мы присоединяем новые rect
каждому новому фрагменту данных и присваиваем им класс barchartrectangle
. Поскольку “удалений” нет, инструкция .exit()
будет проигнорирована.
Теперь при изменении массива на [1,2] и повторном выполнении кода селектор .barchartrectangle
вернет три существующих элемента. Однако D3 отметит наличие в массиве только двух. Тогда она воспользуется функциями, связанными с инструкцией .exit()
, и в этом случае удалит .remove()
третий из них.
Как D3 понимает, какой элемент следует удалять? Это зависит от ключа данных, определенного ранее. В нашем примере им по-прежнему является индекс, поэтому он удалит третий добавленный элемент. Но если элемент оказался фактическим id (item, index)=> item
, и мы изменили массив на [1,3], то вместо третьего будет удален второй barchartrectangle
.
Работа инструкций .enter()
и .exit()
теперь понятна, переходим к отображению гистограммы.
В строке 4 применяем .enter()
и тем самым инструктируем D3, что делать с данными. В строке 5 добавляем новый rect
и устанавливаем необходимые атрибуты. Как видно в строках 6 и 7, функция .attr()
принимает не только фиксированные значения, но и функцию, которая использует элемент и его индекс, что позволяет отрисовывать элементы в зависимости от данных.
Сначала в строке 6 получаем масштабированное положение x путем вызова функции масштабирования x, определенной ранее с номером года. В ответ вернется значение пикселя, на котором нужно разместить гистограмму. Поскольку далее ширина будет установлена как 100, то мы подстраиваем столбец к центральной точке этого значения, выполняя -50
. Для координаты y просто вызываем функцию масштабирования. В строке 9 для определения высоты выполняем 800- y(item.comments)
из-за обратной функции масштабирования. Напомню, что диапазон был определен как [800, 100]
, поэтому малые значения комментариев будут отображаться в большие значения пикселей. В связи с этим мы обращаем эту функцию, выполняя 800 — y(item.comments)
.
Наконец, для привлекательности задаем синий цвет заливки. Итоговый результат:
Отлично! На основе данных мы отрисовали на экране гистограмму. В этом и состоит вся суть D3. Но у нее намного больше возможностей. На D3.js — Data-Driven Documents (d3js.org) вы можете ознакомиться с интересными графиками и фигурами.
Заключение
Теперь у вас есть общее представление о визуализации посредством библиотеки D3. Вы можете рисовать фигуры в SVG на основе динамически генерируемых данных.
А впереди еще много интересного: геокартирование, управление HTML, визуализация сетевых процессов и т.д. D3 ценна прежде всего тем, что предоставляет почти безграничные возможности, которыми вы обязательно должны воспользоваться.
Читайте также:
- notebookJS: JavaScript и D3 в Jupyter Notebook
- Почему я больше не пользуюсь D3.js
- ТОП-25 библиотек React 2021–2022: новые, полезные, но малоизвестные пакеты JavaScript
Читайте нас в Telegram, VK и Дзен
Перевод статьи Diederik Mathijs: A Quick Look at D3.js