Изграждане на диаграми, които се мащабират с D3.js и Canvas (част 2)

Написано от Maximiliano Duthey, Full Stack Developer @ XOOR

Здравей отново! Тук ще продължим да изграждаме нашия мащабируем график с D3.js и HTML Canvas. Като супер кратко резюме за предишната публикация, научихме как да начертаем точкова диаграма с помощта на D3 върху Canvas вместо най-популярния, но немащабируем SVG подход.

В крайна сметка получихме хубав сюжет, но доста елементарен и скучен, тъй като беше напълно статичен, така че изобщо нямаше възможност за взаимодействие със сюжета. Целта на тази публикация е да добави малко интерактивност към нея чрез добавяне на функции за панорамиране и мащабиране.

За тази публикация ще продължим да работим върху кода, както го оставихме в предишната публикация. Първата ни стъпка ще бъде да създадем функция, която ще ни позволи да рисуваме отново съдържанието на платното с всяко събитие за мащабиране.

Новата функция за рисуване

Ако си спомняте последния пост, използвахме да рисуваме всички точки само веднъж, когато графиката беше заредена. Тъй като нямахме никаква интерактивност, имаше смисъл да го направим по този начин, тъй като функцията за рисуване щеше да бъде извикана само веднъж, когато графиката беше изобразена. Ето как изглеждаше кодът:

Сега имаме различна ситуация. Искаме да позволим на потребителите да правят неща с нашия график, така че ще трябва да начертаем няколко пъти точките. Защо? Е, да предположим, че плъзнете съдържанието на графиката, така че да се движи и да ви позволи да видите отвъд границите на първоначалното изчертаване. Всеки път, когато се случи такова събитие, трябва да начертаем отново съдържанието на платното, за да сме сигурни, че отговаряме на въведените от потребителя данни. Ако потребителят премести графиката със 100 пиксела надясно, трябва да начертаем отново всичко, така че да изглежда, че графиката е преместена наистина.

За тази цел ще напишем нова функция за рисуване, която ще изглежда така (не се паникьосвайте, ще ви обясним какво е това трансформиращо нещо):

Както можете да видите, цикълът forEach, който имахме преди, сега е малко по-различен. Функцията drawPointвече получава 3 допълнителни параметъра, които ще обясним скоро: scaleX, scaleYиtransform.k.

Нека първо обясним какво представлява нашият нов приятел трансформация:
Всеки път, когато изпълнявате поведение на панорамиране или мащабиране, видимото съдържание на платното ще бъде засегнато от новия „контекст“, който го засяга. Този нов контекст е основно колко сюжетът е бил преместен на всяка страна и колко е бил увеличен. Цялата тази информация се намира в трансформиращия обект. За повече информация относно трансформациите, d3-zoom github страницата има доста хубаво API обяснение, което може да бъде полезно.

Така че основно transform е обект, който освен някои методи има 3 основни свойства:

  • transform.x — количеството на транслация tx по оста x. Това е колко трябва да се премести графиката хоризонтално.
  • transform.y — количеството на превод ty по оста y. Това е, колко трябва да се премести графиката вертикално.
  • transform.k — мащабният фактор k. Това е колко трябва да мащабираме графиката при промяна на мащаба. Ако мащабът не се е променил, ще се използва стойността 1 (известна като идентичност на мащаба).

И така, сега, след като разбрахме какво е трансформация, можем да продължим. Първото нещо, което правим във функцията за чертане в редове 2 и 3, е изчисляването на новия мащаб за оста X и Y. Много е важно да не заменяме оригиналните мащаби и да не съхраняваме трансформираните мащаби в нови променливи (повече за това тук). За да преизчислим скалите, ние използваме специалните функции в трансформиращия обект, интуитивно наречени rescaleX и rescaleY.

Следващата стъпка в редове 4 и 6 е да начертаем отново оста X и Y, прилагайки новите мащаби, които получихме. Ние правим това с помощта на функцията call, която получава като параметър новата скала.

След това изчистваме платното, тъй като ще трябва да начертаем отново всички точки според новите детайли на трансформацията. И накрая ние итерираме нашия набор от данни и продължаваме да рисуваме точките в платното.

Новата функция за рисуване на точки

Както направихме с всичко досега, нека първо да разгледаме крайната функция и след това да обясним направените промени:

Както можете да видите, имаме тези 3 нови параметъра:

  • scaleX, който е новият изчислен мащаб за оста x
  • scaleY, който е новият изчислен мащаб за оста y
  • k, което е мащабният фактор, който зависи от промяната на мащаба

Както можете да видите, сега изчисляваме стойностите за px и py въз основа на scaleX и scaleY, предадени от параметър. Това ще изчисли новите стойности за координатите на точката, които съответстват на текущия контекст на трансформация. Също така изчисляваме нов радиус за точката въз основа на коефициента на мащаба k, като умножим първоначалния радиус от 1,2 по коефициента на мащаба.

Сега, след като преместихме поведението на чертане във функция, трябва да извикаме функцията за първоначалния чертеж, в противен случай нашият график ще бъде празен. За първоначалния чертеж ще използваме специална константа, която D3 глобалният обект е нарекъл zoomIdentity. Това ще начертае графиката без увеличение, нито трансформации по оста X или Y. Така че ще поставим следното извикване точно след затварящата скоба на функцията за рисуване.

Манипулаторът за мащабиране и панорамиране

Имаме нашата нова функция за рисуване, сега е време да създадем някакъв манипулатор, който ще отговаря на въведените от потребителя данни и ще извиква подобрената функция за рисуване. Нека да разгледаме този манипулатор:

Създаваме променлива, наречена zoom_function, която ще бъде присвоена с поведението на мащабиране, така че да можем да я използваме повторно, където пожелаем, но ние просто я дефинираме веднъж. За събитието за мащабиране използваме модула d3-zoom, който предоставя необходимите функции за обработка на събития за мащабиране и панорамиране.

Така че първо извикваме функцията d3.zoom()след това свързваме извикването scaleExtent()където задаваме мащаба на мащабиране. Ние задаваме тази скала от 1 до 1000, не се колебайте да експериментирате с каквито стойности искате тук и да видите как се държи графиката. Накрая дефинираме функцията за обработка на събития, която прави следното:

  1. Получаваме трансформационния обект
  2. Ние наричаме чертащата функция, преминавайки трансформацията

Лесно нали? :)

И накрая, трябва да присвоим манипулатора на някого, в противен случай това е просто мъртва променлива, която никой няма да извика. В нашия случай се интересуваме от прилагането на мащабиране и панорамиране към обекта canvas, така че това е, което правим в последния ред, като използваме метода call. Това ще присвои манипулатора за мащабиране на диаграмата.

Това е! Вече можете да отворите index.html в любимия си браузър и да проверите своя лъскав нов график с мащабиране и панорамиране. Предполагам, че е доста очевидно, но можете да мащабирате с колелцето на мишката и панорамно, като щракнете+плъзнете графиката наоколо.

Възстановяване в първоначалното състояние

Смятаме, че изграждането на това беше толкова лесно, че трябва да добавим допълнителна функция към него :)

Това, което ще направим сега, е да добавим бутон от дясната страна на графиката, за да върнем платното в първоначалното състояние. Така че потребителите да могат да играят с функциите за мащабиране и панорамиране, но също така да върнат сюжета обратно в първоначалното състояние, без да опресняват страницата.

Първото нещо, което трябва да направим, е да добавим някакъв самообясняващ се html към индексния файл:

След това ще добавим друг самообясняващ се код към файла styles.css:

Ще запазим нашата кутия с инструменти скрита, докато скриптът не се зареди.

Сега трябва да внедрим javascript кода за функционалността на бутона за нулиране. Преминете към нашия файл plot.js и добавете следния код:

Това, което правим тук е:

  1. Вземете контейнера на кутията с инструменти, като използвате функцията за избор, която вече обяснихме в предишната публикация.
  2. След това прилагаме малко поле към него, за да го поддържаме отгоре подравнен с графиката.
  3. Накрая разкриваме кутията с инструменти, като задаваме видимостта на видима.

Сега преминаваме в кутията с инструменти и правим следното:

  1. Използваме отново функцията за избор, но този път хващаме нашия бутон „Нулиране“ по неговия ID
  2. Присвояваме манипулатор на щракване на нашия бутон
  3. Манипулаторът за щракване първо създава трансформацията на идентичността на мащаба, за да начертае отново графиката, както направихме, когато го начертахме за първи път
  4. След това използваме преход в платното, за да анимираме чертежа от текущото състояние към първоначалното състояние на „идентичност“. Тази анимация ще продължи 200 ms и ще използва функция за линейно облекчаване, както обикновено правим с css преходите. Можете да получите повече информация за преходите с D3 тук.
  5. След като преходът е дефиниран, ние извикваме функцията за обработка на мащаба, като използваме трансформацията, дефинирана в стъпка 3.

И ние сме готови! Вече имаме мащабиране, панорамиране и страхотен бутон за нулиране, за да започнем всичко отначало :)

Резултатът

В крайна сметка трябва да получите сюжет, който изглежда като този тук.

Нещата започнаха да изглеждат по-интересни сега, когато можем да взаимодействаме със сюжета. Все още има няколко други неща, които бихме искали да добавим към диаграмата на разпръскване, като мащабиране на кутията и някои подсказки, които да позволят на потребителите да виждат стойностите зад всяка точка. Но ще ги оставим за предстоящите публикации!

Благодарим ви, че прочетохте, надяваме се, че се наслаждавате и учите, както направихме ние с тази серия, и очакваме с нетърпение да видим вашите коментари! И ако ви е харесало, споделете!

Не пропускайте нашите публикации, последвайте ни сега в Twitter!