Programowanie wizualizacji

Tworzenie elementów HTML

JavaScript posiada kilka funkcji które pozwalają na łatwiejsze modyfikowanie drzewa DOM tworzonego przez HTML.

Dowolny element HTML możemy stworzyć wykorzystując document.createElement(elementName), więc żeby stworzyć
pusty <div>: document.createElement('div').

Do tak stworzonego elementu możemy przypisać tekst wykorzystując znany już innerText. Elementy w HTML są w sobie zagnieżdżone, takie zagnieżdżanie w JavaScript tworzymy wykorzystując funkcję appendChild na istniejącym już elemencie.

Czyli, jeśli chcemy stworzyć fragment drzewa DOM z nagłówkiem strony i dodać go do elementu body wykonamy:

Elementy drzewa DOM można ostylować za pomocą pliku CSS i klas/id, lub wpisywać CSS „inline” przypisując go do elementu. Jeśli chcemy zmienić kolor i czcionkę naszego nagłówka możemy wykorzystać klucz style on stworzonym przez nas elemencie:

var header = document.createElement("h1");
header.innerText = "Wizualizacja danych";

header.style.fontFamily = "sans-serif";
header.style.color = "#bbb";

document.body.appendChild(header);

Wizualizacja za pomocą elementów HTML

Do zbudowania prostego wykresu możemy wykorzystać odpowiednio ostylowane elementy <div>.

Zaczniemy od przygotowania prostej tablicy danych opisującej średnią temperaturę w Polsce dla każdego miesiąca z 2015 roku, stworzymy też tablicę miesięcy:

var avgTemperature = [ 0.94, 0.95, 5.29, 7.97, 12.70, 16.26, 19.19, 20.63, 14.98, 8.13, 5.51, 4.27 ];
var monthNames = [ "styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", "lipiec", "sierpień", "wrzesień", "październik", "listopad", "grudzień" ];

Zaczniemy od stworzenia elementu <div> dla każdego
miesiąca:

for (var i = 0; i < avgTemperature.length; i = i + 1) {
  var div = document.createElement("div");

  div.innerText = monthNames[i] + ": " + avgTemperature[i];

  document.body.appendChild(div);
}

Możemy teraz wykorzystać kolejny element div który ostylujemy podając
stałą wysokość, oraz szerokość zależną od danych:

Najważniejszy fragment tego kodu to:

bg.style.width = avgTemperature[i] * 20 + "px";
bg.style.height = "2px";

Ustalamy wysokość elementu na dwa piksele, a jego szerokość na średnią temperaturę z danego miesiąca (przeskalowaną żeby była lepiej widoczna), również w pikselach (należy pamiętać o dodaniu"px").

Wizualizacja danych z API

Możemy teraz połączyć dane pobrane z API oraz powyższą technikę wizualizacji.

Najpierw musimy tak przefiltrować dane, żeby wyciągnąć z nich to co nas interesuje – załóżmy że chcemy zwizualizować liczbę osób żyjących w Polsce na przestrzeni lat.

Dane z naszego pliku JSON to tablica obiektów, każdy z tych obiektów posiada pole meta oraz data. Pole meta opisuje jakie informacje znajdują się w polu data. My poszukujemy takiego pola meta gdzie
wartość n1 oraz n2 jest równa "ogółem", zacznijmy od stworzenia funkcji filterData która ułatwi nam pracę z danymi z API:

function filterData(apiData) {
  var dataPoints;

  for (var i = 0; i < apiData.length; i = i + 1) {
    if (apiData[i].meta.n1 == "ogółem" && apiData[i].meta.n2 == "ogółem") {
      dataPoints = apiData[i].data.results[0].values;
    }
  }

  return dataPoints;
}

(&& oznacza logiczne „i”, || oznacza
logiczne „lub”)

Powyższą funkcję możemy połączyć z przykładem pobierającym i
wyświetlającym dane, żeby upewnić się czy wszystko działa:

Mamy teraz w wyniku tablicę obiektów z polami year oraz
val. Wartości val są bardzo duże, więc
znajdźmy maksimum i przeskalujmy je:

var max = 0;

for (var i = 0; i < filtered.length; i = i + 1) {
  if (filtered[i].val > max) {
    max = filtered[i].val;
  }
}

Powyższy prosty algorytm przechodzi po wszystkich wartościach
val i jeśli napotka taką, która jest większa od obecnego
max to zapamiętuje max jako szukaną wartość.

Łącząc powyższy kod z wcześniejszym, wizualizującym pogodę w Polsce,
otrzymamy:

Taki wykres za dużo nie pokazuje, możemy więc znaleźć jeszcze
najmniejszą wartość i przeskalować go od najmniejszej do największej:

Wykorzystywanie bibliotek JavaScript

Biblioteka to nic innego jak zestaw funkcji i obiektów napisanych już
przez kogoś, które możemy użyć w swoim projekcie.

Obecnie istnieje wiele sposobów na dodanie biblioteki do kodu
JavaScript nad którym pracujemy, ale najprostsza metoda to wciąż tag
<script> w HTML:

<script src="http://some/library.js"></script>

Wizualizacja wykorzystując Chart.js

Przygodę z bibliotekami zaczniemy od
Chart.js – jednej
z najprostszych w obsłudze bibliotek do wizualizacji danych.

Biblioteka ta działa w oparciu o element <canvas>,
musimy go stworzyć, dodać do document.body oraz
udostępnić context bibliotece Chart.js.
context to obiekt który udostępnia funkcje rysujące
działające w obrębie danego elementu <canvas>.

var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
document.body.appendChild(canvas);

Tak przygotowany <canvas> połączymy z pierwszym
przykładem o średnich temperaturach w Polsce:

(W zakładce HTML znajduje się tag <script> ładujący
bibliotekę Chart.js)

Jak widać konfiguracja wykresu odbywa się przez obiekt:

{
  type: "bar",
  data: {
    labels: monthNames,
    datasets: [
      {
        label: "Średnia temperatura w Polsce (2017)",
        data: avgTemperature
      }
    ]
  }
}
  • type – opisuje typ wykresu ("bar",
    "line", "radar", "pie",
    "scatter")
  • data – obiekt opisujący jakie dane znajdą się na
    wykresie

    • labels – tablica etykietek (powinno ich być tyle
      ile danych)
    • datasets – tablica zestawów danych jakie
      wizualizujemy, w naszym wypadku mamy tylko jedną serię danych

      • label – etykietka serii danych
      • data – tablica z danymi

Możemy teraz narysować wykres z dwoma seriami, np. porównać liczbę
kobiet i mężczyzn na przestrzeni lat.

Uogólnijmy funkcję filtrującą, tak że można jej przekazać parametry
n1 oraz n2:

function filterData(apiData, n1, n2) {
  var dataPoints;

  for (var i = 0; i < apiData.length; i = i + 1) {
    if (apiData[i].meta.n1 == n1 && apiData[i].meta.n2 == n2) {
      dataPoints = apiData[i].data.results[0].values;
    }
  }

  return dataPoints;
}

Możemy teraz połączyć Chart.js z danymi z pliku JSON:

Wizualizacja wykorzystując p5.js

Chart.js pozwala na szybkie stworzenie dobrze
wyglądającej wizualizacji, kosztem dość niskiego stopnia
konfigurowalności.

Druga biblioteka którą pokrótce poznamy to
p5.js, adaptacja znanego języka
Processing działająca w
JavaScript.

Tak samo jak w Chart.js musimy zacząć od załączenia
biblioteki w kodzie HTML naszego projektu:

<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>

p5.js wymaga stworzenia przez nas dwóch funkcji:
setup oraz draw.
setup uruchamiana jest tylko raz, a
draw uruchamia się około 60 razy na sekundę. Tak jak
wskazują nazwy, setup odpowiada za ustawienia naszego
szkicu, a draw za rysowanie rzeczy na ekranie.

Podstawowy skrypt wygląda następująco:

function setup() {
  createCanvas(600, 600);
}

function draw() {
  background(200);
}
  • createCanvas tworzy element
    <canvas> i dodaje go do drzewa DOM, więc nie
    musimy tego robić ręcznie tak jak w Chart.js, argumenty
    tej funkcji to wysokość i szerokość canvas
  • background wypełnia canvas jednolitym
    kolorem, można podać jako argument stopień szarości
    (0-255), lub trzy wartości opisujące kolor w RGB (każda
    z nich 0-255)

Zbudujemy teraz wizualizację która na osi y pokazuje przedziały
wiekowe, na osi x kolejne lata, a dane przedstawia jako okręgi
skalowane ilością ludzi oraz kolorowane bazując na płci.

Żeby pobrane dane były „widoczne” wewnątrz funkcji
draw stworzymy globalną zmienną data która
zostanie podmienion po wykonaniu fetch:

var data = [];

fetch("https://cors-anywhere.herokuapp.com/http://satyrium.pl:8080/work/media-3.0-datavis-course/data.json")
  .then(function(response) {
    return response.json();
  })
  .then(function(result) {
    data = result;
  });

function setup() {
  createCanvas(600, 600);
}

function draw() {
  background(240);


  // data będzie pustą tabelą, a po chwili wynikami z fetch, gdy dane zostaną pobrane
}

Wybierzmy z danych kilka przedziałów wiekowych:

var ageRanges = [ "0-4", "5-9", "10-14", "15-19", "20-24", "25-29", "30-34", "35-39", "40-44", "45-49", "50-54", "55-59", "60-64", "65-69", "70-74", "75-79", "80-84" ];

Wyciągnijmy też z danych dostępne lata:

var years = [];

if (data.length > 0) {
  for (var i = 0; i < data[0].data.results[0].values.length; i = i + 1) {
    years.push(data[0].data.results[0].values[i].year);
  }
}

Możemy teraz połączyć zakresy wieków i lata i wyciągnąć interesujące
nas dane wykorzystując wcześniejszą funkcję filterData:

for (var i = 0; i < ageRanges.length; i = i + 1) {
  // ageRanges[i] to dany przedział wiekowy - wyszukujemy kobiety i mężczyzn osobno
  var women = filterData(data, ageRanges[i], "kobiety");
  var men = filterData(data, ageRanges[i], "mężczyźni");

  // przechodzimy po wszystkich latach
  for (var j = 0; j < years.length; j = j + 1) {
    // "wyciągamy" dane gdzie rok jest naszym poszukiwanym rokiem
    var womenValue = 0;
    var menValue = 0;

    for (var k = 0; k < women.length; k = k + 1) {
      if (women[k].year == years[j]) {
        womenValue = women[k].val;
      }
      if (men[k].year == years[j]) {
        menValue = men[k].val;
      }
    }

    // przeskalowane położenie na osi x i y - dobrane "eksperymentalnie"
    var x = i * 35 + 15;
    var y = j * 25 + 20;

    // przeskalowanie wartości - dobrane "eksperymentalnie" - można by wykorzystać min i max z wcześniejszych przykładów
    var rWomen = womenValue / 100000;
    var rMen = menValue / 100000;

    fill(255, 0, 0, 150);
    ellipse(x, y, rWomen, rWomen);

    fill(0, 0, 255, 150);
    // rysujemy mężczyzn "przesuniętych" o 10 pikseli, żeby obie wartości były widoczne
    ellipse(x + 10, y, rMen, rMen);
  }
}

Dwie nowe funkcje należące do p5.js to:

  • fill – ustala kolor wypełnienia, można podać skalę
    szarości, RGB lub RGBA (gdzie „A” to „alpha” czyli stopień
    przeźroczystości)
  • ellipse – rysuje elipsę w podanym miejscu
    x/y, dwie kolejne wartości to promień w
    osi x oraz w osi y – jeśli promienie są
    sobie równe, to rysujemy okrąg

Finalny kod poniżej:

Jak widać p5.js wymaga nieco bardziej „niskopoziomowego”
myślenia do stworzenia wizualizacji, ale pozwala przez to na
zbudowanie zupełnie niestandardowych grafik.

Quiz:

#1 Wizualizację elementów strony internetowej można stworzyć w oparciu o:

#2 Wygląd wizualizacji stylujemy:

#3 Zestaw funkcji i obiektów, z których można skorzystać w projekcie to:

#4 Znak && oznacza:

Sprawdzam

Wynik