Головна / Статті

D3.js у React: прив’язка даних, SVG та правило власності useEffect

Дізнайтеся, що насправді є D3, як працює прив’язка введення/оновлення/вихіду, чому підходить SVG, та як розділити керування DOM, щоб D3 та React співпрацювали у стовпчастій діаграмі.

Видавець: REACTAPP.TOP7 хв читання1530 слів

Джерела: Harnessing the Power of Data Visualization: Integrating D3.js with React

Три речі, які потрібно знати спочатку

  1. D3 — це інструментальний набір, а не бібліотека діаграм. Ті, хто очікує щось на кшталт Chart.js, стикаються з труднощами. D3 — це низькорівневий механізм для прив’язки даних до елементів DOM, що пояснює як його потужність, так і складність навчання.
  2. React та D3 конкурують за одні й ті самі вузли DOM. Рішення просте: React відображає контейнер SVG, а D3 керує всім, що знаходиться всередині нього, за допомогою useEffect. Якщо проігнорувати це правило, доведеться годинами боротися з дивними конфліктами.
  3. Паттерн enter, update та exit спочатку здається дивним. Після кількох випадків використання він стає зрозумілим, а інші підходи до графіки, заснованої на даних, здаються незграбними.

Що насправді є D3

Ця назва означає «Документи, засновані на даних». Важливим тут є слово «документи»: це елементи DOM, вигляд яких визначається даними. D3 пов’язує масиви з елементами та дозволяє визначати їхній вигляд як функцію від значень у цих масивах. Графік — це лише один із можливих результатів, а сама бібліотека не має уявлення про типи графіків.

Саме тут полягають і сила, і складність цього інструменту. D3 не приймає рішень замість вас. Кожна візуальна властивість, кожна позначка на вісі, кожен колір та кожна взаємодія — це те, що ви самі визначаєте. Ви отримуєте повну творчу свободу, але й багато роботи з введенням коду.

Зв’язування даних та DOM

Саме тут більшість початківців здаються, тому варто розглядати цей аспект повільно.

У звичайному JavaScript ви створюєте елемент, заповнюєте його контентом та вставляєте на сторінку. З D3 ви обираєте групу елементів, включаючи ті, яких ще немає, прив’язуєте до цього вибору масив, а потім отримуєте назад три групи:

  • елементи, які вже існують та потребують лише оновлених даних (вибірка для оновлення),
  • дані, які ще не мають елементів (вибірка для додавання),
  • елементи, даних яких більше немає (вибірка для видалення).

Дивно те, що методи викликаються на порожньому виборі. Ви вибираєте всі елементи rect усередині SVG, який їх не містить, викликаєте .data(myArray), потім .enter(), а потім .append('rect'), і прямокутники з’являються. Насправді .enter() створює місце для кожного даних, у якого немає елемента, а .append() перетворює кожне таке місце на справжній вузол. Це можна зрозуміти як «для кожної точки даних без елемента створити один».

До того, як це почне працювати, код D3 здається чимось магічним. Після цього він стає зрозумілим.

Встановлення візуальних властивостей з даних

Як тільки дані прив’язані, наступний крок стає простішим для розуміння. Будь-яке атрибут чи стиль можна обчислити з даних:

selection.attr('height', d => yScale(d.value))

Тут d — це дані, прив’язані до конкретного елемента, а yScale — це функція, яка перетворює діапазон значень у пікселі. Висота стовпчика є прямою функцією його даних. Змініть дані, знову запустіть код, і висота стовпчика зміниться відповідно. Майже все у візуалізаціях D3 зводиться до варіацій цієї ідеї: присвоювати візуальні властивості функціям від значень діапазону.

Чому SVG

D3 створює візуалізації у форматі SVG, і причини цього йдуть далі за межі історичних обставин.

SVG є векторним форматом, тому фігури визначаються математично, а не як сітки пікселів. Графік у вигляді стовпчиків, який виглядає чітко на ноутбуці, так само чітко виглядає на дисплеї 4K без додаткових зусиль, а експортовані зображення не стають розмитими при некоректних роздільних здатностях.

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

Витрати стають помітними при великій кількості елементів. Тисячі вузлів SVG уповільнюють роботу сторінки, тому відображення 50 000 окремих точок у вигляді окремих елементів буде негативно впливати на продуктивність. Рендеринг за допомогою Canvas краще підходить для таких ситуацій. Для панелей керування, бізнес-діаграм та більшості аналітичних інструментів SVG є прийнятним варіантом, а інтеграція з системою подій виправдовує себе.

Інтерактивність та гнучкість

Інтерфейси для аналізу даних, де користувачі можуть ділити, фільтрувати та досліджувати дані з різних точок зору, — саме тут D3 перевершує статичні інструменти для створення діаграм. Ви контролюєте кожну подію та перехід.

Більшість бібліотек для створення діаграм надають вам типи діаграм для налаштування: стовпчасті, лінійні, кільцеві, з опціями для кольорів, етикеток та легенд. D3 надає замість цього базові елементи: шкали, вісі, генератори ліній та алгоритми розташування. Тип діаграми — це просто їхня комбінація, тому не існує списку підтримуваних варіантів. Якщо певної візуалізації немає ніде, ви можете її створити, причому лише з обмеженнями, пов’язаними з SVG та математикою.

Це потребує додаткової роботи. Чи варто це робити, залежить від проекту. Для стандартних бізнес-діаграм підійдуть Recharts чи Chart.js, і на цьому можна зупинитися. Для індивідуальних, інтерактивних графіків, де важлива саме презентація, кращим вибором є D3.

Функції, які ви будете використовувати постійно

Це не повний довідник, а лише функції, які використовуються у повсякденній роботі:

  • d3.select() бере перший елемент, який відповідає селектору. У React d3.select(svgRef.current) — це звичайний спосіб отримати контейнер.
  • d3.selectAll() повертає всі елементи, які відповідають критеріям. Зазвичай саме тут починається прив’язка даних, оскільки задіяно кілька елементів.
  • selection.data() пов’язує кожен елемент масиву з відповідним елементом інтерфейсу, щоб змінні d були доступні під час подальших викликів. Усе інше залежить від цього кроку.
  • selection.enter() створює тимчасову селекцію для даних, які ще не мають відповідних елементів, а .append() після цього створює ці елементи.
  • selection.attr() встановлює атрибути, наприклад лінію висоти, про яку йшлося вище; цей код доводиться писати сотні разів.
  • selection.style() робить те саме для CSS, наприклад встановлює колір заповнення чи ступінь прозорості, які залежать від того, чи є елемент активним.
  • selection.transition() анімує зміни. Використовуйте його перед .attr() або .style(), щоб значення плавно змінювалися, перетворюючи різкий перехід на анімацію.
  • d3.scaleLinear() перетворює значення з одного числового діапазону в інший. З доменом від 0 до 100 та діапазоном від 0 до 400 значення 50 відповідає пікселю 200. Масштабування використовується майже у кожній візуалізації.
  • d3.axisBottom() та d3.axisLeft() створюють вісі з деленнями та мітками на основі масштабування, що дозволяє уникнути ручного малювання позначок.
  • d3.max() повертає найбільше значення, яке часто використовується для встановлення доменів масштабування, наприклад у d3.max(data, d => d.value).
  • d3.transition() координує анімації кількох елементів одночасно.
  • Використання D3 всередині React

    Проблема, про яку ніхто не згадує, поки з нею не зіткнешся: React підтримує віртуальний DOM та застосовує власні зміни до реального, тоді як D3 безпосередньо редагує реальний DOM. Якщо обидві бібліотеки працюють у межах одного елемента, перерендеринг React перезаписує роботу D3, або D3 додає вузли, про які React не знає, що призводить до плутанини під час наступного рендерингу.

    Ефективний підхід — розподіл обов’язків. React рендерує контейнер SVG та не торкається нічого всередині нього. D3 отримує доступ до цього контейнера через ref та виконує всю свою роботу у функції useEffect. Коли змінюються дані, оновлення стану запускає цю функцію, і D3 очищує та перерисовує елементи. Жодна з бібліотек не втручається в роботу іншої.

    1. Встановіть пакети

    Додайте D3 разом із React та ReactDOM за допомогою вашого менеджера пакетів.

    npm install d3 react react-dom
    

    Користувачі Yarn роблять те саме за допомогою власної команди add.

    yarn add d3 react react-dom
    

    2. Напишіть компонент

    У React є лише одне завдання: відобразити елемент SVG та передати D3 посилання на нього. Ніщо інше у візуалізації не належить до React.

    3. Малюйте за допомогою useEffect

    У функції ефекту напишіть код для D3 так само, як це робилося б поза React, використовуючи посилання для пошуку контейнера. Два масштаби виконують обчислення координат: один відображає мітки категорій у горизонтальних положеннях, а інший — значення даних у вертикальних. Висота кожної смуги дорівнює нижньому краю діаграми за вирахуванням змасштабованого значення. Цей етап часто створює труднощі, оскільки вісь y у SVG спрямована вниз, тому вищі смуги відповідають меншим значенням y.

    4. Відобразіть компонент

    Розмістіть компонент у вашому додатку та передайте йому масив даних. Змініть масив, і графік перерисується: React помічає змінену властивість, запускається ефект, а D3 очищує та перерисовує графік. Зв’язок між станом додатку та візуалізацією є автоматичним.

    5. Запустіть додаток

    Запустіть сервер розробки за допомогою npm.

     npm start
    

    Або за допомогою Yarn.

     yarn start
    

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

    Підсумок

    D3 та React ефективно співпрацюють, коли структура правильна. React керує контейнером та життєвим циклом компонентів, а D3 відповідає за візуалізацію всередині них. Тримайте ці межі чіткими, щоб кожна бібліотека виконувала свою функцію.

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