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 });
Має вийти щось схоже на оце:
. Якщо не вийшло – подивіться в чому відмінність вашого і мого коду.
Мені й самому дивно, для чого так незвично описувати залежність між даними й елементами. Але тут основна фішка в тому що ми можемо передати нашому зображенню нові дані, а воно не буде видаляти всі елементи і створювати їх заново, а змінить атрибути тих елементів які треба змінити, додасть нових елементів якщо їх бракує і видалить елементи які стали зайвими.
Щоб вони видалялись, не забудьте написати:
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 не нудний, на ньому не тільки графіки можна малювати, а й наприклад прості іграшки.
Посилання
- JSFiddle – спонсор даної публікації. :) Всім рекомендую користуватись під час читання різноманітних підручників з веб дизайну.
- Scott Murray – Learning d3 (youtube)
- Mike Bostock – Three Little Circles
- Mike Bostock – Thinking With Joins
Filed under: Графіка, Кодерство, Павутина Tagged: JavaScript
