Введение в d3.js
Что такое d3.js?
Зрение обеспечивает нас более 90% от общего объема информации, которую получаем, именно поэтому самый эффективный способ сделать анализ большого объёма данных — представить их визуально. Пытаясь на практике найти оптимальный способ представления информации, я наткнулся на библиотеку d3.js и понял, что именно на её основе можно в полной мере воплотить мои идеи.
Название d3 расшифровывается как data driven document. Это JavaScript библиотека, ориентированная на работу с данными и их визуальное представление для веба, включая загрузку данных, визуализацию в режиме реального времени и множество других возможностей. Здесь можно найти много вдохновляющих примеров практического применения d3.js.
В чём сложность?
Множество библиотек, основанных на d3.js, предоставляет пользователю готовый набор функциональных возможностей, без необходимости вникать во все технические тонкости визуализации. Эти библиотеки ограничиваются стандартными графиками и диаграммами, в то время как возможности d3.js ограничены только вашим воображением и техническими возможностями платформы, что даёт возможность сосредоточиться на самих данных и создавать визуализации любой сложности.
В основе d3.js лежат такие веб-стандарты:
- HTML
- CSS
- JS
- SVG
- DOM
При изучении d3.js желательно в должной мере владеть этими технологиями. Это основная проблема при изучении d3.js — порог входа, как показывает практика, довольно высок.
Изучение d3.js позволит углубить и систематизировать знания и представления о веб-стандартах.
С чего начать?
Один из самых важных для понимания работы d3.js аспектов — присоединение данных к элементам (data join).
Рассмотрим все аспекты присоединения данных на примере:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="application/javascript" src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<script type="application/javascript">
d3.select('body').selectAll('p')
.data([16, 23, 42])
.enter()
.append('p')
.text("Hello");
</script>
</body>
</html>
Рассмотрим пошагово интересующий нас фрагмент кода:
d3.select('body').selectAll('p')
.data([16, 23, 42])
.enter()
.append('p')
.text("Hello");
В d3.js мы пишем, вызывая всё последовательно цепочкой методов (так называемый chain syntax).
-
d3.select('body').selectAll('p')
— этой командой мы сделали пустую выборку параграфов (элементp
). Такой подход d3 позволяет не думать есть ли у нас эти элементы или нет, а просто сказать чего мы хотим. Это одно из главных достоинств d3. -
.data([16, 23, 42])
—data()
всегда принимает массив объектов и объявляет отношение между выборкой и данными. Другими словами, пустая выборка связывается с массивом данных. Результатомdata()
является резервирование d3 мест под данные (кол-во новых мест равно кол-ву мест под новые данные), а так же data() возвращает три состояния выборки (enter, update, exit).
-
.enter()
— вернёт зарезервированные новые места для элементов с новыми, уже привязанными, данными (данные пишутся в__data__
) и вернёт ссылку на них (это наша новая выборка зарезервированных элементов с привязанными к ним данными).[{__data__: 16}, {__data__: 23}, {__data__: 42}]
-
.append()
— для каждого несуществующего элемента создаётся элемент и добавляется в dom на ранее зарезервированные d3 места.
.text("Hello")
— добавление текста в каждый элемент.
Всё это, при детальном рассмотрении даёт широкие возможности в плане визуализации данных в реальном времени с плавными переходами между одних данных к другим, также, что немаловажно в процессе визуализации — возможность интерактивного взаимодействия пользователя с визуализированными данными.
А где, собственно, сами данные?
Как я указал ранее, данные хранятся в атрибуте "__data__"
элемента. Обратиться
к нему можно через анонимную функцию. Например, вместо текста “Hello” можно
вставить наши данные:
.text(function(d) { return d; })
Анонимная функция вторым параметром может принимать текущий индекс элемента, а
так же внутри себя имеет объект this
, который ссылается на текущий элемент
(в данном случае — <p>
).
.text(function(d, i) {
console.log(this);
console.log(i, "-", d);
return (d + 2 * i);
})
Различные варианты привязки данных
Есть 2 способа привязки: по индексу и по ключу.
Рассмотрим привязку данных по индексу
Для этого сделаем новую привязку с другими данными. Новую привязку мы выделим в отдельную переменную:
var p = d3.select('body').selectAll('p')
.data([45, 16, 87, 108]);
Теперь в переменной p
к каждому существующему элементу по его индексу было
переприсвоено новое значение (первое значение записано в массив под индексом 0,
второй под индексом 1 и т.д.) и зарезервировано место для нового элемента.
Так же в переменной p
хранятся три состояния выборки (enter, update, exit).
В выборку enter()
ушёл один элемент. Добавим его.
p.enter().append('p');
Теперь у нас есть новые данные для каждого параграфа. Осталось используя их обновить содержимое параграфов:
.text(function(d) { return d; });
Старые данные ушли в выборку exit()
и их можно удалить:
p.exit().remove();
Теперь рассмотрим привязку данных по ключу
Когда вторым аргументом data()
является анонимная функция, то привязка
происходит по ключу:
var p = d3.select('body').selectAll('p')
.data([45, 16, 87, 108], function(d) { return d; });
В данном случае мы возвращаем само значение. Важно понимать, что совпадающие по значению объекты игнорируются, например, если в данных уже есть объект 16, то в новых данных при добавлении он не будет учитываться.
Как правило, данные менее тривиальны. Рассмотрим более сложный пример с привязкой по ключу:
var data1 = [
{"name": "Ringo", "height": 175},
{"name": "Paul", "height": 190}
];
var data2 = [
{"name": "Paul", "height": 190},
{"name": "Sam", "height": 170}
];
d3.select('body').selectAll('p')
.data(data1)
.enter()
.append('p')
.text(function(d) { return d.height; });
var p = d3.select('body').selectAll('p')
.data(data2, function(d) { return d.name; });
p.enter().append('p');
p.exit().remove();
p.text(function(d) { return d.height; });
Привязка происходит по уникальному ключу (в данном случае по ключу "name"
).
var p = d3.select('body').selectAll('p')
.data(data2, function(d) { return d.name; });
Сейчас у нас в exit выборке один элемент на удаление и в enter выборке один элемент на добавление.
Ещё раз про три состояния enter, update, exit
data()
предоставляет три выборки для реализации отношений между элементами и
данными. Выбор состояния зависит от кол-ва данных и элементов.
Если данных больше чем элементов, то новые элементы попадают в enter()
выборку
для добавления.
В случае, если у нас на руках данных меньше, чем есть сейчас элементов, лишние
элементы попадают в exit()
выборку и удаляются.
Если кол-во данных совпадает с кол-вом элементов, то происходит обновление, данных с последующим удалением старых данных, находившихся в элементах.
Заключение
Вопросы, затронутые в статье, может показаться довольно сложными для понимания. Дополнительную информацию можно прочесть здесь и посмотреть тут. Как всегда, практика — ключ к успеху. Пробуйте всё изложенное своими руками. Будут возникать вопросы — пишите в комментариях.