Frontender Magazine

Введение в d3.js

Что такое d3.js?

Зрение обеспечивает нас более 90% от общего объема информации, которую получаем, именно поэтому самый эффективный способ сделать анализ большого объёма данных — представить их визуально. Пытаясь на практике найти оптимальный способ представления информации, я наткнулся на библиотеку d3.js и понял, что именно на её основе можно в полной мере воплотить мои идеи.

Название d3 расшифровывается как data driven document. Это JavaScript библиотека, ориентированная на работу с данными и их визуальное представление для веба, включая загрузку данных, визуализацию в режиме реального времени и множество других возможностей. Здесь можно найти много вдохновляющих примеров практического применения d3.js.

В чём сложность?

Множество библиотек, основанных на d3.js, предоставляет пользователю готовый набор функциональных возможностей, без необходимости вникать во все технические тонкости визуализации. Эти библиотеки ограничиваются стандартными графиками и диаграммами, в то время как возможности d3.js ограничены только вашим воображением и техническими возможностями платформы, что даёт возможность сосредоточиться на самих данных и создавать визуализации любой сложности.

В основе d3.js лежат такие веб-стандарты:

При изучении 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).

Схема

Схема

Схема

Всё это, при детальном рассмотрении даёт широкие возможности в плане визуализации данных в реальном времени с плавными переходами между одних данных к другим, также, что немаловажно в процессе визуализации — возможность интерактивного взаимодействия пользователя с визуализированными данными.

А где, собственно, сами данные?

Как я указал ранее, данные хранятся в атрибуте "__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() выборку и удаляются.

Если кол-во данных совпадает с кол-вом элементов, то происходит обновление, данных с последующим удалением старых данных, находившихся в элементах.

Заключение

Вопросы, затронутые в статье, может показаться довольно сложными для понимания. Дополнительную информацию можно прочесть здесь и посмотреть тут. Как всегда, практика — ключ к успеху. Пробуйте всё изложенное своими руками. Будут возникать вопросы — пишите в комментариях.

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Игорь Ерёмин
Автор:
Игорь Ерёмин
GitHub:
Develer
Twitter:
@igoryeryomin
Сaйт:
http://ai-labs.org/

Комментарии (15 комментариев, если быть точным)

Автар пользователя
joe-skb7

Хотелось бы узнать, в чем отличие от обычного линуксового graphviz (http://en.wikipedia.org/wiki/Graphviz)? Просто наличием JS API? И с помощью каких инструментов d3.js реализует отрисовку (тот же graphviz или что-то своё)? Если своё -- то какие преимущества над graphviz?

Автар пользователя
iamstarkov

@joe-skb7, Самое главное отличие кажется в том, что d3.js можно использовать в вебе, а graphviz нет. Поэтому мне кажется некорректно их сравнивать. Тем не менее d3js в основном использует svg для "отрисовки", если так вообще можно выразиться

Автар пользователя
dchusovitin

@joe-skb7, d3 рисует графики с помощью SVG. Преимущество в том, что работает в браузере =) сотни плагинов, бесконечные возможности. На http://d3js.org/ есть множество демок, или на другой площадке http://bl.ocks.org/mbostock

Автар пользователя
Develer

@joe-skb7, graphviz был создан исключительно для визуализации графов, в то время как d3.js, опираясь на веб стандарты, позволяет делать визуализации любой сложности (что фантация позволит). В d3.js вшита серьезная математика и алгоритмы. Сам понимаешь, что представить всё данные в виде графов просто не возможно, так как будет не читабельно, а основная цель визуализации - это понятное представление любых данных. graphviz совсем не то, с чем нужно сравнивать d3.js )

Автар пользователя
joe-skb7

@matmuchrapna, graphviz тоже можно использовать в вебе: отрисовать png/svg и отобразить. web API у него нет, но препятствий для применения не вижу. Вот пример: http://www.webgraphviz.com/ .

@Develer, ага, понятно: т.е. d3.js более универсален (позволяет визуализировать данные в различном представлении, не только графы, но и графики и т.д.), плюс изначально заточен под применение в web. Надо также посмотреть, что испольует R для визуализации (то ли Gnuplot, то ли что-то другое), т.к. я точно помню, что там визуализация статистических данных весьма неплохая была.

Автар пользователя
iamstarkov

@joe-skb7, что такое web API?

Автар пользователя
joe-skb7

@matmuchrapna, http://en.wikipedia.org/wiki/Web_API . См. "Client-side". Я в частности имел ввиду то, что d3.js предоставляет javascript API, в олтичии от graphviz или gnuplot, которые имеют только консольный интерфейс (ну или программный API на Си). Оба не могут быть органично интегрированы в веб-приложениях (без дополнительной прослойки на сервере), соответственно они не имеют Web API.

Автар пользователя
iamstarkov

Ух, не думал, что мы будем говорить о программах без "web API" aka "client side" aka "front end" на frontender.info

Автар пользователя
Develer

@joe-skb7, да, R крутой инструмент для учёных, которым надо быстро, на коленке показать результаты вычислений и т.д. Понятие "визуализация данных" включает 2 блока: графический дизаин и взаимодействие с пользователем. Подчеркну что графический дизаин имеется в виду технически правильное, строгое представление данных. Есть стандарты, целые теоретические базы под это дело. В общем d3 позволяет в полной мере развернуться на данных и сделать всё как надо, без каких-либо ограничений. d3 довольно мощьный и потому сложный инструмент, но есть множество библиотек построеных на d3 позволяющих сделать какую-то простую задачу не вдаваясь во все тонкости d3.js.

Автар пользователя
Develer

@joe-skb7, кстати не нашел чем рисует R, неужели OpenGL напрямую юзает?

Автар пользователя
joe-skb7

@Develer, да не, у него своя какая-то внутренняя тулза для отрисовки по умолчанию. Но я так понял что многие используют ggplot2 (http://en.wikipedia.org/wiki/Ggplot2). Я сам удивился, Maxima например Gnuplot использует по умолчанию. Очевидно для R не хватает функционала Gnuplot (хотя он вроде как достаточно мощный).

@matmuchrapna, да я даже не обратил внимания на тематику сайта. Меня в первую очередь интересуют тулзы для визуализации, а на чем они написаны -- второй вопрос. Пока что юзал Gnuplot и graphviz, их труднее конечно в web интегрировать, но мне как-то консольный интерфейс привычнее и удобнее -- можно делать автоматизацию на скриптах, опять же. Хотя javascript думаю тоже можно запускать локально на машине, не используя web.

Автар пользователя
denis-zavgorodny

@joe-skb7, да, человечество придумало Node.js :)

Автар пользователя
subzey

@denis-zavgorodny, справедливости ради стоит отметить, что запускать скрипты на локальной машине можно ещё с 1998 года, а на вебсервере — с 2006 :)

Автар пользователя
denis-zavgorodny

@subzey, не могу не согласится, о Script Host не подумалось сразу )

Автар пользователя
madbonez

Спасибо. Более менее все теперь понятно)