Back to Blog

D3.js - krok po kroku. Data binding.

11 Apr 2016

W poprzednim poście opisałem, jak w D3 manipulować danymi. Dziś pokażemy, jak wykorzystać tę wiedzę w praktyce.

Dane

W przykładzie wykorzystamy dane o kategoriach projektów Daj Się poznać. Jakiś czas temu przeglądnąłem całą listę użytkowników i każdy projekt przyporządkowałem do co najmniej jednej z kategorii:

  • Web. Wszystkie projekty typowo webowe.
  • Mobile. Aplikacje mobilne.
  • IOT. Tak oznaczyłem wszystkie projekty w jakiś sposób związane z hardware.
  • Game. Gry, silniki do gier, narzędzia gamedev.
  • Tool. Narzędzia dewelowerskie i biblioteki.
  • Data. Do tej kategorii zaliczyłem wszystko, co związane z przetwarzaniem danych, AI, Machine Learning, algorytmiką, Big Data oraz mój projekt dsp-meta.
  • Windows. Różnorakie aplikacje na Windowsa, głównie UWP (ale nie uprzedzajmy faktów).

Niektóre projekty podpadają pod więcej niż jedną kategorię, dlatego ich ilość na wykresie nie będzie sumować się do 296.

Dane dostępne są w repozytorium. Po ich zagregowaniu otrzymamy:

Kategoria Liczba projektów
Web 121
Mobile 46
IOT 30
Game 50
Tool 53
Data 16
Windows 28

Dane do pokazania są, zatem do dzieła.

Ładowanie danych

Mimo że danych nie jest wiele, nie powinniśmy ich zaszywać w kodzie. Najlepiej będzie przygotować je w pliku i następnie załadować w skrypcie. D3 oferuje wiele helperów ładowania danych z zewnątrz w różnych formatach: XML, JSON czy CSV. Kategorie projektów DSP znajdują się w tym pliku CSV. Zostaną one załadowane w ten sposób:

d3.csv('data/categories.csv', function(categories){    
	//create chart here
})

Do funkcji csv() należy więc przekazać adres pliku na serwerze oraz callback, wewnątrz którego trzeba będzie zbudować wykres. Parametr categories to dane pobrane z serwera w postaci tablicy obiektów.

Tworzenie wykresu

Do napisania aplikacji posłużymy się szkieletem bazującym na npm i browserify, który opisałem w tym poście. Projekt daje nam zalążek strony HTML z dodanym kontenerem SVG:

<html>
    <body>
        <svg id='container'>
        </svg>
        <script src='/js/bundle.js'></script>
    </body>
</html>

Zaczniemy więc od dodania do niego elementów rect:

d3.select('#container')
  .selectAll('rect')
  .data(categories)
  .enter()
  .append('rect')

Dodajemy je w sposób, jaki opisałem poprzednim razem, za pomocą enter().append(...). Tu należy zrobić taką szuczkę i wykorzystać fakt, że selektor d3.select('#container').selectAll('rect') zwróci pustą kolekcję, więc element rect powstanie dla każdego wiersza danych.

Tutaj mała dygresja. Ktoś mógłby zapytać: Skoro selektor i tak ma zwrócić pustą kolekcję, to jakie znaczenie ma to, że napiszemy .selectAll('rect') a nie .selectAll('whatevah')?

Odpowiedź brzmi: nie ma to znaczenia, efekt będzie ten sam. Chodzi bardziej o zachowanie pewnej konwencji i czytelności. Po drugie, gdy trzeba będzie w przyszłości dodać kod, który również usuwa bądź aktualizuje te same tagi, to powyższy selektor będzie można reużyć. Dlatego najlepiej od razu wskazać, jakimi elementami zamierzamy manipulować.

Prostokąty pojawiają się na stronie, ale ich nie widać. Trzeba nadać im odpowiednie koordynaty:

d3.select('#container')
  .selectAll('rect')
  .data(categories)
  .enter()
  .append('rect')
  .attr('height',10)
  .attr('y',function(_,i){return i*14})
  .attr('width',function(d){return d.count; })

Myślę, że 3 nowe linijki opisują się same i nie muszę im pomagać. Po uruchomieniu skryptu na stronie pojawi się taki wykres:

Do ideału to mu jeszcze brakuje, ale nie od razu Kraków zbudowano. Dodajmy jeszcze marginesy i delikatniejszy kolor:

    d3.select('#container')
	  .selectAll('rect')
	  .data(categories)
	  .enter()
	  .append('rect')
	  .attr('height',10)
	  .attr('y',function(_,i){return i*14 + 10})
	  .attr('width',function(d){return d.count; })
	  .attr('x',10)
	  .style('fill', "#bdbdbd")

W tym momencie powinniśmy zadbać o skalowanie. Ilość projektów przekłada się jeden do jeden na długość prostokąta w pikselach, co oczywiście jest mało praktyczne. Na szczęście D3 daje nam do ręki gotowe mechanizmy do skalowania danych i przeliczania ich w łatwy, deklaratywny sposób na piksele. O tym już w kolejnym odcinku.

Źródła

Cały kod z dzisiejszego posta znajduje się w branchu categories-chart-1.