Kako raditi s općim uzorkom ažuriranja D3.js-a

Obilazak s primjenom modula za vizualizaciju s dinamičkim skupovima podataka

Uobičajeno je ukloniti postojeći element skalabilne vektorske grafike (SVG) pozivanjem d3.select('#chart').remove()prije prikazivanja novog grafikona.

Međutim, mogu postojati scenariji kada morate izraditi dinamičke vizualizacije iz izvora kao što su vanjski API-ji. Ovaj će vam članak pokazati kako to učiniti pomoću D3.js.

D3.js obrađuje dinamičke podatke prihvaćajući opći obrazac ažuriranja. To se obično opisuje kao spajanje podataka, nakon čega slijede operacije odabira unosa, ažuriranja i izlaska. Ovladavanje ovim metodama odabira omogućit će vam nesmetani prijelaz između stanja, omogućujući vam da smislenim pričama pričate podacima.

Početak rada

Zahtjevi

Izgradit ćemo graf koji ilustrira kretanje nekoliko fondova kojima se trguje na burzi (ETF) tijekom druge polovice 2018. Grafikon se sastoji od sljedećih alata:

  1. Zatvaranje grafikona cjenovnih linija
  2. Trakasti grafikon obujma trgovine
  3. 50-dnevni jednostavan pokretni prosjek
  4. Bollinger opsezi (20-dnevni jednostavni pokretni prosjek, sa standardnim odstupanjem postavljenim na 2,0)
  5. Grafikon otvoreno-visoko-nisko-zatvoreno (OHLC)
  6. Svijećnjaci

Ti se alati obično koriste u tehničkoj analizi dionica, robe i drugih vrijednosnih papira. Na primjer, trgovci mogu koristiti Bollinger vrpce i svijećnjake kako bi izveli obrasce koji predstavljaju signale za kupnju ili prodaju.

Ovako će grafikon izgledati:

Ovaj članak želi vas opremiti temeljnim teorijama spajanja podataka i obrascem unosa-ažuriranja-izlaska kako bi vam omogućio jednostavnu vizualizaciju dinamičkih skupova podataka. Pored toga, pokrivat ćemo odabir.join, koji je predstavljen u izdanju D3.js v5.8.0.

Općeniti obrazac ažuriranja

Suština općeg uzorka ažuriranja je odabir elemenata modela objektnog dokumenta (DOM), nakon čega slijedi vezivanje podataka za te elemente. Ti se elementi zatim kreiraju, ažuriraju ili uklanjaju kako bi predstavljali potrebne podatke.

Pridruživanje novih podataka

Pridruživanje podataka je mapiranje nbroja elemenata u skupu podataka s nbrojem odabranih čvorova modela objektnog dokumenta (DOM), specificirajući potrebnu radnju na DOM-u kako se podaci mijenjaju.

data()Metodu koristimo za mapiranje svake podatkovne točke na odgovarajući element u DOM odabiru. Uz to, dobra je praksa održavati postojanost objekta tako što ćete navesti ključ kao jedinstveni identifikator u svakoj podatkovnoj točki. Pogledajmo sljedeći primjer, koji je prvi korak prema prikazivanju traka obujma trgovine:

const bars = d3 .select('#volume-series') .selectAll(.'vol') .data(this.currentData, d => d['date']);

Gornji redak koda odabire sve elemente s klasom vol, nakon čega slijedi mapiranje this.currentDataniza s odabirom DOM elemenata pomoću data()metode.

Drugi neobavezni argument data()uzima podatkovnu točku kao ulaz i vraća datesvojstvo kao odabrani ključ za svaku podatkovnu točku.

Unesite / ažurirajte odabir

.enter()vraća odabir enter koji predstavlja elemente koje treba dodati kada je pridruženi niz duži od odabira. Nakon toga slijedi pozivanje .append()koje stvara ili ažurira elemente na DOM-u. To možemo provesti na sljedeći način:

bars .enter() .append('rect') .attr('class', 'vol') .merge(bars) .transition() .duration(750) .attr('x', d => this.xScale(d['date'])) .attr('y', d => yVolumeScale(d['volume'])) .attr('fill', (d, i) => { if (i === 0) { return '#03a678'; } else { // green bar if price is rising during that period, and red when price is falling return this.currentData[i - 1].close > d.close ? '#c0392b' : '#03a678'; } }) .attr('width', 1) .attr('height', d => this.height - yVolumeScale(d['volume']));

.merge()spaja ažuriranje i unosi odabire, prije primjene sljedećih lanaca metoda za stvaranje animacija između prijelaza i ažuriranje njihovih pridruženih atributa. Gornji blok koda omogućuje vam izvršavanje sljedećih radnji na odabranim DOM elementima:

  1. Odabir ažuriranja, koji se sastoji od podatkovnih točaka predstavljenih elementima na grafikonu, ažurirat će svoje atribute u skladu s tim.
  2. Stvaranje elemenata s klasom vol, s gore navedenim atributima definiranim unutar svakog elementa, jer se odabir unosa sastoji od točaka podataka koje nisu prikazane na grafikonu.

Izađi iz odabira

Uklonite stavke iz našeg skupa podataka slijedeći jednostavne korake u nastavku: bars.exit (). Remove ();

.exit()vraća izlazni odabir koji određuje podatkovne točke koje treba ukloniti. .remove()Metoda naknadno briše izbor iz DOM.

Evo kako će trake serija glasnoće reagirati na promjene podataka:

Zabilježite kako se DOM i odgovarajući atributi svakog elementa ažuriraju dok odabiremo drugi skup podataka:

Selection.join (od verzije 5.8.0)

Uvođenje selection.joinverzije D3.js v5.8.0 pojednostavilo je cijeli postupak spajanja podataka. Odvojeni funkcije su sada prošli rukovati unos , ažuriranje , te izlaz što pak vraća spojenih unos i ažuriranje izbor.

selection.join( enter => // enter.. , update => // update.. , exit => // exit.. ) // allows chained operations on the returned selections

U slučaju traka serije volumena, primjena selection.joinće rezultirati sljedećim promjenama na našem kodu:

//select, followed by updating data join const bars = d3 .select('#volume-series') .selectAll('.vol') .data(this.currentData, d => d['date']); bars.join( enter => enter .append('rect') .attr('class', 'vol') .attr('x', d => this.xScale(d['date'])) .attr('y', d => yVolumeScale(d['volume'])) .attr('fill', (d, i) => { if (i === 0) { return '#03a678'; } else { return this.currentData[i - 1].close > d.close ? '#c0392b' : '#03a678'; } }) .attr('width', 1) .attr('height', d => this.height - yVolumeScale(d['volume'])), update => update .transition() .duration(750) .attr('x', d => this.xScale(d['date'])) .attr('y', d => yVolumeScale(d['volume'])) .attr('fill', (d, i) => { if (i === 0) { return '#03a678'; } else { return this.currentData[i - 1].close > d.close ? '#c0392b' : '#03a678'; } }) .attr('width', 1) .attr('height', d => this.height - yVolumeScale(d['volume'])) );

Također, imajte na umu da smo unijeli neke promjene u animaciju traka. Umjesto prosljeđivanja transition()metode spojenim odabirima unosa i ažuriranja, ona se sada koristi u odabiru ažuriranja tako da će se prijelazi primijeniti samo kad se skup podataka promijeni.

Vraćeni odabiri za unos i ažuriranje potom se spajaju i vraćaju selection.join.

Bollinger bendovi

Slično tome, možemo se prijaviti selection.joinza prikazivanje Bollinger bendova. Prije generiranja opsega, moramo izračunati sljedeća svojstva svake podatkovne točke:

  1. 20-dnevni jednostavan pokretni prosjek.
  2. Gornji i donji pojas, koji imaju standardno odstupanje od 2,0 iznad i ispod 20-dnevnog jednostavnog pomičnog prosjeka.

Ovo je formula za izračunavanje standardne devijacije:

Sada ćemo gornju formulu prevesti u JavaScript kôd:

calculateBollingerBands(data, numberOfPricePoints) { let sumSquaredDifference = 0; return data.map((row, index, total) => { const start = Math.max(0, index - numberOfPricePoints); const end = index; // divide the sum with subset.length to obtain moving average const subset = total.slice(start, end + 1); const sum = subset.reduce((a, b) => { return a + b['close']; }, 0); const sumSquaredDifference = subset.reduce((a, b) => { const average = sum / subset.length; const dfferenceFromMean = b['close'] - average; const squaredDifferenceFromMean = Math.pow(dfferenceFromMean, 2); return a + squaredDifferenceFromMean; }, 0); const variance = sumSquaredDifference / subset.length; return { date: row['date'], average: sum / subset.length, standardDeviation: Math.sqrt(variance), upperBand: sum / subset.length + Math.sqrt(variance) * 2, lowerBand: sum / subset.length - Math.sqrt(variance) * 2 }; }); } . . // calculates simple moving average, and standard deviation over 20 days this.bollingerBandsData = this.calculateBollingerBands(validData, 19);

Brzo objašnjenje izračuna standardnog odstupanja i vrijednosti Bollinger pojasa na gornjem bloku koda je kako slijedi:

Za svaku iteraciju,

  1. Izračunajte prosjek cijene zatvaranja.
  2. Pronađite razliku između prosječne vrijednosti i cijene zatvaranja za tu točku podataka.
  3. Rezultat svake razlike izjednačite s kvadratom.
  4. Pronađite zbroj kvadrata razlika.
  5. Calculate the mean of the squared differences to get the variance
  6. Get the square root of the variance to obtain the standard deviation for each data point.
  7. Multiply the standard deviation by 2. Calculate the upper and lower band values by adding or subtracting the average with the multiplied value.

With the data points defined, we can then make use of selection.join to render Bollinger Bands:

// code not shown: rendering of upper and lower bands . . // bollinger bands area chart const area = d3 .area() .x(d => this.xScale(d['date'])) .y0(d => this.yScale(d['upperBand'])) .y1(d => this.yScale(d['lowerBand'])); const areaSelect = d3 .select('#chart') .select('svg') .select('g') .selectAll('.band-area') .data([this.bollingerBandsData]); areaSelect.join( enter => enter .append('path') .style('fill', 'darkgrey') .style('opacity', 0.2) .style('pointer-events', 'none') .attr('class', 'band-area') .attr('clip-path', 'url(#clip)') .attr('d', area), update => update .transition() .duration(750) .attr('d', area) );

This renders the area chart which denotes the area filled by the Bollinger Bands. On the update function, we can use the selection.transition()method to provide animated transitions on the update selection.

Candlesticks

The candlesticks chart displays the high, low, open and close prices of a stock for a specific period. Each candlestick represents a data point. Green represents when the stock closes higher while red represents when the stock closes at a lower value.

Unlike the Bollinger Bands, there is no need for additional calculations, as the prices are available in the existing dataset.

const bodyWidth = 5; const candlesticksLine = d3 .line() .x(d => d['x']) .y(d => d['y']); const candlesticksSelection = d3 .select('#chart') .select('g') .selectAll('.candlesticks') .data(this.currentData, d => d['volume']); candlesticksSelection.join(enter => { const candlesticksEnter = enter .append('g') .attr('class', 'candlesticks') .append('g') .attr('class', 'bars') .classed('up-day', d => d['close'] > d['open']) .classed('down-day', d => d['close'] <= d['open']); 

On the enter function, each candlestick is rendered based on its individual properties.

First and foremost, each candlestick group element is assigned a class of up-day if the close price is higher than the open price, and down-day if the close price is lower than or equal to the open-price.

candlesticksEnter .append('path') .classed('high-low', true) .attr('d', d => { return candlesticksLine([ { x: this.xScale(d['date']), y: this.yScale(d['high']) }, { x: this.xScale(d['date']), y: this.yScale(d['low']) } ]); });

Next, we append the path element, which represents the highest and lowest price of that day, to the above selection.

 candlesticksEnter .append('rect') .attr('x', d => this.xScale(d.date) - bodyWidth / 2) .attr('y', d => { return d['close'] > d['open'] ? this.yScale(d.close) : this.yScale(d.open); }) .attr('width', bodyWidth) .attr('height', d => { return d['close'] > d['open'] ? this.yScale(d.open) - this.yScale(d.close) : this.yScale(d.close) - this.yScale(d.open); }); });

This is followed by appending the rect element to the selection. The height of each rect element is directly proportionate to its day range, derived by subtracting the open price with the close price.

On our stylesheets, we will define the following CSS properties to our classes making the candlesticks red or green:

.bars.up-day path { stroke: #03a678; } .bars.down-day path { stroke: #c0392b; } .bars.up-day rect { fill: #03a678; } .bars.down-day rect { fill: #c0392b; }

This results in the rendering of the Bollinger Bands and candlesticks:

The new syntax has proven to be simpler and more intuitive than explicitly calling selection.enter, selection.append, selection.merge, and selection.remove.

Note that for those who are developing with D3.js’s v5.8.0 and beyond, it has been recommended by Mike Bostock that these users start using selection.join due to the above advantages.

Conclusion

The potential of D3.js is limitless and the above illustrations are merely the tip of the iceberg. Many satisfied users have created visualizations which are vastly more complex and sophisticated than the one show above. This list of free APIs may interest you if you are keen to embark on your own data visualization projects.

Feel free to check out the source code and the full demonstration of this project.

Thank you very much for reading this article. If you have any questions or suggestions, feel free to leave them on the comments below!

New to D3.js? You may refer to this article on the basics of implementing common chart components.

Special thanks to Debbie Leong for reviewing this article.

Additional references:

  1. D3.js API documentation
  2. Interactive demonstration of selection.join