[Перевод] Как в проекте заменить jQuery на D3

Создавая визуализации или интерактивные страницы, мы часто используем комбинацию из 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('
'); $('.foo').prepend('
');

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('
Hello
');

D3

d3.selectAll('.foo').text('Hello World!');  
d3.selectAll('.foo').html('
Hello
');

Различия


Теперь рассмотрим функции, которые есть в 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 можно вставлять элементы сразу после всех элементов выборки. Рассмотрим код:

  • List
  • List
  • List

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

$('li').after('
  • Item
  • ');

    И вот что мы получим:

    • List
    • Item
    • List
    • Item
    • List
    • Item

    В 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();
    
    

    И в результате:

      В 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. Пример работы:

      some element
      some other element

      Теперь вызываем appendTo () на всех элементах, у которых есть класс «foo», чтобы добавить их к целям.

      d3.selectAll('.foo').appendTo('.target');  
      
      

      Что будет в DOM:

      some element
      some other element
      some element
      some other element

      Функция возвращает добавленные элементы, чтобы с ними можно было работать и далее. К примеру, изменение фона:

      d3.selectAll ('.foo').appendTo ('.target').style ('background', '#f00');

      length ()
      Иногда полезно знать, сколько элементов есть в вашей выборке. в jQuery есть свойство по имени length

      
      
      $('.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.

      © Habrahabr.ru