Pull to refresh
0
Лаборатория данных
Создаём интерактивные визуализации на D3.js

Введение в D3

Reading time 6 min
Views 139K

D3.js (или просто D3) это JavaScript-библиотека для обработки и визуализации данных. Она предоставляет удобные утилиты для обработки и загрузки массивов данных и создания DOM-элементов. Эта заметка описывает работу с основными методами библиотеки, она подойдёт для изучения основ библиотеки и погружения в её логику и возможности.

Для понимания статьи пригодятся знания JS, HTML и CSS.


Текучий интерфейс (fluent interface)


D3 реализует подход, называемый fluent interface. При чтении кода он выглядит как цепочка методов. Каждый метод вызывается на объекте, который вернул предыдущий метод. Чтобы код было удобно читать, каждый вызов располагается на отдельной строчке:

d3.select('body') // выбор в документе body
    .append('svg') // добавление в body svg-контейнера
    .append('text') // добавление в svg-контейнер элемента text
    .text('Click somewhere, please...') // изменение текста в элементе text
    .attr('x', 50) // задание координат элемента text
    .attr('y', 50)
    .style("fill","firebrick") // заливка текста цветом

Этот пример на jsfiddle.net

Выборка


В D3, как и в других JS-библиотеках, работающих с DOM-элементами, взаимодействие с документом начинается с поиска элементов в документе и создания выборки — обёртки набора элементов. Она даёт доступ к методам библиотеки для модификации выбранных элементов.

Выборка (selection) в D3 создается с помощью методов d3.select() и d3.selectAll(). Для создания выборки D3 использует querySelector/querySelectorAll или Sizzle, если он подключён к странице (например, с jQuery).
d3.select('span') // выбор первого span в документе
d3.selectAll('span') // выбор всех span в документе

Полученную выборку используют для работы с элементами и для создания выборки из потомков (subselection).

<span>будет зеленым</span>
<p>
    <span>будет красным</span>
    <span>эти</span>
    <span>будут</span>
    <span>жёлтыми</span>
</p>
<p>останется чёрным</p>

d3.select('span')                  // выбор первого span в документе
    .style('color', 'darkgreen') // установка цвета

d3.selectAll('p')                   // выбор всех параграфов
    .selectAll('span')             // выбор всех span в этих параграфах.
    .style('color', 'goldenrod') // установка цвета

d3.select('p')                      // выбор первого параграфа в документе
    .select('span')                // выбор первого span в этом параграфе
    .style('color', 'firebrick')  // установка цвета

Этот пример на jsfiddle.net

Всегда помните, с какой выборкой вы сейчас работаете. Распространённые ошибки при работе с D3: вызов на элементе-потомке вместо родителя и попытка изменения свойств несуществующего (удаленного или ещё не созданного) элемента.

В примере уже используются операции над элементами (selection.style(name[, value])), дальше рассмотрим их более подробно.

Вычисление значений и функторы


Для работы с DOM D3 использует схожий API для всех вызовов. Давайте разберём его на примере популярной задачи: добавления или удаления класса у элемента. Для этого нам понадобятся некоторые методы выборок:
  • selection.classed(name, value) добавляет или удаляет класс name в зависимости от булевого значения value.
  • selection.on(event, callback) используется для обработки событий, передавая название события event (например, «click») и функцию-обработчик callback. Функции-обработчики вызываются с текущим элементом в this, а также data и index в аргументах. Событие можно получить в переменной d3.event. Повторная установка обработчика заменит предыдущий.


var pressed = false
var button = d3.select('button')     // выбор кнопки
    .on('click', function (data, index) {        // установка обработчика нажатия мыши
        button.classed('pressed', pressed = !pressed)  // в обработчике меняем значение переменной и вычисляем класс
    })

Этот пример на jsfiddle.net

Обратите внимание, что мы пользуемся сохранённой в переменную button выборкой: вызовы on (а так же classed, attr, style, property, html, text) возвращают выборку, на которой они вызваны, что типично для «текучих» интерфейсов.

D3 обрабатывает переданные значения схожим образом. Если вы видите в документации [value], скорее всего речь идёт о следующем:
  • Если вы подадите значение, являющееся функцией, оно будет вызвано с параметрами data, index (см. ниже), а контекстом (объектом this) будет элемент, DOM-узел.
  • Если вы подадите значение, не являющееся функцией, оно будет обёрнуто в «функтор» (функцию, всегда возвращающую переданное значение)
  • Если вы не подадите значение, функция сработает как getter и вернёт значение, о котором идёт речь (например, selection.style('color') вернёт цвет текста, если он установлен для элемента).


О последнем нюансе важно помнить, если вы строите цепочку вызовов (такой getter обычно должен быть последним элементом цепочки).

Важно понимать, что значения или функции используются один раз для каждого элемента в выборке, после чего D3 о них «забывает». Иными словами, изменения в наборе данных или события в документе не заставят D3 «повторно вычислить» значение, поэтому это поведение нужно задавать самостоятельно, как мы выше сделали с classed.

Обратите внимание на аргументы функции (data и index). Они имеют специальное значение: index — номер элемента в выборке, а data — заданный для него элемент данных. Присутствие этих параметров в каждой функции, вызываемой на выборке является одним из важнейших контрактов в D3. Это позволяет писать лаконичный код, вычисляющий состояния свойств элементов в зависимости от данных.

Типичная работа с выборкой


Рассмотрим популярные методы на более сложном примере, демонстрирующем работу с DOM-узлами документа через выборку:
var svg = d3.select('body').append('svg')

svg
    .append('text')
    .text('click somewhere')
    .attr('x', 50)
    .attr('y', 50)

var events = []
svg.on('click', function () {
    events.push(d3.event)
    if (events.length > 5) events.shift()
    var circles = svg.selectAll('circle')
        .data(events, function (e) { return e.timeStamp })
        .attr('fill', 'gray')
    circles
        .enter()
        .append('circle')
        .attr('cx', function (d) { return d.x || d.pageX })
        .attr('cy', function (d) { return d.y || d.pageY })
        .attr('fill', 'red')
        .attr('r', 10)
    circles
        .exit()
        .remove()
})

Этот пример на jsfiddle.net

  • Методы selection.html() и selection.text() задают или возвращают содержимое элементов в виде HTML или текста.
  • Методы selection.style(), selection.attr() и selection.property() задают или возвращают CSS-свойства элемента, его аттрибуты и свойства. Чаще всего мы будем пользоваться style и attr, особенно при описании свойств новых элементов.
  • Метод selection.remove() удаляет элементы текущей выборки.
  • selection.append() добавляет потомка к каждому элементу текущей выборки.
  • Переданные в selection.data() данные сохраняются в поле __data__ DOM-элемента, при вызове методов на выборке происходит их извлечение из элемента.
  • Получить или записать данные в один элемент можно, используя selection.datum().


Связанные множества


В примере особое внимание стоит обратить на метод data(). В отличие от других методов он возвращает модифицированную выборку, хранящую помимо списка элементов соответствие данных элементам. В переводе статьи Thinking with Joins мы подробно рассказываем о методах enter() и exit() которые есть у такой выборки и возможностях которые они дают.

Анимация и настройка


Анимировать изменение свойств элемента в D3 легко, нужно вызвать метод selection.transition(). Этот метод возвращает выборку, которая постепенно изменяет текущие значения на новые, создавая анимационный эффект. Длительность анимации задаётся методом transition.duration().

Добавим анимацию при добавлении и удалении элементов в предыдущий пример:

var svg = d3.select('body').append('svg')

svg
    .append('text')
    .text('click here')
    .attr('x', 50)
    .attr('y', 50)

var events = []
svg.on('click', function () {
    events.push(d3.event)
    if (events.length > 5) events.shift()
    var circles = svg.selectAll('circle')
        .data(events, function (e) { return e.timeStamp })
        .attr('fill', 'gray')
    circles
        .enter()
        .append('circle')
        .attr('cx', function (d) { return d.x || d.pageX })
        .attr('cy', function (d) { return d.y || d.pageY })
        .attr('fill', 'red')
        .attr('r', 0) // Начальное значение
        .transition()
        .duration(1000) // Длительность перехода от начального значения к конечному
        .attr('r', 10) // Конечное значение
    circles
        .exit()
        .transition()
        .attr('r', 0)
        .remove()
})

Этот пример на jsfiddle.net

В этой статье я рассказал о возможностях D3 по работе с выборками. Следующие заметки планирую посвятить утилитам для обработки и загрузки данных, рисованию наборов SVG-элементов и созданию интерактивных элементов визуализации.

Я преподаю D3 на курсе «Визуализация данных». Если вы хотите освоить этот инструмент и начать применять его в своей работе, приходите к нам. Ближайший курс пройдёт в Москве в эти выходные, запись и отзывы участников январского курса: http://brainwashing.pro/dataviz.
Tags:
Hubs:
+55
Comments 7
Comments Comments 7

Articles

Information

Website
datalaboratory.ru
Registered
Founded
Employees
2–10 employees
Location
Россия