Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Вступ до D3

with 2 comments

D3 (розшифровується як DDD, що означає Data Driven Documents) – то бібліотека для написання JavaScript візуалізацій.

Бібліотеку можна скачати з сайту, або під’єднатись до CDN:

<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>

Тепер, щоб змінити наприклад текст якогось елемента, можна за допомогою d3 цей елемент вибрати, і відредагувати його текст:

d3.select('#field').text('hello world!')

Метод select приймає такі самі селектори CSS як і jQuery, тому тут нічого несподіваного для тих хто користувався jQuery (чи CSS) не повинно бути.

Можна також додавати елементи:

d3.select('body').append('p').text('hello world!')

Можна змінювати оформлення елементів:

d3.select('body')
   .append('p')
   .text('hello world!')
   .style('color', 'red')

Цих маніпуляцій з елементами вже досить щоб малювати щось з SVG:

var panel = d3.select('body');
var width = panel[0][0].clientWidth - 2;
var height = panel[0][0].clientHeight - 2;
var svg = panel.append('svg')
    .attr('width', width)
    .attr('height', height)
    .style('border', 'solid black 1px');

var circle = svg.append('circle')
    .attr('cx', width / 2)
    .attr('cy', height / 2)
    .attr('r', height / 2 - 1);

Тепер давайте додамо ще трішки кіл, і подивимось на відмінність між select та selectAll. Перший метод повертає лише перший знайдений елемент, а другий – всі.

for(var i = 0; i <= 10; i++) {
    svg.append('circle')
        .attr('r', 10)
        .attr('cy', height / 2);
};

var circles = svg.selectAll('circle');

Тепер ми можемо задати атрибут всім колам зразу. А можемо сказати що атрибут кожного кола повинен бути результатом обчислення функції. І передати замість значення – лямбду. Наприклад можна змусити наші кола скакати туди-сюди через певний інтервал:

setInterval(function() {
    circles.attr('cx', function() { return Math.random() * width });
}, 100);

Але випадкові функції то не цікаво. Давайте займемось чимось серйозним, і намалюємо графік функції. Для цього функцію треба буде кілька разів обчислити на певному проміжку:

var tabulate_function = function(f, a, b, count) {
    var f_data = [];
    var width = b - a;
    for(var i=0; i < count; i++) {
        f_data.push(f(a + i * width / count));
    };
    return f_data;
};

Передавши в функцію tabulate_function, функцію для табулювання, інтервал a, b, та кількість обчислень, ми отримуємо масив з даними:

var BARS_COUNT = 100;
var data = tabulate_function(Math.sqrt, 0, 10, BARS_COUNT);

Тепер, за допомогою методу data ми можемо прив’язати наші дані до вибірки з прямокутників. А також задати висоту прямокутника як функцію від даних, а його позицію – як функцію від номера елементу (і даних, хоча тут не використовуватимемо):

var bars = svg.selectAll('rectangle').data(data); // Прив’язуємо до вибірки з прямокутників
bars.enter().append('rect'); // А що якщо прямокутників нема (не вистачає)? Тоді додаємо прямокутник.

var bar_width = width / BARS_COUNT;

bars
    .attr('y', function(d) { return height - d * 100 }) // Позиція по осі Y як і висота функції від даних
    .attr('x', function(d, i) { return i * bar_width }) // Позиція по осі X - функція від номера елементу даних.
    .attr('width', bar_width)
    .attr('height', function(d) { return d * 100 });

Має вийти щось схоже на оце: sqrt. Якщо не вийшло – подивіться в чому відмінність вашого і мого коду.

Мені й самому дивно, для чого так незвично описувати залежність між даними й елементами. Але тут основна фішка в тому що ми можемо передати нашому зображенню нові дані, а воно не буде видаляти всі елементи і створювати їх заново, а змінить атрибути тих елементів які треба змінити, додасть нових елементів якщо їх бракує і видалить елементи які стали зайвими.

Щоб вони видалялись, не забудьте написати:

bars.exit().remove();

Тепер про оновлення. Хотілось би просто написати bars.data(new_data), але таке чомусь не працює. Тому треба заново знайти всі елементи, прив’язати до них дані і сказати їм як на ці дані реагувати.

В такому випадку, я виношу це все в функцію update:

var update_graph = function(data) {
    var bar_width = width / data.length;
    var bars = svg.selectAll('rect').data(data);

    bars.enter().append('rect');

    bars
      .attr('x', function(d, i) { return i * bar_width })
      .attr('width', bar_width)
      .transition().duration(2000) // Наступні атрибути змінювати плавно, за 2 секунди
      .attr('y', function(d) { return height - d * 100 })
      .attr('height', function(d) { return d * 100 });
    
    bars.exit().remove();  
};

І тоді, коли ми викличемо цю функцію кілька разів з різними даними – картинка буде плавно змінюватись, не перестворюючи надто багато елементів. А якщо видалити виклик transition().duration() – то буде змінюватись миттєво. Можна подивитись тут.

І на цьому напевне завершу мою розповідь, а то вона щось виходить нуднішою ніж я сподівався. А d3 не нудний, на ньому не тільки графіки можна малювати, а й наприклад прості іграшки.

Посилання

  1. JSFiddle – спонсор даної публікації. 🙂 Всім рекомендую користуватись під час читання різноманітних підручників з веб дизайну.
  2. Scott Murray – Learning d3 (youtube)
  3. Mike Bostock – Three Little Circles
  4. Mike Bostock – Thinking With Joins
Advertisements

Written by bunyk

Листопад 30, 2014 at 10:37

Оприлюднено в Графіка, Кодерство, Павутина

Tagged with

Відповідей: 2

Subscribe to comments with RSS.

  1. Ремарка – CDN-url можна взяти тут: https://cdnjs.com/libraries/d3/

    garantino

    Листопад 30, 2014 at 13:15


Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: