JavaScript biblioteka za vizualizaciju podataka pomoću web standarda. D3 oživljava podatke pomoću SVG-a, Canvas-a i HTML-a. D3 kombinira moćne tehnike vizualizacije i interakcije s pristupom koji se temelji na podacima i manipulaciji DOM-om, pružajući pune mogućnosti modernih preglednika i slobodu dizajniranja pravog vizualnog sučelja za podatke. Prikladan za kreiranje grafova, mapa, ili bilo koje druge reprezentacije podataka.
Za bolje praćenje ovog tutorijala, poželjno je bazično znanje HTML-a, CSS-a, JavaScript-a
Za početak potrebno je uključiti D3.js biblioteku u HTML stranicu
<script src="https://d3js.org/d3.v5.min.js"></script>
Nakon uključivanja bibiloteke dobiva se pristup globalnom d3 objeku u JavaScript kodu.
Najčešće korišten koncept su Selektori. Slično kao i kod JQuery i AngularJs bibiloteka, bazirani su na W3C selectors API.
D3 ima dvije metode selekcije: select
za selekciju jednog DOM elementa i selectAll
za višestruku selekciju.
Selektori se mogu ulančavati. Na primjer, može se selektirati svu djecu div elementa čiji je id
container
// selektiraj dom element sa id-jem container// te zatim selektiraj svu div djecu tog elementad3select("#container")selectAll("div")
Neki od jednostavnih selektora su sljedeći
d3select("tag") // gdje je tag ime tag-a DOM elementa, kao što su bodi ili divd3select("#id") // gdje id reprezentira točno odreženi DOM elementd3select(".class") // gdje je class CSS klasa DOM elementa
Nakon selekcije elemenata, na njima se obavljaju razne operacije kao što su mijenjanje svojstava (CSS klasa i stilova, textova, itd.).
Uzmimo da imamo sljedeći DOM element u HTML stranici
<div id="select-div"></div>
naredni kod tada selektira div prema id-u, te mijenja njegovu pozadinu u plavu boju
d3select("#select-div")style("background-color" "#039BE5");
Uzmimo da imamo sljedeće DOM elemente u HTML stranici
<div class="select-all-div"> Div #1 </div><div class="select-all-div"> Div #2 </div><div class="select-all-div"> Div #3 </div>
naredni kod tada mijenja pozadinu elementi u nasumičnu boju, na način da dohvati sve elemente sa CSS klasom select-all-div
te za svaku od njih poziva funkciju koja dinamički nasumično računa pozadinsku boju
Parametri za funkciju su sljedeći:
d
- podaci (eng. data) - koji se mogu predati selekciji - više u nastavku
i
- index trenutnog elementa, počevši od nule
d3selectAll(".select-all-div")style("background-color" function (d i) {return "hsl(" + random() * 360 + "," + 10 * (i + 1) + "%,50%)";});
D3.js omogućuje povezivanje podataka s selekcijom pomoću funkcije podataka i dinamičkih vrijednosti.
U sljedećem primjeru ažuriraju se svi elementi sa klasom .item
DIV-ovi sadržani u roditelju sa klasom .selection
.
Primjer html-a
<div class="selection"><div class="item">1</div><div class="item">2</div><div class="item">3</div></div>
Prilikom selekcije elemenata postavljaju se predefinirane vrijednosti pomoću funkcije data
. Prilikom postavljanja pozadinske boje za trenutni element u funkciji se dobiva jedan od elemenata predanih data
funkciji, ovisno o poziciji trenutnog elementa.
d3select(".selection")selectAll(".item")data(["#039BE5" "#00897B" "#00ACC1"])style("background-color" function (d) {return d;});
Ukoliko je predano više podataka nego što ima elemenata, ti su podaci jednostavno zanemareni. Isto vrijedi i u suprotnom. Ako ima manje podataka nego elemenata u selekciji, višak selekcije je zanemaren.
Ako imamo slučaj nejednakog broja podataka i selektiranih elemenata onda u tom slučaju možemo dodavati/brisati elemente.
Dodavanje elemenata:
Za kreiranje novog DOM elementa koristi se enter()
funkcija koja manipulira sa podacima koji ulaze u selekciju.
// selekcija spremljena u div variabluvar div = d3select(".selection")selectAll(".item")data(["#039BE5" "#00897B" "#00ACC1""#C7254E"]);// za elemente koji ulazediventer()// dodaj div element u selekcijuappend("div")// postavi njegovu CSS klasu na itemclassed("item" true)// postavi text novog elementa na index + 1text(function (d i) {return i + 1;})// animarija tranzicijetransition()// koja traje 0.5 sekundeduration(500)// ažuriraj svaki novi element sa novom pozadinskom bojomstyle("background-color" function (d) {return d;});// također potrebno je ažurirati i sve postojeće elementedivtransition()duration(500)style("background-color" function (d) {return d;});
Brisanje elemenata:
Za brisanje viška DOM elementa koristi se exit()
funkcija koja manipulira sa podacima koji izlaze iz selekcije.
// selekcija spremljena u div variabluvar div = d3select(".selection")selectAll(".item")data(["#039BE5" "#00897B"]);// izbrisi sve elemente koji izlaze iz selekcijedivexit()remove();// i naravno potrebno je ažurirati postojeće elementedivtransition()duration(500)style("background-color" function (d) {return d;});
U sljedećem primjeru prikazano dodavanje novog svg elementa u body element, te generiranje i dodavanje krugova u taj svg
// dimenzije prikazavar width = 960height = 400;// selekcijom dohvati element bodyvar svg = d3select("body")// na njega dodaj novi element svgappend("svg")// te postavi dimenzije tog svg elementaattr("width" width)attr("height" height);// generiraj raspon od 200 brojevavar nodes = d3range(200)// mapiraj svaki broj kao element sa poljem radius koji se računa nasumičnomap(function() {return {radius: random() * 12 + 4};})// prvvi čvor je korijen čija će pozicija odgovarati poziciji kurzora na ekranuroot = nodes[0]// skala bojacolor = d3scaleOrdinal(d3schemeCategory10);// korijen nema radius te na njega ne djeluju nikakve silerootradius = 0;rootfixed = true;// napravi selekciju na sve elemente circle// naravno elemenata još nemasvgselectAll("circle")// podaci su svi čvorovi osim prvog(korijena)data(nodesslice(1))// za sve elemente koji ulazeenter()// dodaj novi element cricleappend("circle")// postavi atribut r na radius čvora iz kolekcije predane data funkcijiattr("r" function(d) { return dradius; })// ispuni krug sa jednom od prvih 3 boje iz prije definirane palete bojastyle("fill" function(d i) { return color(i % 3); });// kreiranje nove simulacije generiranih čvorovavar force = d3forceSimulation(nodes)// vrijednost između 0 i 1 koja reprezentira preostalo vrijeme izvođenja simulacijealpha(0.3)// za svaki čvor postavi naboj - svi čvorovi imaj naboj -15 osim nultog čvora, odnosno korijena koji ima naboj -2000// negativan naboj znači da će se čvorovi odbijati, što je veća vrijednost veće je odbijanje// cilj je da se svi čvorovi jako odbijaju od korijenaforce("charge" d3forceManyBody()strength(function(d i){return i ? -15 : -2000}))// forsiraj sve čvorove prema sredini svg elementaforce('center' d3forceCenter(width / 2 height / 2))// nemoj primjenjivati sile ni po x ni po y osimforce("x" d3forceX(function(){return 0}))force("y" d3forceY(function(){return 0}))// svi čvorovi se mogu sudarati jedni s drugima te je radius u kojem se čvorovi sudaraju zaparavo radius čvoraforce('collision' d3forceCollide()radius(function(d) {return dradius}));// registriraj slušač na svaku iteraciju tajmera simulacijeforceon("tick" function(e) {// zato što je pri definiranju simulacije definirano// kako se čvorovi grupiraju u sredini te// kako se odbijaju jedan od drugog za -15 te svi od korijena za -2000// i kako se čvorovi sudaraju međusobno (nemogu pregaziti jedan drugog)// dovoljno je samo ažurirati pozicije svih krugova u svg elementu// zato što se sve operacije računanja koordinata izvšavaju u samoj simulaciji prema zadanim parametrimasvgselectAll("circle")attr("cx" function(d) { return dx; })attr("cy" function(d) { return dy; });});// regist slušač na pokrete kurzora unutar svg elementasvgon("mousemove" function() {// dohvati trenutnu poziciju kurzoravar p1 = d3mouse(this);// postavi korijen na trenutnu poziciju kurzora// zato što korijen ima jako velik negativan naboj i nalazi se na istoj pozziciji kao i kurzor// stvara se osjećaj kako se svi čvorovi odbijaju od kurzorarootx = p1[0];rooty = p1[1];// simulacija po default-u završava za 300 iteracija// zato je potrebno "resetirati" preostali broj iteracija simulacijeforcealpha(0.3);});
Pretpostavimo da imamo sljedeće podatke zapisane u csv datoteci
year | value |
---|---|
2011 | 45 |
2012 | 47 |
2013 | 52 |
2014 | 70 |
2016 | 78 |
2015 | 75 |
Kao i uvijek potrebno je dodati novi svg element ili selektirati postojeći
// dimenzije prikazavar width = 960height = 400//margina oko grafamargin = 200;// selekcijom dohvati element bodyvar svg = d3select("body")// na njega dodaj novi element svgappend("svg")// te postavi dimenzije tog svg elementaattr("width" width)attr("height" height);// smanji visinu i širinu za iznos marginewidth -= margin;height -= margin;
Nakon toga potrebno je kreirati skale za x i y os. Sljedeći kod prikazuje način kreiranja raspona vrijednosti za svaku od osi.
// kreira se diskretna skala sa rasponom od 0 do duljine grafa// sa funkcijom padding dodaje se razmak između stupacavar xScale = d3scaleBand()range ([0 width])padding(0.4)// kreira se linearna skala koja će prikazivati cijene// u rasponu visine grafayScale = d3scaleLinear()range ([height 0]);
Na svg element dodaje se element g u koji će se dodati x i y osi i stupci grafa
// na svg element dodaj novi element g (group)var g = svgappend("g")//CSS attribut kako bi se graf pozicionirao sa marginomattr("transform" "translate(" + 100 + "," + 100 + ")");
Sljedeći korak je učitavanje podataka sa servera (u ovom primjeru iz csv datoteke)
// čitaj csv datoteku "data.csv"d3csv("data.csv")// ako je pronađena i uspješno pročitanathen(function(data) {// radi sa podacima})// uhvati i obradi greškucatch(function(error){ throw error;});
Dohvaćeni podaci koriste se za izgradnju grafa. Variabla data
predstavlja referencu na kolekciju podataka pročitanih iz csv datoteke.
Nakon što su podaci učitani, na x i y osi postavljamo vrijednosti domena.
// na skalu x osi postavi sve godinexScaledomain(datamap(function(d) { return dyear; }));// na skalu y osi postavi raspon od 0 do maximalne vrijednosti u podacimayScaledomain([0 d3max(data function(d) { return dvalue; })]);
Nakon postavljanja vrijednosti skala, dodaj ih u graf. Najprije se u prethodno kreirani element g dodaj novi element g koji će sadržavati x i y osi.
// dodaj novi element ggappend("g")// koristi CSS svojstvo kako bi pozicionirao x os prema dnu svg-aattr("transform" "translate(0," + height + ")")// dodaj skalu u novi g elementcall(d3axisBottom(xScale));// dodaj novi element ggappend("g")// dodaj skalu u novi g elementcall(d3axisLeft(y)// zato što y os prikazuje vrijednost onda definiraj vlastiti format labela prikazanih na y ositickFormat(function(d){ return "$" + d; })// ograniči broj prikazanih vrijednosti labela y ositicks(10))
Sljedeći korak je dodavanje podataka u graf. Sljedeći isječak prikazuje prikaz podataka dohvaćenih prije prikazanom metodom čitanja csv datoteke.
// napravi selekciju na sve elemente sa style klasom bargselectAll(".bar")// kao podatke prenesi podatke pročitane iz csv datotekedata(data)// za sve podakte koji ne postoje (za sve nove elemente)enter()// dodaj novi element rect(pravokutnik)append("rect")// postavi style klasu novog rect elementa na barattr("class" "bar")// svaki bar postavlja na pripadajuću x i y pozicijuattr("x" function(d) { return xScale(dyear); })attr("y" function(d) { return yScale(dvalue); })// širina stupaca određena je scaleBand() funkciom.// Stoga, x skala vraća izračunatu širinu iz raspona(range) i paddinga predanih x skaliattr("width" xScalebandwidth())// visina stupaca je izračunata kao visina yScale(d.value).// To jest visina svg-a minus trenutna y vrijednost stupca sa y skale.// Napomena: y vrijednost je vrh stupcaattr("height" function(d) { return height - yScale(dvalue); });
Nakon što su podaci dodani u graf potrebno je te podatke označiti labelama. Labele se u graf dodaje dodavanjem text elemenata u svg.
Sljedeći isječak prikazuje dodavanje naslova, te labela za x i y os
// Naslov// dodaj novi text element u svgsvgappend("text")// postavi css svojstvo transformattr("transform" "translate(100,0)")// postavi ga na poziciju 50, 50attr("x" 50)attr("y" 50)// povećaj veličinu fontaattr("font-size" "24px")// postavi stvarni tekst koji će element prikazivatitext("CSV Foods Stock Price")// x-os// nadovezujemo se na prije objašnjen kod za dodavanje x skalegappend("g")attr("transform" "translate(0," + height + ")")call(d3axisBottom(xScale))// na postojeću x skalu dodaj novi text elementappend("text")// postavi x i y pozicijuattr("y" height - 250)attr("x" width - 100)// postavi css svojstvaattr("text-anchor" "end")attr("stroke" "black")// postavi tekst koji će se prikazivatitext("Year");// y-os// nadovezujemo se na prije objašnjen kod za dodavanje y skalegappend("g")call(d3axisLeft(yScale)tickFormat(function(d){return "$" + d;})ticks(10))//na postojeću y skalu dodaj novi text elementappend("text")// rotiraj text za 90 stupnjeva obrnuto smjera kazaljkeattr("transform" "rotate(-90)")// postavi y pozicijuattr("y" 6)// postavi css svojstvaattr("dy" "-5.1em")attr("text-anchor" "end")attr("stroke" "black")// postavi tekst koji će se prikazivatitext("Stock Price");
Statički graf u današnje vrijeme je dosta dosadan. Potrebno je napraviti različite animacije kako bi korisniku bio zanimljviji.
Sljedeći isječak prikazuje dodavanje animacija na prethodno kreiran graf. Konkretno, registriraju se slušači za pokrete miša preko stupaca grafa.
// kod za kreiranje grafa prije objašnjengselectAll(".bar")data(data)enter()append("rect")attr("class" "bar")attr("x" function(d) { return x(dyear); })attr("y" function(d) { return y(dvalue); })attr("width" xbandwidth())attr("height" function(d) { return height - y(dvalue); })// kada korisnik mišem uđe unutar granica novog elementa rect pozovi funkciju onMouseOveron("mouseover" onMouseOver)// kada korisnik mišem iziđe izvan granica novog elementa rect pozovi funkciju onMouseOuton("mouseout" onMouseOut)// animiraj dodavanje novih elemenata tranzicijomtransition()// linearno pojavljivanje novih elemenataease(d3easeLinear)// u trajanju od 400 milisekundiduration(400)// odgodi svako sljedeće dodavanje novog elementa za 50 milisekundidelay(function (d i) {return i * 50;});
Slušač onMouseOver
povećava širinu i visinu stupca te mijenja boju stupca u narančastu. Također se prikazuje i y vrijednost stupca kao tekst.
// funkcija prima parametre// @d = podaci vezani za element// @i = index elementafunction onMouseOver(d i) {// odaberi trenutni element za koji je pozvan ovaj slušačd3select(this)// postavi mu style klasu highlightattr('class' 'highlight')// animacija tranzicijetransition()// u trajanju od 400 milisekundiduration(400)// povećaj širinu za 5 pixelaattr('width' xScalebandwidth() + 5)// povećaj visinu za 10 pixelaattr("height" function(d) { return height - yScale(dvalue) + 10; })// pomakni y poziciju grafa 10 prema doljeattr("y" function(d) { return yScale(dvalue) - 10; });// na g element dodaj novi text elementgappend("text")// dodaj style klasu valattr('class' 'val')// x pozicija je ista kao i kod stupcaattr('x' function() {return xScale(dyear);})// y pozicija elementa je za 15 pixela manja od vrhaattr('y' function() {return yScale(dvalue) - 15;})// u tekst postavi vrijednosttext(function() {return [ '$' +dvalue];});}
Slušač onMouseOut
poništava sve napravljene promjene.
// funkcija prima parametre// @d = podaci vezani za element// @i = index elementafunction onMouseOut(d i) {// odaberi trenutni element za koji je pozvan ovaj slušačd3select(this)// vrati natrag style klasu barattr('class' 'bar')// animacija tranzicijetransition()// u trajanju od 400 milisekundiduration(400)// postavi širinu na početnu širinuattr('width' xScalebandwidth())// postavi visinu na početnu vrijednostattr("height" function(d) { return height - yScale(dvalue); })// vrati y poziciju na početnu vrijednostattr("y" function(d) { return yScale(dvalue); });// odaberi text element koji je prikazivao vrijednost stupacad3selectAll('.val')// izbrisi sve odabrane elementeremove()}
Konačni kod za primjer stupčastog grafa je sljedeći:
var width = 960height = 400margin = 200;var svg = d3select("body")append("svg")attr("width" width)attr("height" height);width -= margin;height -= margin;var xScale = d3scaleBand()range ([0 width])padding(0.4)yScale = d3scaleLinear()range ([height 0]);var g = svgappend("g")attr("transform" "translate(" + 100 + "," + 100 + ")");d3csv("data.csv")then(function(data) {xScaledomain(datamap(function(d) { return dyear; }));yScaledomain([0 d3max(data function(d) { return dvalue; })]);gappend("g")attr("transform" "translate(0," + height + ")")call(d3axisBottom(xScale))append("text")attr("y" height - 250)attr("x" width - 100)attr("text-anchor" "end")attr("stroke" "black")text("Year");gappend("g")call(d3axisLeft(yScale)tickFormat(function(d){ return "$" + d; })ticks(10))append("text")attr("transform" "rotate(-90)")attr("y" 6)attr("dy" "-5.1em")attr("text-anchor" "end")attr("stroke" "black")text("Stock Price");gselectAll(".bar")data(data)enter()append("rect")attr("class" "bar")attr("x" function(d) { return xScale(dyear); })attr("y" function(d) { return yScale(dvalue); })attr("width" xScalebandwidth())attr("height" function(d) { return height - yScale(dvalue); })on("mouseover" onMouseOver)on("mouseout" onMouseOut)transition()ease(d3easeLinear)duration(400)delay(function (d i) {return i * 50;});svgappend("text")attr("transform" "translate(100,0)")attr("x" 50)attr("y" 50)attr("font-size" "24px")text("CSV Foods Stock Price")})catch(function(error){ throw error;});function onMouseOver(d i) {d3select(this)attr('class' 'highlight')transition()duration(400)attr('width' xScalebandwidth() + 5)attr("height" function(d) { return height - yScale(dvalue) + 10; })attr("y" function(d) { return yScale(dvalue) - 10; });gappend("text")attr('class' 'val')attr('x' function() {return xScale(dyear);})attr('y' function() {return yScale(dvalue) - 15;})text(function() {return [ '$' +dvalue];});}function onMouseOut(d i) {d3select(this)attr('class' 'bar')transition()duration(400)attr('width' xScalebandwidth())attr("height" function(d) { return height - yScale(dvalue); })attr("y" function(d) { return yScale(dvalue); });d3selectAll('.val')remove()}
Pretpostavimo da imamo sljedeće podatke zapisane u csv datoteci
browser | percent |
---|---|
Chrome | 73.70 |
IE/Edge | 4.90 |
Firefox | 15.40 |
Safari | ,3.60 |
Opera | 1.00 |
Pita graf gradimo na sličan način kao i prethodni graf.
var width = 500height = 400radius = min(width height) / 2;var svg = d3select("body")append("svg")attr("width" width)attr("height" height);var g = svgappend("g")attr("transform" "translate(" + width / 2 + "," + height / 2 + ")");// kreiranje skale boja za vrijednostivar color = d3scaleOrdinal(['#4daf4a''#377eb8''#ff7f00''#984ea3''#e41a1c']);// funkcija koja će vraćat vrijednost (percent) podatka dvar pie = d3pie()value(function(d) {return dpercent;});// kreiraj novi lukvar path = d3arc()// sa vanjskim radiusom za 10 manjim od stvarnogouterRadius(radius - 10)// i unutarnjim radiusom od 0innerRadius(0);// kreiraj novi lukvar label = d3arc()// sa vanjskim radiusom radiusouterRadius(radius)// i unutarnjim radiusom za 80 manjim od stvarnoginnerRadius(radius - 80);//ovime se postiglo pozicioniraje labela blizu ruba grafad3csv("browseruse.csv")then(function(data) {// dodaj sve podatkevar arc = gselectAll(".arc")data(pie(data))enter()append("g")attr("class" "arc");// na svaki element dodaj element patharcappend("path")// postavi postavke radiusa kao attribut dattr("d" path)// popuni sa bojom iz skaleattr("fill" function(d) { return color(ddatabrowser); })// kada korisnik prijeđe mišom dodaj style klasu highlighton("mouseover" function(d i){d3select(this)attr("class""highlight");})// kada korisnik pomakne miš iz elementa postavi style klasu na null, čime se automatski čita fill svojstvoon("mouseout" function(d i){d3select(this)attr("class"null);});// dodaj labele u svaki lukarcappend("text")// translatiraj labelu na odgovarajuću pozicijuattr("transform" function(d) {return "translate(" + labelcentroid(d) + ")";})// postavi tekst labeletext(function(d) { return ddatabrowser; });})catch(function(error){consolelog(error)});
Provjeriti primjere na d3js.org. Pokušati prebaciti primjere na najnoviju verziju v5.
AutorRobert Šajina