Блог о программировании

Введение в D3js для чайников

Категория: Javascript
 26 апреля 2016 г. 19:35

Перевод статьи

За прошедшие 5 лет в Web’е, фактически, прошла революция в области визуализации данных. Майком Бостоком была создана библиотека D3js, пропагандирующая новый, революционный, подход к построению различных визуализаций. Как ни странно, идея проста: связать визуализируемые данные с конкретными DOM-элементами на HTML-странице. Содержание данной статьи должно помочь читателю понять данный подход до того, чтобы можно было самостоятельно использовать его в построении собственных визуализаций.


От данных к их визуализации

Визуализация данных преобразовывает количественные данные в графические образы. В самом общем виде набор данных представляет собой последовательность чисел. Каждое число в последовательности соответствует какому-то графическому элементу, например, столбику в столбиковой диаграмме, или точке в системе координат или область на карте. Если мы берем подходящий графический атрибут для отображения набора данных(высота столбика, координата точки, цвет области на карте), значение числа из последовательности данных определяет вид атрибута графического элемента, с которым это число связано.

Это наш набор данных - просто числа

Это наш набор данных - просто числа


В качестве примера взглянем на набор данных, представленных целыми положительными числами. Мы можем преобразовать этот набор данных в столбиковую диаграмму из 4х столбиков и выбрать высоту столбика как атрибут графического элемента, который представляет значение из набора данных:

Каждому значению из набора данных соответствует собственный столбик в диаграмме

Каждому значению из набора данных соответствует собственный столбик в диаграмме

В зависимости от характеристики данных, мы можем выбирать более подходящий способ отображения для этого набора; например, если данные представлены последовательным во времени набором значений, мы можем отобразить этот набор в виде простого графика. У нас же в примере каждое значение из набора данных представляет собой высоту соответствующего столбика на диаграмме. Если бы каждое значение из нашего набора данных было бы связано с какой-либо страной, то можно было бы отобразить карту мира, и закрасить эти страны; в таком случае каждое значение из набора данных можно было бы отобразить цветовой насыщенностью конкретной страны. Хоть и приведены совершенно разные примеры визуализаций, они все основываются на одном общем принципе: значения из набора данных представляются каким-то визуальным способом, таким образом мы наглядно видим разницу в данных. В этом, в общем-то, и основная суть революционного подхода к построению визуализаций. Ну а нам, перед тем как отображать данные, необходимо их для начала выбрать.


Выборка данных

Также как и в JQuery, D3 позволяет делать выборки DOM-элементов, используя CSS-селекторы, идентификаторы, классы и атрибуты элементов. Результатом выборки является массив, содержащие подходящие DOM-элементы. Предположим, у нас на странице есть столбиковая диаграмма со следующей разметкой и стилями:

.bar {
  float: left;	
  width: 30px;	
  margin-right: 20px;	
  background-color: #F4F5F7;	
  border: 1px solid #C5C5C5;
}

Используя метод select(), мы выбираем один DOM-элемент:

// Выбираем первый элемент-потомок из #chartvar 
selection = d3.select("#chart")
              .select(".bar");

Если же надо выбрать множество DOM-элементов, удовлетворяющих определенному условию, то нужно использовать метод selectAll():

// Выбираем все .bar элементы-потомки из #chartvar 
selection = d3.select("#chart")
              .selectAll(".bar");

Теперь, когда у нас выбраны элементы, можно, применяя различные методы, производить с ними различные манипуляции: менять значения атрибутов, устанавливать новые стили и многое другое. В нашем же примере мы выбрали несколько блоков, которые должны быть столбиковой диаграммой. Поэтому мы зададим высоту каждому элементу, устанавливая CSS-свойство height:

// Выставляем высоту каждого столбика в 40 пикселей
selection.style("height", "40px");

Это работает. И самое замечательно то, что не приходится городить цикл по перебору каждого элемента в нашей выборке чтобы произвести какие-либо действия. D3 это все делает за нас - мы вызываем метод изменения CSS-стилей, а D3 внутри себя производит необходимые изменения к каждому элементу выборки. Но все-таки это скучно - устанавливать статическое значение высоты каждому столбику. Ведь мы их еще не связали с данными!


Привязка данных

Самый простой набор данных в программе - массив значений:

var numbers = [15, 8, 42, 4];

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

// Выбираем все столбики в диаграмме
var bars = d3.select("#chart")
             .selectAll(".bar");

// Связываем столбики из диаграммы с набором данных
bars.data(numbers);

И вот тут-то у нас и происходит первое проявление магии в D3: используя метод data() к нашей выборки элементов, и передавая туда массив значений, мы каждому DOM-элементу из выборки привязываем конкретное значение из массива значений: первому элементу из выборки первое значение из массива, второму - второе и т.д. Если вы хотите убедиться, что это произошло, просто откройте консоль разработчика в браузере и проинспектируйте любой элемент из выборки, вы увидите, что у каждого элемента из выборки появилось новое свойство __data__, которое и хранит привязанное к нему значение.

Как и при работе с jQuery, в D3 широко используется технология цепочек вызовов методов, тем самым позволяя пример выше переписать в одну строку кода:

d3.selectAll(".bar").data(numbers);

Теперь, если мы применим метод изменения CSS-свойств у выборки, то сможем задать высоту элемента в зависимости от значения, которое мы привязали к этому элементу:

d3.select("#chart")
  .selectAll(".bar")
  .data(numbers)  
  .style("height", function(d){     
      return d + "px";   
  });

Как и прежде, для того, чтобы задать высоту элементам, мы используем метод style(), но статическое значение заменили функцией, возвращающей значение высоты элемента. В функцию в качестве аргумента передается привязанное значение к элементу.

Если сейчас запустить пример, то мы увидим, что столбики строятся сверху вниз, а поскольку мы хотим, чтобы они строились снизу, то нужно задать необходимый отступ сверху опять-таки, применив метод style():

d3.select(“#chart”)
	.selectAll(“.bar”)	
  .data(numbers)	
  .style(“height”, function(d){
  		return d + "px";	
  })	
  .style("margin-top", function(d){ 		
      return (100 - d) + "px"; 	
  });

Функции доступа в сочетании с методами изменения CSS-стилей и свойство элемента предоставляют нам практически неограниченный спектр возможностей для построения различных визуализации по наборам данных.

А сейчас познакомимся поближе с тем, как нам хранить визуализации актуальными, даже если значения в наборе данных изменились.


Вход, обновление, выход

В примере выше мы создали простую визуализацию путем создания связи между набором данных и набором DOM-элементов одинакового количества. Но что будет, если у нас данных в наборе больше, чем количество DOM-элементов? Или что, если мы поменяем количество данных в наборе "на лету"? И вот тут-то вступает в игру вторая часть "магии" D3: методы enter() и exit().


Обновление выборки

Путем применения метода data() к нашей выборке мы связали значения из набора данных с DOM-элементами из выборки:

var selection = d3.select("#chart")	
                  .selectAll(".bar")	
                  .data(numbers);

Полученная выборка называется выборка-обновление:

var numbers = [15, 8, 42, 4];

function update() {	
  // Update selection: Resize and position existing 	
  // DOM elements with data bound to them.	
  var selection = d3.select("#chart")		
                    .selectAll(".bar")		
                    .data(numbers)		
                    .style("height", function(d){ 			
                        return d + "px"; 		
                    })		
                    .style("margin-top", function(d){ 			
                        return (100 - d) + "px"; 		
                    });
};

update();

Когда вы применяете метод data() к вашей выборке - возвращается выборка, но уже с привязанными значениями, поэтому мы можем сразу же использовать метод style() для установления позиции элементов и задания их высоты.

А теперь кое-что интересное: когда мы привязываем данные к элементам, возвращаемая выборка также хранит две ссылки на подвыборки, так-называемые входная выборка и выходная выборка. Эти две подвыборки, возможно, представляю собой наиболее важные аспекты D3: они позволяют нам производить динамические изменения в наборе привязанных данных.


Входная выборка

Рассмотрим следующее изменение нашего набора данных: мы добавим 5е значение в наш массив данных, но в нашей столбиковой диаграмме #chart отображаются все еще 4 столбика:

var numbers = [15, 8, 42, 4, 32];

Когда в нашем наборе данных находится больше значений, нежели в выборке, с которой этот набор данных связан, излишки значений из набора данных(которые не были связаны ни с одним DOM-элементом), хранятся в подмножестве выборки, которая называется входная выборка.

Представим нашу визуализацию комнатой, в которой находится определенное количество стульев(наши DOM-элементы) и определенное количество гостей(наши данные), сидящих на стульях(то есть значения, связанные с DOM-элементами). Входной выборкой в нашей аналогии тогда будут гости, которые вошли в комнату, но не могут сесть, поскольку не хватает стульев. Принести необходимое количество стульев в комнату для стоячих гостей(что означает, создать необходимое количество div.bar DOM-элементов для лишних значений в наборе данных) - это и есть та работа, которую можно переложить на D3.

Пятый элемент в набор данных уже добавлено, но у нас пока четыре столбика в диаграмме

Пятый элемент в набор данных уже добавлено, но у нас пока четыре столбика в диаграмме

Получить доступ к входной выборке мы можем, применив метод enter() к нашей текущей выборке:

var enterSelection = selection.enter();

Теперь, когда у нас есть входная выборка, мы можем использовать методы D3 по созданию необходимых DOM-элементов, добавлению их в DOM-дерево, устанавливать им соответствующий класс(".bar"), задавать высоту и вертикальное позиционирование на основании привязанного значения, используя функцию-акцессор, как мы делали раньше:


selection.enter()	
         .append("div")	
         .attr("class", "bar")	
         .style("height", function(d){ 		
              return d + "px"; 	
          })	
          .style("margin-top", function(d){ 		
              return (100 — d) + "px"; 	
          });

Теперь наша функция update() выполняет решает две разные задачи:

1) Для тех значений из набора данных, которые уже привязаны к своим DOM-элементам происходит обновление высоты и отступа от верхнего края;

2) Для тех значений из набора данных, которые еще не были привязаны к DOM-элементам(входная выборка) - создаются DOM-элементы(".bar"), добавляются к графику, выставляется высота и отступ сверху.

Код функции update() получился таким:

var numbers = [15, 8, 42, 4, 32];

function update() {	
    var selection = d3.select("#chart")		
                      .selectAll(".bar")		
                      .data(numbers);	

    // Enter selection: Create new DOM elements for added 	
    // data items, resize and position them.	
    selection.enter()		
             .append("div")		
             .attr("class", "bar")		
             .style("height", function(d){ 			
                  return d + "px"; 		
             })		
             .style("margin-top", function(d){ 			
                  return (100 - d) + "px"; 		
             });
};

update();

Как результат, в нашей столбиковой диаграмме теперь присутствуют пять столбиков:

Выходная выборка


Выходная выборка

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

Сделаем так, чтобы при клике на столбик диаграммы - он удалялся из графика + удалялось значение из набора данных. Для этого нам нужно добавить слушателя клика мыши на каждый DOM-элемент, представляющий столбик:


selection.enter()	
         .append(“div”)	
         .attr(“class”, “bar”)	
         .style(“height”, function(d){ 		
              return d + "px"; 	
         })	
         .style(“margin-top”, function(d){ 		
              return (100 — d) + "px"; 	
         })	
         .on(“click”, function(e, i){		
              numbers.splice(i, 1);		
              update();	
         });

Теперь при клике на столбик, когда мы вызовем нашу update() функцию, D3 уведомит, что привязанное значение к третьему столбику диаграммы удалено и добавит соответствующий DOM-элемент(по которому мы кликнули) в выходную выборку:

Значение 42 удалено из набора данных; теперь он находится в выходной выборке

Значение 42 удалено из набора данных; теперь он находится в выходной выборке

Методы, которые применяются к выходной выборке, позволяют "почистить" ненужные DOM-элементы:

selection.exit().remove();

В коде выше мы используем метод remove(), подходящий метод для удаления ненужных DOM-элементов. Этот метод применяется к выходящей выборке, то есть только к тем DOM-элементам, привязанные значения которых были удалены из набора данных.

Теперь наша столбиковая диаграмма снова содержит 4 столбика - ровно столько находится значений в наборе данных.

График снова обновлен!

График снова обновлен!


Резюмируя

Наша функция update() теперь выполняет три разных действия, в зависимости от типа выборки, над которой совершаются действия: входная выборка, общая выборка, выходная выборка.

Рабочий пример тут.


И что же дальше?

D3, безусловно, не ограничивается работой со столбиковыми диаграммами. Совсем наоборот, применение библиотеки настолько широко, и при желании, не ограничивается одними визуализациями.

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

Если же вы хотите посмотреть, на что способна данная библиотека, посетите github wiki с примерами. Для дальнейшего изучения можно воспользоваться набором туториалов, руководствоваться документацией.

Удачных визуализаций!

Теги:  d3js  визуализация 

Поделиться статьей

Оставить комментарий