Визуализация данных на JS

Нередко встречаются веб-приложения, в которых данные удобным для нас образом представлены в виде всевозможных графиков, линейных/круговых диаграмм. Отличным инструментом, способным справиться с рисованием этих объектов и других, гораздо более сложных (от обработки древовидных данных до визуализации геоданных в различных проекциях), является JavaScript библиотека D3


D3 (Data-Driven Documents — Управление данными в документах [досл.]). Она позволяет «оживить» ваш проект, используя произвольные данные, совместить их с HTML, CSS и SVG (Scalable Vector Graphic — Векторная графика), динамически манипулируя элементами DOM (Document Object Model — Объектная модель документа) и на выходе получить, к примеру, линейную диаграмму или любой другой объект/картинку. Из реального использования D3 можно отметить такие важные применения, как построение графиков, создание карты, с использованием координат и другое.

Для успешного изучения данной библиотеки потребуется знания в области HTML, CSS, JS и SVG (хорошее знакомство с SVG можете посмотреть здесь). Данная статья дает лишь краткий обзор широких возможностей, которые вы сможете изучить уже в дальнейшем самостоятельно.

Что же подразумевается под данными? Данные — это любая информация, потенциально содержащая некий смысл, причем как явного, так и неявного характера и применяя визуализацию к данным в такой постановке вопроса, можно этот смысл представить в довольно наглядном виде. В самом простом виде мы можем представить себе данные, как массив чисел, описывающих какой то смысл, к примеру то же самое голосование в виде линейного графика, сколько проголосовало за «хорошо», а сколько за «плохо». На входе поступают данные (пользователь выбрал вариант, этот вариант обработался), а на выходе мы видим отображение для проделанных действий, — голос зачтен, суммировался с предыдущими голосами и общая картинка представилась в виде графика.

Что нам предоставляет D3? Библиотека дает нам все необходимые инструменты для обработки данных и манипулирования DOM (наподобие jQuery, в частности построение цепочек методов). Данные библиотеки имеют очень большое сходство друг с другом, так как имеют встроенные процедуры манипулирования DOM, распознают CSS-селекторы (выборку элементов) и основываются на веб-стандартах, но все же имеют отличие друг от друга по назначению. jQuery — обертка над стандартным JS, имеющимся в любом браузере, библиотека общего назначения, предназначенная для облегчения множества операции, проделываемых на обычном JS, в то время как D3 предназначается для манипуляции данными (их визуализации, фильтрации, трансформацией, генерированием и т.д.), поддерживает работу с векторной графикой и содержит различные механизмы привязки данных к html-документу.

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

<svg width="250" height="250">
    <line x1="0" y1="250" x2="0" y2="0" stroke-width="1" stroke="rgb(0,0,0)"></line>
    <line x1="0" y1="250" x2="250" y2="250" stroke-width="1" stroke="rgb(0,0,0)"></line>
    <line x1="250" y1="0" x2="0" y2="250" stroke-width="1" stroke="rgb(155,50,30)"></line>
    <circle r="50" cx="125" cy="125" fill="rgb(200,200,200)"></circle>
</svg>

Прежде всего нужно отметить, что весь код пишется в тегах svg, в контейнере для векторной графики с шириной и высотой в 250 пикселей (на самом деле вы можете сами определять размеры, просто я сделал 250px для примера). На листинге отрисованы всего навсего 4 геометрических фигуры: три линии и одна окружность, по-изучайте немного данный код, здесь все просто, а статья про знакомство с SVG, о которой я писал выше развеет все вопросы.

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

Теперь попробуем сделать тот же «причудливый» график но уже в динамике, методами D3. Для начала подключим D3, например так:

<script src="https://d3js.org/d3.v3.min.js"></script>

Затем создайте элемент:

<svg id="area" width="250" height="250"></svg>

И пропишите следующий код:

var svg = d3.select("#area"),      // выбираем контейнер svg
    line_1 = svg.append("line"),   // вставляем линию
    line_2 = svg.append("line"),
    line_3 = svg.append("line"),
    circle = svg.append("circle");   // вставляем окружность

/* присваиваем атрибуты вновь добавленным элементам в контейнер svg */

line_1.attr("x1", "0")
    .attr("y1", "250")
    .attr("x2", "0")
    .attr("y2", "0")
    .attr("stroke-width", "1")
    .attr("stroke", "rgb(0,0,0)");
line_2.attr("x1", "0")
    .attr("y1", "250")
    .attr("x2", "250")
    .attr("y2", "250")
    .attr("stroke-width", "1")
    .attr("stroke", "rgb(0,0,0)");
line_3.attr("x1", "250")
    .attr("y1", "0")
    .attr("x2", "0")
    .attr("y2", "250")
    .attr("stroke-width", "1")
    .attr("stroke", "rgb(0,0,0)");
circle.attr("r", "50")
    .attr("cx", "125")
    .attr("cy", "125")
    .attr("fill", "rgb(200,200,200)");

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

Код выше можно, и нужно, конечно оптимизировать, задав массив координат и прогнать все в цикле, таким образом убрать повторяющиеся куски кода, но мы здесь не про оптимизацию кода, а про основы и возможности библиотеки. Идем дальше.

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

Концепция D3

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

/* простая функция генерации массива из 5 случайных положительных целых чисел в диапазоне 0..100 */
function numRand(max) {
    if (!max || max < 0 || max > 100)
        return false;
    var arr = [];
    for (var i = 0; i < 5; i++)
        arr[i] = Math.round(Math.random() * max);
    return arr;
}

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

<!doctype html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Работа с D3</title>
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <script>
        // ... функция генерации массива ...
    </script>
</head>
<body>
<style>
    /* инлайн стили */
</style>
<!-- пустой DIV #diagramm -->
<div id="diagramm"></div>
</body>
<script>
    // ... скрипт D3 ...
</script>
</html>

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

Ситуация входного потока Enter возникает тогда, когда элементов DOM меньше чем количество данных, или элементов DOM нет совсем, а данные есть. В таком случае из всего множества элементов выделяется подмножество элементов, которые необходимо добавить на страницу специальным для этого методом .append( )

Вставим скрипт генерации массива в наш документ вместо // ... функция генерации массива ... выше закрывающего тега head и сразу после функции сформируем данные: var randData = numRand(50);, случайные числа в диапазоне 0..50. В данный момент у нас есть пустой div id="diagramm", то есть в нем еще нет абсолютно никаких элементов DOM, но уже есть данные, переменная randData содержит массив случайных чисел!

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

.bar {
    display: inline-block;
    width: 20px;
    margin: 0 2px;
    padding: 3px;
    color: #fff
}

Класс .bar задаст одинаковую ширину для столбцов, отступы и цвет шрифта внутри. Теперь, где фраза // ... скрипт D3 ... вставим скрипт, который осуществляет привязку данных массива и создает элементы DOM внутри div id="diagramm" методом .append( ):

var diagramm = d3.select("#diagramm");   // выбираем пустой div#diagramm
diagramm
    .selectAll("span")   // выбираем все дочерние элементы span; данных элементов нет-поэтому выборка является пустой
    .data(randData)   // привязываем массив данных (случайные 5 чисел) с предыдущей выборкой
    .enter()   // метод выделяет множество элементов, которые нужно добавить и которым соответствуют данные из массива - т.е данные, соответствующие всем эл-м массива
    .append("span")   // добавляем <span></span> внутри div#diagramm
    .classed("bar", true)   // присваиваем класс bar каждому элементу span
    .style("height", function (d, i) {
        return 3 * d + "px"
    })   // формируем высоту каждого span соответственно числовому значению из массива данных
    .style("background-color", function (d, i) {
        return "hsl(200,80%," + (50 - d / 2) + "%)";
    })   // формируем цвет каждого span соответственно числовому значению из массива данных
    .text(String);   // отображаем числовые значения внутри span

Метод .data() связывает данные из массива с выборкой, которую вернул метод .selectAll(). Здесь мы устанавливаем что вновь создаваемыми элементами будут span, но так как данных элементов в текущий момент времени нет, то мы резервируем место исходя из того, что каждому элементу из массива данных соответствует элемент DOM span. Метод .enter( ) тем временем выделяет подмножество добавляемых элементов (получится столько же элементов span, сколько элементов в массиве randData). Метод .append( ) непосредственно добавляет элементы в DOM.

Для того чтобы высота столбцов соответствовала числовым значениям массива, для каждого вновь добавленного элемента динамически формируется высота и цвет с помощью callback-функции function(d, i){...}, куда передается значение d и i, — это текущее значение элемента массива и его порядковый номер, а в теле функции мы возвращаем результат, например для высоты: { return 3*d+"px" }, я возвращаю утроенное значение текущего элемента массива, из за чего можно утверждать что максимальная высота столбца может быть 150px и минимальная 0. Аналогично можно обрабатывать цвет столбцов!

Множества Exit и Update

Выше мы рассмотрели ситуацию когда формируется множество входного потока Enter и реализовали простейшую вертикальную диаграмму основанную на добавлении элементов DOM, для которых есть данные в массиве. Но есть и противоположная ситуация: когда данных меньше чем элементов на странице, которым будут соответствовать эти данные, в таком случае такие элементы автоматически попадают в подмножество Exit и впоследствии удаляются методом .remove( ).

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

Множеству Update соответствуют элементы, для которых есть соответствующие элементы массива и есть элементы DOM, но значения которых изменились. Это ситуация находится между Enter и Exit.




Похожие заметки:

Удобная разработка с livereload, установка browser-sync

Livereload — «живая» перезагрузка страниц в браузере, при изменении в файлах проекта. Обычно очень удобно при разработке нового проекта, когда постоянно вносятся и тестируются изменения, особенно если дебажим во многих вкладках и браузерах сразу

Открыть здесь

1283

Верстка

Услуги » верстка страниц сайта

Открыть здесь

227

Скрипт динамической ширины

Скрипт для равномерного распределения блоков по ширине родительского контейнера. В качестве контейнера может выступать любой блок как определенной ширины, так и неопределенной, вплоть до body. Что умеет?

  • Нарезать блоки на одинаковую ширину в зависимости от заданного количества колонок
  • Генерировать нужное количество колонок
  • Проставлять clearfix после оканчивающей ряд колонки, чтобы вовремя отменить обтекание
  • Удалять лишние clearfix

Открыть здесь

240


Перед тем как писать комментарии, рекомендую ознакомиться:

Markdown синтаксис »

Оформление кода »

Нужна аватарка »

Комментарии