Słowem wstępu

W niniejszej publikacji zademonstruję wykorzystanie osi czasu jako narzędzia do przedstawienia swojej dotychczasowej historii, którą można pochwalić się w swoim CV.


Pakiet timelineR

Pierwszym rozwiązaniem pozwalającym uzyskać wizualizację osi czasu jest zastosowanie pakietu timelineR, który można pobrać z Githuba użytkownika timelyportfolio. Efekt, jaki możemy uzyskać dzięki temu pakietowi, będzie często w wielu przypadkach wystarczający.

Zanim jednak zaczniemy tworzyć nasze osie czasu, zainstalujmy i załadujmy wymagane pakiety.

#install.packages("devtools") #instalacja wymagana, gdy nie posiadamy pakietu devtools
library(devtools)

#devtools::install_github("ramnathv/htmlwidgets")
#devtools::install_github("timelyportfolio/timelineR")
library(timelineR)

library(RColorBrewer)

Załadowanie danych

Wpierw zdefiniujmy sobie dane, które chcielibyśmy zwizualizować. W tym celu do zmiennej osczasu_data przypisałem dataframe składający się z 3 kolumn (dat, wydarzeń i kolorów). Dwie pierwsze kolumny - time i name są wymagane, zaś trzecia (kolory) posłuży mi w późniejszym etapie do zastosowania odpowiednich kolorystyk.

osczasu_data <- data.frame(
  time = c(
    "30-Apr-10", "14-Febr-14", "29-Febr-16", "4-Oct-16", "5-Sept-19",
    "4-Jul-17", "20-Febr-17", "7-Nov-16", "14-May-19"
  ),
  name = c(
    'Ukończenie I LO w Łodzi z wyróżnieniem',
    'Inżynier budownictwa',
    'Magister inżynier budownictwa',
    'Rozpoczęcie nauki na WNE UW',
    'Licencjat z ekonomii (?)',
    'Rozpoczęcie pracy w Linkcity Poland S.A.',
    'Wolontariat w Fundacji EY',
    'Praca w Komisji UW HERE',
    'Ukończenie ścieżki MS Excel i VBA na WNE UW'
  ),
  stringsAsFactors = FALSE,
  kolory = c("yellowgreen","steelblue","steelblue","deeppink","deeppink",
             "deepskyblue", "darkorchid","slateblue","sienna"
  )
)

Pierwsze efekty

W celu zilustrowania naszych danych posłużymy się komendą d3kit_timeline jest to element htmlwidget z pojedynczą osią i ładnymi dla oka labelkami.

d3kit_timeline(
  osczasu_data,                    # nasze dane
  direction = "right",             # orientacja osi, tutaj pionowa + opisy z prawej strony
  timeFn = ~time,                  # dane czasowe, argument niewymagany, jeśli mamy kolumnę time w naszym dataframe
  textFn = htmlwidgets::JS(        # opisy labeli, tutaj ustawione za pomocą funkcji: "rok - nazwa wydarzenia"
    "
    function(d){
    return new Date(d.time).getFullYear() + ' - ' + d.name;
    }
    "
  ),
  width = 500,                     # szerokość naszej wizualizacji
  height = 300,                    # wysokość naszej wizualizacji
  labelBgColor = "darkorchid",     # kolorystyka labeli (jeden kolor do wszystkich)
  dotColor = "coral"               # kolor zaznaczeń na osi
)

Powyższy efekt jest ok, ale można by spróbować wyśrodkować nasz wykres, zrobić parę usprawnień.

Drugie podejście

Tym razem będziemy mieć zewnętrzną oś, do której dodamy nasze dane z d3kit_timeline.

add_axis(
  d3kit_timeline(                          # obróbka naszych danych - "funkcja" d3kit_timeline
    osczasu_data,                          # nasze wyjściowe dane
    direction = "left",                    # orientacja osi
    labelBgColor = '#377',                 # kolor labelek (tu w postaci kodu)
    linkColor = '#727',                    # kolor linii łączącej
    textFn = htmlwidgets::JS(              # opisy na labelkach
      "
      function(d){
      return new Date(d.time).getFullYear() + ' - ' + d.name;
      }
      "
    ),
    margin = list(left=20, right=250, top=20, bottom=20), # marginesy
    width = 800,                           # szerokość
    height = 450,                          # wysokość
    dotColor = "magenta"                   # kolor zaznaczeń na osi
    ),
  ticks = 10,                              # liczba podziałek na osi 
  tickSize = 5                             # wielkość podziałek
  )

Wersje alternatywne

Wersja druga wydaje się być stosunkowo przystępna. Zobaczmy jeszcze, jaki efekt uzyskamy jeśli zdecydujemy się na orientację poziomą i zechcemy posiadać labelki w różnych kolorach.

Przydadzą nam się tutaj pakiety scales i dplyr oraz definicje palet kolorów, z których będziemy chcieli skorzystać.

library(scales)

wlasnekolory <- htmlwidgets::JS(           # paleta odwołująca się do samodzielnie zdefiniowanych kolorów w dataframe'ie
  "function(d)
  { return d.kolory;
  }")

d3kit_timeline(
  osczasu_data,
  direction = "down",                      # orientacja pozioma: labelki pod osią
  layerGap = 50,                           # odległość pierwszego rzędu labelek od osi
  labella = list(maxPos = 500,             # reguły określające rozmieszczenie labelek
                 algorithm = "simple",     # algorytm określający jak powinny się układać labelki w kolejnych rzędach
                 stubWidth = 20),          # szerokość odstępy między liniami łączącymi w danym rzędzie labelek
  textFn = ~name,
  dotColor = wlasnekolory,                 # w tym przypadku definiujemy wspólne kolory dla zaznaczeń, linii i labelek
  labelBgColor = wlasnekolory,
  linkColor = wlasnekolory,
  margin = list(left=20, right=20, top=40, bottom=20),
  width = 900,
  height = 400
)

Orientacja pozioma jest z pewnością bardziej naturalna w odbiorze, niemniej należy mieć na uwadze, że wymaga ona stosunkowo krótkich opisów bo wtedy nasz rysunek staje się kolokwialnie mówiąc “rozlazły” i “pływające” linie łączące nie najlepiej wyglądają.

Zobaczmy jeszcze jak wyglądałby wykres z wykorzystaniem wbudowanej palety kolorów R.

library(dplyr)

colorJS <- htmlwidgets::JS("function(d){ return d.color; }") # paleta korzystająca z Rcolors

d3kit_timeline(
  osczasu_data %>%                            # korzystamy z operatora przetwarzania potokowego, by przetransformować nasz wyjściowy dataframe i dodać kolumnę z daną paletą kolorów (tutaj skorzystano z palety Paired)
    mutate( color = col_factor( palette = "Paired", domain = NULL)(.$name)),
  direction = "right",                        # orientacja pionowa, labele z prawej strony
  layerGap = 50,                              # odległość między osią a labelami
  textFn = ~name,                             # nazwy do labeli
  dotColor = colorJS,                         # jednolite kolory do zaznaczeń
  labelBgColor = colorJS,                     # jednolite kolory do labeli
  linkColor = colorJS,                        # jednolite kolory do linii łączących
  margin = list(left=300, right=20, top=30, bottom=20),  # marginesy
  width = 800,                                # szerokość
  height = 425                                # wysokość
)

Podsumowanie

Na powyższych przykładach widać, że pakiet timelineR jest przydatny i pozwala w łatwy i szybki sposób zwizualizować niezbyt skomplikowane osie czasowe. Jednak pakiet ten nie daje aż takiego efektu wow, czego niektórzy użytkownicy mogliby się spodziewać. Dlatego w kolejnej części zostanie przedstawiony drugi przydatny pakiet do wizualizji osi czasowych.


Pakiet VisualResume

Jak już wspomniano, w tej części przyjrzymy się innemu pakietowi do wizualizacji osi czasu. Tym razem jest to narzędzie głównie dedykowane do prezentacji w Curriculum Vitae (wszystkim dobrze znanym jako CV), stąd jego prosta do zapamiętania nazwa VisualResume i podobnie jak pierwszy pakiet, ten również pochodzi z Githuba (autorstwa użytkownika ndphillips). Niemniej oczywiście jest możliwe zilustrowanie samej osi czasu.

Zastosowany pakiet daje dodatkowe możliwości wizualizacji. Umożliwia wizualizację czynności/zadań, które trwają pewny okres czasu. Ponadto VisualResume pomoże nam zobrazować proste minigrafy drzewkowe, które można wykorzystać do przedstawienia swoich zainteresowań. Każdą z tych funkcji postarano się zademonstrować w niniejszej publikacji.

devtools::install_github("ndphillips/VisualResume")

library(VisualResume)

VisualResume::VisualResume(
  titles.left = c(                                                  # Nagłówki lewostronne
    "Janusz Fortuniak",                                             # Nagłówek 1 rzędu
    "",                                                             # Nagłówek 2 rzędu
    "Zwycięzcy robią to, czego przegranym się nie chciało."),       # Nagłówek 3 rzędu
  titles.left.cex = c(3, 1, 1.1),                                   # Wielkości nagłówków lewostronnych
  
  titles.right = c(                                                 # Nagłówki prawostronne
    "jfortuniak26[at]gmail.com", "", "tel. 500-653-xxx"),
  titles.right.cex = c(1.8, 1, 1.8),                                # Wielkości nagłówków prawostronnych
  
  timeline.labels = c("Dotychczasowa kariera", "Zainteresowania"),  # Nazwy sekcji
  
  timeline = data.frame(                                            # Dataframe z danymi do osi czasu
    title = c(                                                      # Tytuły poszczególnych etapów
      "I LO w Łodzi", "PŁ WBAIŚ", "PŁ WBAIŚ", "WNE UW", "Fundacja EY", "ULMA",
      "Linkcity Poland S.A.", "Linkcity Poland S.A.", "EC Industria", "Inbud"),
    sub = c(                                                        # Podtytuły
      "Profil mat-fiz", "Studia inż.", "Studia II stop.", "Studia licenjackie", "Wolontariat", "Staż",
      "Praktyka", "Junior Officer", "Asystent Projektanta", "Inżynier budowy"),
    start = c(2007.8, 2010.85, 2014.25, 2016.8, 2017.2, 2013.52, 2017.6, 2017.9, 2016.5, 2015.6), # początek wydarzenia
    end = c(2010.5, 2014.2, 2016.25, 2019.9, 2019.6, 2013.8, 2017.82, 2019.7, 2016.78,2015.7), # koniec wydarzenia
    side = c(1, 1, 1, 1, 0, 0, 0, 0, 1, 1)),                        # Położenie nad (1) lub pod (0) osią czasu
  
  milestones = data.frame(                                          # Kamienie milowe (dataframe)
    title = c("", "", ""),                                          # Tytuły nagłówków
    sub = c("inż. budownictwa", "mgr inż. budownictwa", "lic. nauk ekonom."), # Tytuły nagłówków 2 rzędu
    year = c(2014.01, 2016.2, 2019.7)),                             # Moment zajścia w czasie (rok z częściami dziesiętnymi)
  
  events = data.frame(                                              # Wydarzenia, które są ponumerowane na osi czasu (Kamienie milowe 2 rzędu)
    year = c(2008.4, 2010.4, 2014.15, 2016.2, 2019),                # Moment zajścia (rok z częsciami dziesiętnymi)
    title = c("Finalista XXXIV Olimpiady Geograficznej",            # Nazwy wydarzeń
              "Finalista XXXVI Olimpiady Geograficznej",
              "Praca inżynierska: Projekt budowlany z elementami projektu \nwykonawczego domu jednorodzinnego w technologii tradycyjnej",
              "Praca magisterska: Projekt stalowej konstrukcji wsporczej \nbanera reklamowego",
              "Ukończona ścieżka Ekonomiczna analiza danych w programie \nMicrosoft Excel i języku programowania VBA.")),
  events.cex = 1.3,                                                 # Wielkość czcionki nazw wydarzeń
  
  interests = list(                                                 # "Zainteresowania" = Lista max 4 obszarów, w których możemy dodatkowo zobrazować 5 pozycji (im częściej dana rzecz występuje tym bardziej jest ona uwydatniona)
    "Piłka nożna" = c(rep("La Liga", 3), rep("Liga Mistrzów", 3), rep("Biało-czerwoni", 1.5), rep("Premier League", 1.2)),
    "Programowanie" = c(rep("R", 3), rep("VBA", 4), rep("HTML & CSS", 1.5), rep("Python", 1)),
    "Japonia" = c(rep("Sudoku", 2), rep("Manga", 4), rep("Kultura", 1.3))),
  
  #font.family = "TeX Gyre Heros",                                  # Rodzina czcionek (tylko jeśli publikujemy do pdf)
  
  year.steps = 1,                                                   # Krok (liczba lat) na osi czasu
  
  col = "basel",                                                    # Kolorystyka zaznaczeń na osi czasu (można zastosować paletę z pakietu yarrr albo zdefiniować własny wektor 
  
  trans = 0.35                                                      # Przezroczystość poszczególnych zaznaczeń (0 - brak)
)

Ładne? Na pewno nie jest źle. Zwizualizowanie procesów nachodzących na siebie i biegnących równolegle w czasie z pewnością jest satysfakcjonujące. Możliwość zastosowania kamieni milowych z legendą, również jest mocno na plus. Wiadomo, końcowy efekt wymaga pewnych dopracowań, stosowania pewnych skrótów/uproszczeń.

Ale uwaga! Przedstawienie tak bogatego w treści wykresu może być kłopotliwe. Domyślne ustawienia wydruku wyniku w pakiecie VisualResume (8.5 x 11 cala) nie pokażą w Markdownie czytelnego obrazka. Dlatego tutaj autor zastosował sztuczkę, że powyższy kod zapuścił “normalnie” w RStudio, a następnie uzyskany output zapisał do obrazka, który to obrazek został umieszczony w kodzie. Stąd brak responsywności w tym outcomie (#aledziała).

Zapraszam do przetestowania i życzę jak najfajniejszych wizualizacji w przyszłości :).


Publikacja została wykonana w ramach zaliczenia przedmiotu Raportowanie i Wizualizacja danych w R na Wydziale Nauk Ekonomicznych (WNE) Uniwersytetu Warszawskiego.


Referencje:

  1. https://github.com/kristw/d3kit-timeline/blob/master/docs/api.md
  2. https://github.com/twitter/labella.js/blob/master/docs/Force.md/#constructor
  3. https://www.rdocumentation.org/packages/timelineR/versions/0.0.2/topics/d3kit_timeline
  4. https://github.com/ndphillips/VisualResume/
  5. https://www.rdocumentation.org/packages/VisualResume/versions/0.1.1/topics/VisualResume