Сейчас все продвинутые парни любят JavaScript и данные. А что любят данные? Обработку и визуализацию. Кстати, последнюю ценят как продвинутые парни, так и их непродвинутые клиенты и тем более начальники. В этой статье мы представим твоему вниманию лучшую в обитаемой части Галактики JS-библиотеку для визуализации данных.

Название библиотеке D3 дано по первым буквам D слов Data-Driven Documents, что можно перевести как «документы, движимые данными». Библиотека D3.js позволяет проводить групповые операции над элементами HTML-документов, применяя к ним данные из массива. Она предназначена для визуализации самой разной информации, и подход, примененный в этой библиотеке, оказался настолько успешным, что она используется в огромном количестве различных инструментов визуализации данных и десятках библиотек JavaScript для построения графиков. На странице проекта можно увидеть действительно красивые, иногда буквально завораживающие примеры самых различных графических представлений данных и получить некоторое представление о возможностях библиотеки.

 

Простейшая диаграмма

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

Создадим пустой HTML-документ:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <style>
    </style>
  </head>
  <body>
    <script>
    </script>
  </body>
</html>

Подгрузим саму библиотеку, добавив внутри тегов строку:

<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>

Динамически добавить элемент <div> с классом chart_area, внутри которого будет размещаться наша диаграмма, на классическом JavaScript можно, к примеру, так (не гонясь за совместимостью):

var div = document.createElement('div');
div.classList.add('chart_area');
document.body.appendChild(div);

При использовании jQuery эта операция может выглядеть так:

$('body').append( $('<div></div>').addClass('chart_area') );

или так:

$('<div></div>').addClass('chart_area').appendTo('body');

С использованием D3 эта операция будет следующей:

d3.select('body').append('div').classed('chart_area', true);

Пока мы работаем с одним элементом, внешний вид операций с использованием D3 мало отличается от других библиотек. В этой строке сначала была сделана выборка элементов HTML (состоящая из единственного элемента body функции select), затем к каждому элементу выборки (то есть единственному элементу <body>) был добавлен новый дочерний элемент 'div', и выборка стала соответствовать этим новым элементам, затем элементам текущей выборки (по-прежнему из одного элемента) был задан класс chart_area.

В D3 основные операции совершаются именно такими цепочками функций над выборками элементов. При этом функции select(), append(), classes() на самом деле методы объекта selection (выборка), причем в результате своего выполнения они тоже возвращают объект типа selection (выборка), что и позволяет выстраивать их в такие цепочки. Так как при использовании D3 эти цепочки часто получаются достаточно длинными, принято разбивать их на отдельные строки:

var chart_area =
  d3
    .select("body")   // Выборка состоит из элемента <body>
    .append('div')    // Выборка состоит из вновь созданного элемента <div>
    .classed('chart_area', true) // Задаем класс выбранному элементу <div class='chart_area'></div>
  ;

Добавим этот код в тело документа, внутри тегов <script></script>.

Подготовим массив случайных чисел:

var RANDOM_MIN = 0, RANDOM_MAX = 100;
// Функция генерации случайного целого числа в диапазоне [lo..up]
function irand(lo, up) { return Math.floor(Math.random()*(up-lo+1)+lo); }
// Массив случайных чисел
var data = []; for (var i=0; i<10; i++) { data.push(irand(RANDOM_MIN, RANDOM_MAX)); }

А теперь следи за руками:

// Берем предыдущую выборку элементов (хранящуюся в переменной chart_area)
chart_area
  // Делаем выборку всех дочерних элементов <div> из текущей выборки;
  // на данный момент таких элементов нет, и эта выборка пока пуста
  .selectAll('div')
  // Связываем выборку с массивом данных
  .data(data)
  // Из всего множества элементов выделяем подмножество добавляемых элементов 'enter';
  // в данном случае это элементы, соответствующие всем элементам массива
  .enter()
  // Добавляем новые элементы <div> </div>
  .append('div')
  // Задаем класс выбранным элементам class='bar_area'
  .classed('bar_area', true)
;

Функция data() связывает текущую выборку с массивом данных, выбираются элементы, которые необходимо создать функцией enter(), и функцией append() они создаются для каждого элемента данных.

С этого момента у нас создано столько же элементов div.bar_area, сколько элементов данных содержится в массиве data:

...
<div class="chart_area">
  <div class="bar_area"></div>
  <div class="bar_area"></div>
  ...
  <div class="bar_area"></div>
</div>
... 

Пока что эти элементы невидимы. Добавив в цепочку (непосредственно перед завершающим 😉 следующие строки:

.style('background-color', 'hsl(240,50%,75%)')
.style('height', '20px')
.style('margin', '2px 0px')

Теперь мы видим столько же прямоугольников, сколько элементов в нашем массиве исходных данных. Но пока все их свойства заданы константами и они все одинаковые. Чтобы размер прямоугольников соответствовал значениям данных, добавим в цепочку:

// Задаем стиль width='<d>px', где d — значение элемента массива
.style('width', function(d,i) { return d + 'px'; } )
// Задаем строковое значение равным значению элемента массива
.text(String)

Параметры d и i соответствуют значению текущего элемента массива исходных данных и его порядковому номеру. Так можно задавать любые свойства стиля или атрибуты (функцией attr) элементов, причем, как видно в этом примере, свойства элементов могут задаваться динамически, с использованием элементов массива данных: первый параметр функции — элемент массива, второй — его порядковый номер.

Например, если заменить строку с параметром background-color таким образом:

.style('background-color', function(d, i) { return 'hsl(240,50%,'+(100-d/2)+'%)'; })

цвет элемента HTML будет определяться значением связанного с ним элемента данных. Чтобы интенсивность цвета была не слишком высокой, выражением (100-d/2) исходный диапазон значений данных [0..100] приводится к диапазону [50..100].

После всех этих операций каждый прямоугольник диаграммы имеет следующий вид:

<div
  class="bar_area"
  style="height: 20px;
         margin: 2px 0px;
         width: 175px;
         background-color: rgb(123, 123, 211);"
>38</div>

Разумеется, статические свойства элементов можно было задать и обычными стилями, добавив в заголовок <style>:

div.bar_area {
  background-color: hsl(240,50%,75%);
  height: 20px;
  margin: 2px 0px;
}

Линейный график
Линейный график

Выборки enter() и exit()

В примере выше элементы HTML создавались для всех данных массива data. В случае если в уже существующий массив были добавлены новые элементы, а часть старых — удалена, при привязке данных к элементам HTML D3 автоматически определяет несколько подмножеств: элементы документа, которые не существуют, но для которых в массиве есть соответствующие данные (множество enter). Эти элементы, как правило, необходимо добавить функцией append(). Другое подмножество — exit, его элементы существуют, но для них в массиве нет соответствующих данных (множество exit). Элементы этого подмножества, как правило, должны быть удалены функцией remove(). Множеству update соответствуют элементы, которые существуют, для которых есть соответствующие элементы массива, но значения которых могли измениться. В рассмотренном выше примере всем данным массива соответствует подмножество enter (которое выбирается соответствующей функцией) и для них всех создаются элементы функцией append().
Аналогично тому, как были объявлены действия над подмножеством новых элементов enter(), можно объявить действия над подмножеством удаленных элементов exit().
Соответствие устанавливается по функции-ключу, который можно задать вторым параметром в функции data, например в данном случае ключом будет порядковый номер элемента: `data(data, function(d,i) { return i; } ).
Подробнее об этих подмножествах — здесь. Хороший пример визуализации периодически пополняемых данных — тут.

Анимация

Базовая анимация в D3.js реализуется очень просто. Добавим перед строкой, в которой задается ширина элементов

.style('width', function(d,i) { return d + 'px'; } )

следующие строки:

.style('width', 0) // Исходная ширина элемента до начала анимации
.transition()
.duration(750)     // Продолжительность анимации в миллисекундах

Перед тем как задать конечное значение ширины элементов текущей выборки, мы сначала задаем их ширину равной нулю, объявляем анимацию длительностью 750 мс, после которой элемент примет необходимую ширину.
Результатом выполнения будет анимация изменения ширины элемента от нуля до значения, соответствующего элементу массива.

Масштабирование

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

var CHART_WIDTH = 500,
    CHART_HEIGHT = 300;
var widthScale = d3.scale.linear()
  // Объявляем исходный диапазон
  .domain([
    // Определяем минимальное...
    d3.min(data, function(d, i) {``
      return d;
    }),
    // ...и максимальное значения массива данных
    d3.max(data, function(d, i) {
      return d;
    })
  ])
  // Результирующий диапазон — от нуля до ширины диаграммы
  .range([0, CHART_WIDTH])
  // Начало и конец диапазона — «красивые» значения
  .nice();

В этом фрагменте функцией d3.scale.linear() объявляется линейный масштаб. Масштабирование устанавливает соответствие между исходным доменом (input domain) и выходным диапазоном (output range). В D3.js есть два основных типа масштабирования: количественное (quantative, при котором входной домен непрерывен, к ним относятся линейное, логарифмическое, экспоненциальное и другие) и перечислимое (ordinal, при котором входной домен дискретен, например представляет собой имена или категории). Кроме этого, есть временной линейный масштаб (d3.time.scale).

Функцией domain() задается исходный диапазон данных, функцией range — результирующий диапазон данных.

С помощью функций d3.min() и d3.max() определяются минимальное и максимальное значения набора данных.
Функция nice() позволяет расширить начало и конец входного домена до ближайших округленных значений.

Изменим строку, в которой задается ширина строки, заменив d на widthScale(d):

// .style('width', function(d,i) { return d + 'px'; } )
.style('width', function(d,i) { return widthScale(d) + 'px'; } )

Координатные оси

Добавим простейшую координатную ось:

var hAxis_area =
  d3
    .select("body")
    .append('div')
    //.classed('haxis_area', true)
    .style('position', 'absolute')
  ;

У масштабирования есть метод ticks(), позволяющий разбить входной домен на заданное число частей для отображения меток на осях. Количество частей определяется входным параметром; по умолчанию — десять:

var ticks = widthScale.ticks(10);

Теперь ticks — это массив, состоящий из десяти значений, которые мы отобразим на горизонтальной оси.

hAxis_area
  .selectAll('span')
  .data(ticks)
  .enter()
  .append('span')
  .style('position', 'absolute')
  .style('left', function(d,i) { return widthScale(d) + 'px'; } )
  .text(String)
;

Линейный график с метками
Линейный график с метками

Продолжая усложнять график, границами элементов div можно изобразить оси, нанести на них деления, провести линии разметки и сделать это все с использованием только HTML и CSS.
Однако, попытавшись так сделать, довольно скоро мы столкнемся с существенными ограничениями этого подхода. Все-таки HTML в первую очередь язык разметки текстовых страниц, а не рисования.

А это значит, что пора переходить на следующий уровень.

 

Использование SVG в библиотеке D3

В HTML-документах можно использовать язык SVG (Scalable Vector Graphic, масштабируемая векторная графика), предназначенный для описания двумерной графики в XML. Что для нас важно — что манипуляция всеми графическими примитивами SVG осуществляется почти точно так же, как и HTML.

Возьмем такой же пустой файл HTML со ссылкой на библиотеку D3 и тем же массивом исходных данных:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
  </style>
  <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
  <script>
    var RANDOM_MIN = 0, RANDOM_MAX = 100;
    // Функция генерации случайного целого числа в диапазоне [lo..up]
    function irand(lo, up) { return Math.floor(Math.random()*(up-lo+1)+lo); }
    // Массив случайных чисел
    var data = []; for (var i=0; i<10; i++) { data.push(irand(RANDOM_MIN, RANDOM_MAX)); }
  </script>
</body>
</html>

Для начала определимся с размерами базовой области диаграммы:

// Внешние размеры области диаграммы
var CHART_WIDTH = 500,
    CHART_HEIGHT = 300;

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

var
  AXIS_SIZE = 30, // Отступ для оси
  PADDING = 5;    // Дополнительный зазор между

// Размер непосредственно графика = общий размер минус сумма отступов по сторонам
var
  PLOT_AREA_WIDTH = CHART_WIDTH - 2*(AXIS_SIZE + PADDING),
  PLOT_AREA_HEIGHT = CHART_HEIGHT - 2*(AXIS_SIZE + PADDING);

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

var
  // Общая высота для каждого прямоугольника =
  // = доступная высота, деленная на число элементов данных
  BAR_AVAIL_HEIGHT = PLOT_AREA_HEIGHT / data.length,
  // Зазоры сверху и снизу прямоугольника
  BAR_SPACING_TOP = 1, BAR_SPACING_BOTTOM = BAR_SPACING_TOP,
  // Собственно высота прямоугольника
  BAR_HEIGHT = BAR_AVAIL_HEIGHT - BAR_SPACING_TOP - BAR_SPACING_BOTTOM;

Теперь добавим к документу элемент <svg> аналогично тому, как до этого создавался элемент <div>, внутри которого размещались все остальные элементы диаграммы:

var chart_area = d3
  .select('body')
  .append('svg') // Добавляем элемент svg
  .attr('class', 'chart_area') // Задаем класс
  // При задании размеров и координат единицы измерения не указываются
  .attr('width', CHART_WIDTH)    // ширина
  .attr('height', CHART_HEIGHT)  // и высота
;

Как видно, пока отличия минимальны — вместо элемента 'div' был добавлен элемент 'svg', и при задании его размера не указываются единицы измерения ('px'), так как размеры элемента 'svg' всегда задаются в пикселях.
Сразу определим уже знакомую функцию масштабирования по ширине:

var widthScale = d3.scale.linear()
  .domain([
    d3.min(data, function(d,i) { return d; }), 
    d3.max(data, function(d,i) { return d; })  
  ])
  .range([0,  PLOT_AREA_WIDTH])
  .nice()
;

Добавим прямоугольники для данных массива — элементы 'rect' аналогично тому, как раньше для данных массива добавлялись элементы 'div':

var bars = chart_area
  .selectAll('rect')
  .data(data)
  .enter()
  .append('rect')
;

Если взглянуть на получившийся HTML, увидим, что в теле документа появились элементы rect, соответствующие элементам исходного массива:

...
<svg class="chart_area" width="500" height="300">
  <rect></rect>
  <rect></rect>
  ...
  <rect></rect>
</svg>
...

Зададим их свойства:

bars
  // По оси x отступим справа
  .attr('x', AXIS_SIZE+PADDING)
  // По оси y
  .attr('y', function(d,i) {
    // Смещаемся на ширину оси с дополнительным отступом плюс
    // порядковый номер прямоугольника, умноженный на его высоту, и дополнительный зазор
    return AXIS_SIZE + PADDING + i*BAR_AVAIL_HEIGHT + BAR_SPACING_TOP;
  } )
  // Ширина прямоугольника определяется с использованием функции масштабирования
  .attr('width', function(d,i) { return widthScale(d); } )
  // Высота прямоугольника постоянна
  .attr('height', BAR_HEIGHT )
;

Отличия от предыдущего варианта также очень малы. Вместо 'left' и 'top', как бы это делалось для 'div', задаются координаты 'x' и 'y'. И, как мы уже знаем, не указываются единицы измерения.

Пока что все прямоугольники одинакового черного цвета. Раскрасим их, используя свойство стиля 'fill' (вместо 'background-color' при использовании

):

bars
  .attr('fill', function(d, i) { return 'hsl(240,50%,'+(75-d/4)+'%)'; })
  ;

Если при построении диаграммы на базе чистого HTML координатные оси необходимо было рисовать полностью самостоятельно, то для SVG при отрисовке координатных осей можно использовать готовую функцию d3.svg.axis.

Создадим функции для рисования горизонтальных осей сверху и снизу:

// Горизонтальная сверху
var htAxis = d3.svg.axis()
  .scale(widthScale)
  // Ориентация может принимать одно из четырех значений:
  // 'top', 'bottom' (по умолчанию), 'left' и 'right'
  .orient('top')
  ;
// Горизонтальная снизу
var hbAxis = d3.svg.axis()
  .scale(widthScale)
  .orient('bottom')
  ;

И добавим их на диаграмму

chart_area
  .append('g')
  .attr('transform', 'translate('+(AXIS_SIZE+PADDING)+','+(AXIS_SIZE)+')')
  .classed('axis', true)
  .call(htAxis)
;
var hbaxis_area = chart_area
  .append('g')
  .attr('transform', 'translate('+(AXIS_SIZE+PADDING)+','+(CHART_HEIGHT-AXIS_SIZE)+')')
  .classed('axis', true)
  .call(hbAxis)
;

Чтобы сделать их посимпатичней, добавим следующие стили в разделе <style> заголовка HTML-файла:

.axis path {
  fill: none;
  stroke: grey;
  shape-rendering: crispEdges;
}
.axis text {
}
.tick line {
  stroke: grey;
  shape-rendering: crispEdges;
}

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

hbaxis_area
  .selectAll('line.minor')
  .data(widthScale.ticks(20))
  .enter()
  .append('line')
  .attr('class', 'grid')
  .attr('y1', 0)
  .attr('y2', -PLOT_AREA_HEIGHT - 2*PADDING)
  .attr('x1', widthScale)
  .attr('x2', widthScale)
  .attr('stroke-dasharray', '5,5')
;

И добавив еще один стиль:

.axis .grid {
  stroke: grey;
  shape-rendering: crispEdges;
}
 

Круговая диаграмма

Как мы увидели, линейные диаграммы в D3 рисуются очень просто. Что насчет более сложных? Посмотрим на круговую диаграмму, в которой исходным данным соответствуют углы секторов или дуг окружности.

Одна из ключевых функций для построения этой диаграммы в D3 — d3.svg.arc(), позволяющая рисовать сектора окружности и дуги. Эта функция формирует значения для атрибута 'd' элемента SVG 'path', определяющего список операции, с помощью которых отрисовываются сложные контуры. Подробнее об элементе 'path' с примерами можно прочитать здесь.

Если бы нам нужно было нарисовать всего одну дугу, это можно было бы сделать примерно так:

var arc1 = d3.svg.arc()  // Создаем функцию
  .innerRadius(10)       // Внутренний радиус — 10 пикселей
  .outerRadius(100)      // Внешний радиус — 100 пикселей
  .startAngle(0)         // Начальный угол 0 радиан (0 градусов)...
                         // ...считая от направления вверх по часовой стрелке
  .endAngle(Math.PI / 4) // Конечный угол Pi/4 радиан = 45 градусов)
;
chart_area               // В уже существующей выборке элементов
  .append('path')        // добавляем объект SVG 'path'
  .attr('d', arc1)       // Задаем его контур с помощью функции arc1, объявленной выше
  .attr('transform', 'translate(100,100)') // Перемещаем в координаты (100, 100)
;

Разобравшись с функцией arc, перейдем к построению самой круговой диаграммы и начнем с объявления в том же файле HTML нескольких дополнительных констант:

var ARC_RADIUS_INNER = 25,  // Внутренний радиус круговой диаграммы
    ARC_RADIUS_OUTER = 100, // Внешний радиус круговой диаграммы
    ARC_SEL_SHIFT    = 20,  // Сдвиг дуги при наведении мыши
    ANIM_DELAY_1     = 400, // Длительность анимации при наведении мыши
    ANIM_DELAY_2     = 50;  // Длительность анимации при выходе мыши

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

var arc = d3.svg.arc()
  .innerRadius(ARC_RADIUS_INNER)
  .outerRadius(ARC_RADIUS_OUTER)
;

В предыдущих примерах цвета задавались на основе данных. Можно использовать и уже предопределенные наборы цветов. Например, такая функция сформирует гамму из 20 цветов:

var color = d3.scale.category20c();

В том же элементе SVG cоздадим элемент 'g' (group), позволяющий группировать элементы SVG, и поместим ее по центру пространства, выделенного для диаграммы, с помощью атрибута 'transform':

var pie_area = chart_area
  .append('g')
  .attr('transform', 'translate('+CHART_WIDTH/2+','+CHART_HEIGHT/2+')')
;

Для упрощения построения некоторых типов диаграмм D3 предлагает инструменты, называемые компоновками (layout). Для круговых диаграмм предназначена компоновка d3.layout.pie. Она позволяет преобразовать данные исходного массива в данные, используемые для последующей отрисовки.

// Создаем компоновку круговой диаграммы
var pie = d3.layout.pie()
  // При необходимости данные исходного массива можно преобразовать функцией
  .value(function(d) { return d; })
;

Использовать компоновку необязательно, нужно это прежде всего для вычисления значений углов (startAngle и endAngle) на основе значений исходного массива; эти значения в дальнейшем будут использованы для отрисовки дуг.

Для каждой дуги сначала добавим элемент , в котором сгруппируем само графическое изображение дуги и текст со значением элемента.

// Выберем все элементы <g> с классом 'slice'
var arcs = pie_area.selectAll('.slice')
  // Свяжем с данными, которые представляют собой массив значений
  // startAngle, endAngle, value, определяемых из исходных данных
  .data(pie(data))
  // Определяем выборку добавляемых элементов данных
  .enter()
  // Создаем группу <g>
  .append('g')
  // Зададим класс
  .attr('class', 'slice')
;

Теперь arcs — это выборка элементов , соответствующих элементам компоновки pie, которые, в свою очередь, привязаны к исходному массиву данных (его значения доступны через свойство data каждого из элементов компоновки), и наш HTML выглядит следующим образом:

...
<svg class="chart_area" width="500" height="300">
  ...
  <g transform="translate(250,150)">
    <g class="slice"></g>
    <g class="slice"></g>
    ...
    <g class="slice"></g>
  </g>
</svg>
...

Дуги диаграммы отрисовываются так:

arcs.append('path')
  // Цвет заливки определяется функцией, заданной выше
  .attr('fill', function(d, i) { return color(i); } )

  // Рисование контура SVG path по действиям, задаваемым функцией arc
  // with the arc drawing function
  .attr('d', arc)
;

Внутри каждого элемента добавляется элемент , с помощью которого можно определять контур сложных фигур. Его атрибут 'fill' нам уже знаком, а с помощью атрибута 'd' задается список операции, описывающий сам контур.

Добавим текст; для его размещения в визуальном центре дуги используется функция arc.centroid(), возвращающая пару координат (x,y).

arcs.append('text')
  .attr('transform', function(d) { return 'translate(' + arc.centroid(d) + ')'; })
  .style('text-anchor', 'middle') // Выравнивание текста по центру
  .text(function(d) { return d.data; }) // Значение из исходного массива
;

Так как в качестве данных при отрисовке мы используем не исходный массив, а компоновку d3.layout.pie, сформированную на его основе, для того чтобы получить значение из исходного массива (которое и должно быть выведено в виде текста), нужно обратиться к свойству data текущего элемента данных, которое и вернет значение из исходного массива.

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

Чтобы добавить анимацию при наведении мыши, определим для выборки arcs обработчики mousenter и mousexit и вспомним немного тригонометрии для того, чтобы элемент, на который указывает мышь, выдвигался из диаграммы от центра по радиусу:

arcs
  .on('mouseenter', function(d) {
    d3.select(this)  // Выберем элемент, на который наведена мышь
      .transition()  // Начинаем анимацию
      .duration(ANIM_DELAY_1) // Длительность анимации
      .attr('transform', function(d) { // Перемещаем элемент по радиусу от центра
        // Направление, по которому смещаем, — среднее от начального и конечного угла дуги
        var a = (d.endAngle+d.startAngle)/ 2,
          // Смещение по оси x — противоположный катет
          dx =  ARC_SEL_SHIFT*Math.sin( a ),
          // Смещение по оси y — прилежащий катет (ось направлена вниз, нулевой угол — вверх)
          dy = -ARC_SEL_SHIFT*Math.cos( a );
        return 'translate(' + dx + ','+dy+')';
      })
    ;
  })
  .on('mouseleave', function(d) {
    d3.select(this)
      .transition()
      .duration(ANIM_DELAY_2)
      // Возвращаем в начальную позицию
      .attr('transform', function(d) {
        return 'translate(0,0)';
      })
    ;
  })
;

Вычисление смещения дуги
Вычисление смещения дуги

Графики с использованием SVG
Графики с использованием SVG

 

Заключение

Мы коснулись лишь очень малой части возможностей D3, попробовав простейшие линейные и круговые диаграммы. D3 предоставляет более десятка только базовых способов визуального представления данных: круговые, хордовые, силовые, для иерархических данных — кластерный, в виде деревьев, древовидных карт и других... Мощные средства визуализации геоданных в различных проекциях и их преобразования. Доступ к источникам данных в различных форматах (CSV, JSON, XML и другие), форматирования данных, работы с датами и временем, утилиты для работы с цветом, с массивами, интерполяции, двумерной геометрии.

Все это в сочетании с прекрасной документацией и огромным количеством разнообразных примеров объясняет большую популярность D3 и делает его одним из обязательных к освоению инструментов JavaScript-разработчика.

 

События в D3

В D3 обработчики событий добавляются привычной функцией on(). В качестве параметров обработчику передается текущий элемент данных и его порядковый номер. Само событие хранится в переменной d3.event, а переменная this указывает на текущий элемент модели DOM:

.on('click', function (d, i) { console.log(d, i, d3.event, this); } )

Подробнее — здесь.

 

D3 behaviors

Для определения поведения программы при обработке действий пользователя предназначены так называемые behaviors, позволяющие назначать обработчики событий и упрощающие их обработку для перетаскивания и масштабирования.


Оставить мнение

Check Also

Конкурс хаков: разведка DNS средствами chaosnet txt-записи hostname.bind

Тема information gathering с помощью DNS уже многократно поднималась в специализированных …