Un tur ghidat privind implementarea modulelor de vizualizare cu seturi de date dinamice

Este obișnuit să eliminați elementul existent Scalable Vector Graphics (SVG) apelând d3.select('#chart').remove(), înainte de a reda o nouă diagramă.

Cu toate acestea, pot exista scenarii când trebuie să produceți vizualizări dinamice din surse, cum ar fi API-urile externe. Acest articol vă va arăta cum să faceți acest lucru folosind D3.js.

D3.js gestionează datele dinamice adoptând modelul general de actualizare. Aceasta este descrisă în mod obișnuit ca o asociere de date, urmată de operații pe selectările de introducere, actualizare și ieșire. Stăpânirea acestor metode de selecție vă va permite să produceți tranziții fără probleme între state, permițându-vă să spuneți povești semnificative cu date.

Noțiuni de bază

Cerințe

Vom construi un grafic care ilustrează mișcarea câtorva fonduri tranzacționate la bursă (ETF) în a doua jumătate a anului 2018. Graficul constă din următoarele instrumente:

  1. Diagrama liniei de închidere a prețurilor
  2. Volumul comerțului grafic cu bare
  3. 50 de zile simplu medie mobilă
  4. Trupe Bollinger (Medie mobilă simplă de 20 de zile, cu deviația standard setată la 2,0)
  5. Deschidere-mare-mică-închidere (OHLC) grafic
  6. Sfeșnice

Aceste instrumente sunt utilizate în mod obișnuit în analiza tehnică a stocurilor, mărfurilor și a altor valori mobiliare. De exemplu, comercianții pot folosi benzile Bollinger și sfeșnice pentru a obține modele care reprezintă semnale de cumpărare sau vânzare.

Așa va arăta graficul:

Cum sa lucrati cu modelul de actualizare general al D3js
Dezvoltat de D3.js. Observați modul în care graficul răspunde la interacțiunile utilizatorilor și la modificările de date sau de stare.

Acest articol își propune să vă echipeze cu teoriile fundamentale ale îmbinărilor de date și modelul de introducere-actualizare-ieșire pentru a vă permite să vizualizați cu ușurință seturi de date dinamice. În plus, vom acoperi selecție.alăturați-vă, care este introdus în versiunea v5.8.0 a lui D3.js.

Modelul general de actualizare

Esența modelului general de actualizare este selectarea elementelor Modelului obiectului documentului (DOM), urmată de legarea datelor la aceste elemente. Aceste elemente sunt apoi create, actualizate sau eliminate, pentru a reprezenta datele necesare.

Alăturarea datelor noi

Unirea datelor este cartografierea n numărul de elemente din setul de date cu nnumărul de noduri selectate Document Object Model (DOM), specificând acțiunea necesară către DOM pe măsură ce datele se modifică.

Noi folosim data() metoda de mapare a fiecărui punct de date la un element corespunzător din selecția DOM. În plus, este o bună practică de întreținut constanta obiectului prin specificarea unei chei ca identificator unic în fiecare punct de date. Să aruncăm o privire la următorul exemplu, care este primul pas către redarea barelor volumului tranzacțiilor:

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

Linia de cod de mai sus selectează toate elementele din clasă vol , urmată de cartografierea this.currentData matrice cu selectarea elementelor DOM folosind data() metodă.

Al doilea argument opțional al data() ia un punct de date ca intrare și returnează fișierul date proprietate ca cheie selectată pentru fiecare punct de date.

Introduceți / actualizați selecția

.enter() returnează o selecție enter care reprezintă elementele care trebuie adăugate atunci când matricea unită este mai lungă decât selecția. Aceasta este urmată de apel .append(), care creează sau actualizează elemente pe DOM. Putem implementa acest lucru în modul următor:

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() îmbină actualizarea și introduceți selecții, înainte de a aplica lanțurile de metode ulterioare pentru a crea animații între tranziții și pentru a actualiza atributele asociate acestora. Blocul de cod de mai sus vă permite să efectuați următoarele acțiuni asupra elementelor DOM selectate:

  1. Selecția de actualizare, care constă din puncte de date reprezentate de <rect> elementele din grafic, vor avea atributele actualizate corespunzător.
  2. Crearea <rect> elemente cu clasa vol, cu atributele de mai sus definite în cadrul fiecărui element pe măsură ce selecția Enter este formată din puncte de date care nu sunt reprezentate pe grafic.

Ieșiți din selecție

Eliminați elementele din setul nostru de date urmând pașii simpli de mai jos: bars.exit (). Remove ();

.exit() returnează o selecție de ieșire, care specifică punctele de date care trebuie eliminate. .remove() metodă șterge ulterior selecția din DOM.

Acesta este modul în care barele din seria de volume vor răspunde la modificările datelor:

1612142471 604 Cum sa lucrati cu modelul de actualizare general al D3js
Observați cum se schimbă barele pe măsură ce comutăm între seturile de date.

Rețineți modul în care DOM și atributele respective ale fiecăruia <rect>elementul este actualizat pe măsură ce selectăm un set de date diferit:

1612142471 176 Cum sa lucrati cu modelul de actualizare general al D3js
Observați modificările din DOM prin Chrome DevTools încorporat.

Selection.join (începând cu v5.8.0)

Introducerea selection.join în v5.8.0 din D3.js a simplificat întregul proces de asociere a datelor. Funcțiile separate sunt acum trecute pentru a gestiona intrarea, Actualizați, și exit, care la rândul său returnează selecțiile de introducere și actualizare combinate.

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

În cazul barelor seriei de volume, aplicarea selection.join va avea ca rezultat următoarele modificări ale codului nostru:

//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']))
);

De asemenea, rețineți că am făcut câteva modificări la animația barelor. În loc să treacă transition() metoda pentru selectarea combinată de introducere și actualizare, este acum utilizată în selecția de actualizare, astfel încât tranzițiile vor fi aplicate numai atunci când setul de date s-a modificat.

Selecțiile de introducere și actualizare returnate sunt apoi îmbinate și returnate de selection.join.

Trupe Bollinger

În mod similar, putem aplica selection.join despre redarea benzilor Bollinger. Înainte de a reda benzile, ni se cere să calculăm următoarele proprietăți ale fiecărui punct de date:

  1. Media mobilă simplă de 20 de zile.
  2. Banda superioară și inferioară, care au o abatere standard de 2,0 peste și sub media mobilă simplă de 20 de zile, respectiv.

Aceasta este formula pentru calcularea abaterii standard:

0*aVvmssYG1cPl2aDB
Credite: Academia Khan

Acum, vom traduce formula de mai sus în cod JavaScript:

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);

O explicație rapidă a calculului abaterii standard și a valorilor benzii Bollinger pe blocul de cod de mai sus este următoarea:

Pentru fiecare iterație,

  1. Calculați media prețului închis.
  2. Găsiți diferența dintre valoarea medie și prețul apropiat pentru acel punct de date.
  3. Păstrați rezultatul fiecărei diferențe.
  4. Găsiți suma diferențelor pătrate.
  5. Calculați media diferențelor pătrate pentru a obține varianța
  6. Obțineți rădăcina pătrată a varianței pentru a obține abaterea standard pentru fiecare punct de date.
  7. Înmulțiți abaterea standard cu 2. Calculați valorile benzii superioare și inferioare adăugând sau scăzând media cu valoarea înmulțită.

Cu punctele de date definite, putem folosi apoi selection.join pentru a reda benzile Bollinger:

// 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)
);

Aceasta redă graficul de suprafață care denotă zona umplută de benzile Bollinger. În funcția de actualizare, putem folosi selection.transition()metoda de a furniza tranziții animate la selectarea actualizării.

Sfeșnice

Graficul sfeșnicelor afișează prețurile ridicate, mici, deschise și închise ale unei acțiuni pentru o anumită perioadă. Fiecare sfeșnic reprezintă un punct de date. Verde reprezintă atunci când stocul se închide mai mare, în timp ce roșu reprezintă atunci când stocul se închide la o valoare mai mică.

0*GGsqZeYDGuZzrvGd
Credite: Investopedia

Spre deosebire de benzile Bollinger, nu este nevoie de calcule suplimentare, deoarece prețurile sunt disponibile în setul de date existent.

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']);
  

În funcția de introducere, fiecare sfeșnic este redat pe baza proprietăților sale individuale.

În primul rând, fiecărui element de grup sfeșnic i se atribuie o clasă de up-day dacă prețul de închidere este mai mare decât prețul deschis și down-day dacă prețul de închidere este mai mic sau egal cu prețul deschis.

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']) }
      ]);
    });

Apoi, adăugăm path element, care reprezintă cel mai mare și cel mai mic preț din acea zi, la selecția de mai sus.

  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);
    });
});

Aceasta este urmată de adăugarea fișierului rect element la selecție. Înălțimea fiecăruia rect elementul este direct proporțional cu intervalul său de zi, derivat prin scăderea prețului deschis cu prețul închis.

În foile noastre de stil, vom defini următoarele proprietăți CSS pentru clasele noastre, făcând sfeșnice roșii sau verzi:

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

Acest lucru are ca rezultat redarea benzilor Bollinger și a sfeșnicelor:

1612142475 51 Cum sa lucrati cu modelul de actualizare general al D3js
Este frecvent ca comercianții să folosească atât benzi Bollinger, cât și sfeșnice pentru analize tehnice.

Noua sintaxă sa dovedit a fi mai simplă și mai intuitivă decât apelul explicit selection.enter, selection.append, selection.merge, și selection.remove.

Rețineți că pentru cei care se dezvoltă cu versiunea D3.js v5.8.0 și dincolo, a fost recomandat de Mike Bostock pe care acești utilizatori încep să îl folosească selection.join datorită avantajelor de mai sus.

Concluzie

Potențialul D3.js este nelimitat, iar ilustrațiile de mai sus sunt doar vârful aisbergului. Mulți utilizatori mulțumiți au creat vizualizări care sunt mult mai complexe și mai sofisticate decât cea prezentată mai sus. Acest lista API-urilor gratuite vă poate interesa dacă doriți să vă lansați în propriile proiecte de vizualizare a datelor.

Simțiți-vă liber să verificați cod sursa si demonstrație completă a acestui proiect.

Vă mulțumesc foarte mult pentru că ați citit acest articol. Dacă aveți întrebări sau sugestii, nu ezitați să le lăsați în comentariile de mai jos!

Ești nou în D3.js? Vă puteți referi la acest lucru articol despre noțiunile de bază ale implementării componentelor grafice comune.

Mulțumiri speciale Debbie Leong pentru că a revizuit acest articol.

Referințe suplimentare:

  1. Documentația API D3.js
  2. Demonstrație interactivă a selecției