Back to Blog

D3.js - krok po kroku. Etykiety i osie.

24 Apr 2016

W ostatnim poście opisałem kilka praktyk, jakimi kierowałem się, konstruując powyższy wykres. Pora wcielić te zasady w życie i wyrzeźbić trochę kodu.

Sortowanie

Zacznijmy od posortowania danych według ilości projektów. Oto jak zrealizować to w czystym D3.js:

categories.sort(function(a, b) { return d3.descending(a.count, b.count); });

Można to zrobić krócej, jednym ze sposobów jest użycie biblioteki d3-jetpack, która opakowuje ten zapis w bardziej zwięzłą formę:

categories.sort(d3.descendingKey('count'));

Etykiety

Nanieśmy teraz liczby projektów na każdym z prostokątów. Ponieważ tekst i prostokąt to jeden logiczny element wykresu, dlatego warto je zgrupować i każdą parę włączyć pod jeden wspólny znacznik. Pozwoli to współdzielić koordynaty dla obu znaczników i dzięki temu lepiej nimi zarządzać. W SVG grupy tworzy się za pomocą elementu <g></g>.

var bars =
    d3.select('#container')
      .selectAll('rect')
      .data(categories)
      .enter()
      .append('g')      
      //this will be a common absolute position for bar and text	  
      .attr('transform', function(x) {return 'translate(0, '+yScale(x.category) +')'})

Grupy w odróżnieniu od innych znaczników SVG nie korzystają z dwóch atrybutów x i y, tylko jeden atrybut transform przyjmujący funkcję translate(). D3.js w standardzie nie oferuje żadnych ułatwień i domyślnie trzeba kleić takiego smutnego stringa jak powyżej. Na szczęście z pomocą znów przychodzi d3-jetpack, który dostarcza wygodną funkcję translate():

var bars =
    d3.select('#container')
      .selectAll('rect')
      .data(categories)
      .enter()
      .append('g')   
      //this will be a common absolute position for bar and text
      .translate(function(x) {return[0,yScale(x.category)]})  

Gdy mamy utworzone grupy, pozostaje już tylko umieścić w nich belki i tekst:

bars.append('rect')       
    .attr('height',yScale.rangeBand())
    .attr('width',function(d){return xScale(d.count); })              
    .style('fill','#bdbdbd')

bars.append('text')
    //class .number is used in CSS for proper text alignment
    .classed('number',true)
    //putting text in the middle of a bar width
    .attr('y',yScale.rangeBand()/2)
    //putting text at the end of a bar
    .attr('x',function(d){return xScale(d.count)-3; })                     
    .attr('dy','.35em')
    .text(function(d){return d.count});

'rect' nie potrzebuje już pozycji pionowej, natomiast tekst musi dostać niewielki offset względem grupy, aby wyśrodkować go względem szerokości belki (pamietając z wcześniejszego posta, że w przykładzie wynosi ona yScale.rangeBand()).

Oś pionowa

Aby dodać nazwy kategorii, trzeba zrobić im najpierw trochę miejsca z lewej strony. Włączmy zatem wszystkie belki do jednej nadrzędnej grupy i nadajmy jej marginesy:

var chart =
    d3.select('#container')
      .append('g')
      //make some room for axis labels
      .translate([100,0])

var bars = chart
      .selectAll('rect')
      .data(categories)
      .enter()
      .append('g')      
      .translate(function(x) {return[0,yScale(x.category)]})
//(...)

Do tworzenia osi w D3.js służy specjalna funkcja d3.axis. Jest to pójście o krok dalej w stosunku do tego, co rysowaliśmy do tej pory. d3.axis zwróci nam kompletny komponent osi gotowej do naniesienia wprost na graf. Aby z niej skorzystać, będziemy potrzebowali jedynie dwóch rzeczy: skali, na której bazie oś ma być wyrysowana oraz kontenera, w którym ma ona się znaleźć.

Skalę już mamy. Jest to funkcja yScale, którą utworzyliśmy w jednym z poprzednich postów:

var yAxis = d3.svg.axis().scale(yScale).orient('left')

.orient('left') wskazuje, że etykiety osi muszą znaleźć się po jej lewej stronie (co od razu definiuje również pionowe położenie). Kontenerem natomiast będzie grupa chart:

yAxis(chart)

Tyle! Po dodaniu osi i etykiet wykres będzie wyglądać w ten sposób:

121535046302816WebToolGameMobileIOTWindowsData

Jak widać, d3.svg.axis() ułoży nam odpowiednio etykiety na wykresie praktycznie zerowym nakładem pracy. Sama oś jednak nie będzie potrzebna, można ją śmiało usunąć. Można to zrobić za pomocą CSS, ponieważ D3 nadaje komponentom odpowiednie klasy, do których możemy się odwołać później w stylach:

#container path.domain{
    display: none
}

Pozostaje jeszcze dodać nagłówek:

d3.select('#container')         
  .append('text')
  .classed('title',true)//class .title will be used in CSS
  .text('DSP \'16 Projects' )
  .attr('y', 30/2) //one half of the top margin of the whole chart
  .attr('dy','.35em')  

I to już ostatni element statycznej wersji wykresu w D3.js. Graf na żywo można zobaczyć na bl.ocks.org. Pełny kod znajduje się w branchu categories-chart-2.