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