Создавая визуализации или интерактивные страницы, мы часто используем комбинацию из jQuery и D3. Причём в основном используется D3, а из jQuery берут небольшой набор функций для манипуляций с DOM.
И хотя в D3 есть мощные возможности – селекторы и обёртка для ajax, часто нам не хватает каких-то функций из jQuery. Мы покажем, как можно заменить jQuery, используя D3 повсеместно. В результате ваш код упростится, объём проекта уменьшится, и вы не будете смешивать разные подходы, а будете использовать функции так, как принято в D3.
Для начала рассмотрим, в чём эти две библиотеки сходятся. Это удобно для тех, кто уже знает jQuery, и хочет изучить D3.
Схожести
Селекторы
Обе библиотеки основаны на простых в использовании, но богатых на возможности селекторах.
jQuery
$('.foo').addClass('foobar'); $('.foo').removeClass('foobar');
D3
d3.selectAll('.foo').classed('foobar', true); d3.selectAll('.foo').classed('foobar', false);
Управление стилями и атрибутами
jQuery
$('.foo').attr('data-type', 'foobar'); $('.foo').css('background', '#F00');
D3
d3.selectAll('.foo').attr('data-type', 'foobar'); d3.selectAll('.foo').style('background', '#F00');
Ajax
Синтаксис немного отличается, но, как у D3, так и у jQuery есть хорошие обёртки для
ajax.
jQuery
$.getJSON('http://url-to-resource.json', doSomething); $.ajax({ url: 'http://url-to-resource.txt', dataType: 'text', type: 'GET', success: doSomething });
D3
d3.json('http://url-to-resource.json', doSomething); d3.text('http://url-to-resource.txt', doSomething);
Управление классами
Часто бывает необходимо управлять классами элементов DOM, например, чтобы переключать стили.
jQuery
$('.foo').addClass('foobar'); $('.foo').removeClass('foobar');
D3
d3.selectAll('.foo').classed('foobar', true); d3.selectAll('.foo').classed('foobar', false);
Append и Prepend
Вставка дочерних узлов – функция важная, особенно при визуализации входных данных. Это делать легко и просто:
jQuery
$('.foo').append('<div/>'); $('.foo').prepend('<div/>');
D3
d3.selectAll('.foo').append('div'); d3.selectAll('.foo').insert('div');
Отслеживание событий
Одинаковый синтаксис предназначен для отслеживания событий на выбранных элементах.
jQuery
$('.foo').on('click', clickHandler);
D3
d3.selectAll('.foo').on('click', clickHandler);
Удаление элементов
Иногда вам требуется удаление элементов из DOM. Вот как это делается:
jQuery
$('.foo').remove();
D3
d3.selectAll('.foo').remove();
Выборка подмножества элементов
Вы можете выбрать дочерние элементы из более крупной выборки
jQuery
$('.foo').find('.bar');
D3
d3.selectAll('.foo').selectAll('.bar');
Управление содержимым
Для изменения содержимого узла DOM можно использовать следующие функции.
jQuery
$('.foo').text('Hello World!'); $('.foo').html('<div class="bar">Hello</div>');
D3
d3.selectAll('.foo').text('Hello World!'); d3.selectAll('.foo').html('<div class="bar">Hello</div>');
Различия
Теперь рассмотрим функции, которые есть в jQuery, но отсутствуют в D3. Для каждой из них приводится простое решение на её замену, а также более общий вариант использования, который может пригодиться вам в любом месте вашего приложения, использующий фирменные цепочки D3.
Активация событий и настраиваемые события (trigger events and custom events)
Одно из преимуществ jQuery – удобство работы с событиями. Можно запускать или отслеживать настраиваемые события для любого элемента на странице. К примеру, можно запустить настраиваемое событие с некими данными для вашего документа, и отслеживать его в коде:
//слушаем $(document).on('dataChange', function(evt, data) { //do something with evt and data console.log(data.newData); }); //включаем событие $(document).trigger('dataChange', { newData: 'Hello World!' });
В D3 это не поддерживается напрямую, но всегда можно добиться такого поведения. Простой вариант (если в хэндлере вам не нужно d3.event):
//слушаем d3.select(document).on('dataChange', function(data) { console.log(d3.event); //null console.log(data.newData); }); //включаем событие d3.select(document).on('dataChange')({ newData: 'Hello World!' });
Более общий подход – добавить функцию в объект d3, чтобы её можно было использовать на любой выборке.
d3.selection.prototype.trigger = function(evtName, data) { this.on(evtName)(data); }
Добавление этой функции в D3 позволяет вам получить функцию-триггер, напоминающую таковую в jQuery, которую можно использовать следующим образом:
d3.select(document).on('dataChange', function(data) { console.log(data); });
d3.select(document).trigger(‘dataChange’, {newData: ‘HelloWorld!’});
after() и before()
При помощи jQuery можно вставлять элементы сразу после всех элементов выборки. Рассмотрим код:
<ul> <li>List</li> <li>List</li> <li>List</li> </ul>
Можно использовать следующий простой код для вставки нового элемента после каждого элемента списка:
$('li').after('<li>Item</li>');
И вот что мы получим:
<ul> <li>List</li> <li>Item</li> <li>List</li> <li>Item</li> <li>List</li> <li>Item</li> </ul>
В D3 придётся пройти по всем элементам выборки и добавить их через JavaScript:
d3.selectAll('li').each(function() { var li = document.createElement('li'); li.textContent = 'Item'; this.parentNode.insertBefore(li, this.nextSibling); })
Лучший вариант – сделать функцию общего назначения, добавляющую элементы на основании имени тэга и возвращающую новую выборку из созданных элементов, чтобы затем их можно было редактировать:
d3.selection.prototype.after = function(tagName) { var elements = []; this.each(function() { var element = document.createElement(tagName); this.parentNode.insertBefore(element, this.nextSibling); elements.push(element); }); return d3.selectAll(elements); }
Добавив следующее в ваш код, можно будет использовать функцию after() почти так же, как это делается в jQuery:
d3.selectAll('li') .after('li') .text('Item') //тут можно сделать со вставленными элементами что-нибудь ещё
Функция before() выглядит почти также, с той лишь разницей, что элементы вставляются до выборки.
d3.selection.prototype.before = function(tagName) { var elements = []; this.each(function() { var element = document.createElement(tagName); this.parentNode.insertBefore(element, this); elements.push(element); }); return d3.selectAll(elements); }
empty()
Это просто — функция jQuery удаляет все дочерние узлы в выборке.
- List-Item
- List-Item
- List-Item
$('ul').empty();
И в результате:
<ul></ul>
В D3 для этого нужно очистить внутренний HTML у выбранного элемента:
d3.selectAll('ul').html('');
D3 часто используют для работы с SVG. В этом случае такой код не сработает, поскольку там не поддерживается innerHTML. Поэтому лучше не вызывать html(), а выбрать все дочерние узлы и удалить их:
d3.selectAll('ul').selectAll('*').remove();
Код общего назначения будет простой. Я выбрал для функции другое имя, нежели используемое в jQuery, поскольку в D3 уже есть своя функция empty().
d3.selection.prototype.clear = function() { this.selectAll('*').remove(); return this; }
Теперь можно очищать выборку почти так же, как в jQuery:
d3.selectAll('#foo').clear();
appendTo()
В jQuery эта функция работает почти так же, как функция append() в D3, но она добавляет предшествующие выбранные элементы в другую выборку. Чтобы сделать это в D3, необходимо пройти по всем элементам в обоих выборках и добавить элементы друг к другу. Если у вас есть несколько целей, к которым надо добавлять выборку, придётся склонировать объекты, чтобы получить поведение, схожее с jQuery. Вот что у меня получилось:
d3.selection.prototype.appendTo = function(selector) { var targets = d3.selectAll(selector), targetCount = targets[0].length, _this = this, clones = []; targets.each(function() { var currTarget = this; _this.each(function() { if(targetCount > 1) { var clone = this.cloneNode(true); currTarget.appendChild(clone); clones.push(clone); } else { currTarget.appendChild(this); } }); }); if(targetCount > 1) { this.remove(); } return clones.length > 0 ? d3.selectAll(clones) : this; }
Используя её, можно добавлять множественные элементы в DOM. Пример работы:
<div class="target"></div> <div class="target"></div> <div class="foo">some element</div> <div class="foo">some other element</div>
Теперь вызываем appendTo() на всех элементах, у которых есть класс «foo», чтобы добавить их к целям.
d3.selectAll('.foo').appendTo('.target');
Что будет в DOM:
<div class="target"> <div class="foo">some element</div> <div class="foo">some other element</div> </div> <div class="target"> <div class="foo">some element</div> <div class="foo">some other element</div> </div>
Функция возвращает добавленные элементы, чтобы с ними можно было работать и далее. К примеру, изменение фона:
d3.selectAll(‘.foo’).appendTo(‘.target’).style(‘background’, ‘#f00’);
length()
Иногда полезно знать, сколько элементов есть в вашей выборке. в jQuery есть свойство по имени length
<div class=".foo"></div> <div class=".foo"></div>
$('.foo').length; //2
То же самое – в D3:
d3.selection.prototype.length = function() { return this[0].length; }
С таким кодом можно делать так:
d3.selectAll('.foo').length() //2
toggleClass()
Как уже говорилось, в D3 можно использовать функцию classed, чтобы управлять именами классов. Но в D3 нет функции для переключения имён классов, которая частенько используется в jQuery. Её реализация может быть такой:
d3.selection.prototype.toggleClass = function(className) { this.classed(className, !this.classed(className)); return this; }
eq()
Чтобы отфильтровать выборку нескольких элементов и выбрать только узел с заданным индексом, в jQuery можно использовать функцию eq(). Её довольно просто сделать и для D3. Делаем подвыборку из элементов на основании индекса и возвращаем заново сделанную выборку:
d3.selection.prototype.eq = function(index) { return d3.select(this[0][index]); }
show() / hide() / toggle()
Используются для изменения видимости элемента на странице. Они просто меняют стили у выбранных элементов. А в функции toggle() сначала необходимо проверить, виден ли данный элемент.
Показать скрытый:
d3.selection.prototype.show = function() { this.style('display', 'initial'); return this; }
Спрятать видимый:
d3.selection.prototype.hide = function() { this.style('display', 'none'); return this; }
Переключить видимость:
d3.selection.prototype.toggle = function() { var isHidden = this.style('display') == 'none'; return this.style('display', isHidden ? 'inherit' : 'none'); }
moveToFront(), moveToBack()
Этих функций часто не хватает в D3, но с jQuery они не связаны. D3 часто используют для работы с SVG. При этом, в отличие от HTML, в SVG порядок элементов определяет их видимость. Поэтому часто нам не хватает функциональности для перемещения выборки в SVG назад или вперёд.
Для этого мы расширим выборки D3 следующими функциями:
d3.selection.prototype.moveToFront = function() { return this.each(function(){ this.parentNode.appendChild(this); }); }; d3.selection.prototype.moveToBack = function() { return this.each(function() { var firstChild = this.parentNode.firstChild; if (firstChild) { this.parentNode.insertBefore(this, firstChild); } }); };
Использовать их крайне просто – выбираем элемент svg и двигаем его, куда нужно:
d3.select('svg rect') .moveToFront() .moveToBack();
Надеемся, что вышеописанное окажется полезным в тех проектах, в которых излишнее использование jQuery можно заменить простыми решениями на D3. А уже расширенную версию D3 с включёнными функциями можно взять на GitHub.
ссылка на оригинал статьи http://habrahabr.ru/post/264861/
Добавить комментарий