Interaktive applikationer med React & D3

Hvis du overvejer at lave datavisualisering i React, kan du kigge på Semiotic, en ramme for datavisualisering, jeg har udviklet til React.

At samle D3.js og React er en af ​​de ting, der ikke er ny, men som stadig ikke er veletableret nok til at pege på en sikker måde at gøre det på. I dette uddrag fra min bog D3.js i handling, anden udgave, viser jeg dig de vigtigste teknikker til at kombinere React og D3 og forklare deres styrker og svagheder. Koden til dette eksempel findes i React-kapitelkoden på D3.js i Action's repo.

Det fulde eksempel, du bygger i kapitel 9 i D3.js i handling. I denne artikel lykkes det at få noget af søjlediagrammet og noget af kortet.

Vi starter med et design til vores instrumentbræt. Design kan være grove skitser eller detaljerede sæt brugerkrav. Lad os forestille os, at du arbejder for den førende europæiske online-sælger af bordmåtter, MatFlicks, og du er ansvarlig for at oprette et instrumentbræt, der viser deres udrulning til Nordamerika og Sydamerika. Den geniale administrerende direktør for MatFlicks, Matt Flick, besluttede, at strategien for udrulning ville være alfabetisk, så Argentina får adgang på dag 0, og hver dag får endnu et land adgang til den fantastiske MatFlicks-beholdning. De er nødt til at se, hvordan implementeringen fortsætter geografisk, over tid og i alt pr. Land. Figuren herunder viser en enkel skitse for at opnå dette ved hjælp af flere af de diagrammer, jeg udforsker i hele D3.js i handling. Vi vil generere MatFlicks-data tilfældigt, hvor hvert land kun genererer tilfældige data i milliarder af dollars indtægter pr. Dag efter deres udrulning.

Med et datadashboard som dette ønsker vi at give en bruger flere perspektiver på dataene samt muligheden for at bore ned i dataene og se individuelle datapunkter. Vi bruger et linjediagram til at se ændringen over tid, et søjlediagram for totale råændringer og et kort, så brugerne kan se den geografiske fordeling af vores data. Vi vil også lade brugerne skære og terne deres data - funktionalitet, der let opnås ved hjælp af en pensel.

En skitse af et instrumentbræt, der viser et kort, søjlediagram og et stablet områdeskort, der viser vores data.

Fra skitsen kan du nemt forestille dig interaktionsmuligheder og ændringer, som du måske ønsker at se baseret på brugeraktivitet; for eksempel at fremhæve hvilke elementer i hvert diagram, der svarer til elementer i andre diagrammer, eller give flere detaljer om et bestemt element baseret på et klik. Den slags dynamisk filtrering og interaktivitet udforskes i det fulde kapitel. CSS til instrumentbrættet er ikke meget til dette eksempel, bare udfyldning og stræk for det kort, vi laver. Som med de fleste datavisualiseringer, vil meget af stylingen være inline.

Dashboard CSS
path.countries {
   slagbredde: 1;
   slagtilfælde: # 75739F;
   udfyld: # 5EAFC6;
}

Kom godt i gang med React

React er et livssyklusstyringssystem, der er en del af en meget populær applikationsramme og udviklingsmønster. React er visningslaget og giver dig mulighed for at definere HTML-komponenter med brugerdefineret opførsel, som er super nyttig til at komponere applikationer. Det bruger et JavaScript + HTML-sprog kaldet JSX, der afsky mig, da jeg første gang så det, men nu elsker jeg det. Jeg kunne ikke lide det, fordi jeg altid følte, at JavaScript og HTML skulle leve i helt separate verdener, men jeg fandt senere ud af, at det at skrive HTML inde i JavaScript kunne være utroligt nyttigt, når du manipulerer DOM, som vi har været med vanilla D3 i hele denne bog.

Når du ser eksempler på React, parrer de det typisk med et slags statsstyringssystem som Flux eller Redux. Det gør vi ikke i dette kapitel. Dette er et enkelt kapitel, og du kan finde hele bøger om React.

Du har brug for node og node pakkehåndtering (npm) installeret på dit system samt en lille smule komfort med kommandolinjen. Der er gode bøger om React, såsom React Quick, så dette vil kun ridse overfladen, men netop dette kapitel får dig til punktet med et fuldstændigt selvstændigt React-datavisualiseringsprogram.

Hvorfor reagere, hvorfor ikke X?

React er naturligvis det bedste bibliotek, der nogensinde er lavet, og hvis du kan lide Angular, er du stum, bro (og ikke engang får mig i gang med Ember). Nej ikke rigtigt. Det er forfærdeligt, og det er for dårligt, at folk bliver så investerede i deres retlige biblioteks retfærdighed.

Faktisk ønskede jeg bare at vise folk, hvordan man håndterer D3 i et moderne MVC-lignende miljø, og jeg ved React bedst. Selv hvis du aldrig bruger React, vil du sandsynligvis se mønstre i dette kapitel, der gælder for andre rammer. Og selvom du hader applikationsrammer, kan du bruge det meste af koden i dette kapitel i dit eget brugerdefinerede, håndvalsede, smukt uigennemsigtige skræddersyede betjeningspanel.

Grundlæggende består React af en komponentoprettelsesramme, der giver dig mulighed for at opbygge selvstændige elementer (som div eller svg: rect), der har tilpassede gengivelsesmetoder, egenskaber, tilstands- og livscyklusmetoder.

Render

En af de vigtigste egenskaber ved React er, at den holder styr på en kopi af DOM, kendt som den virtuelle DOM, som den kun kan bruge til at gengive elementer, der skal ændres, baseret på modtagelse af nye datalagringscyklusser og fremskynde dine webapplikationer . Dette var Reacts store salgsargument, da det faldt første gang, men det er blevet populært med andre visnings gengivelsessystemer. Funktionen render () i hver React-komponent returnerer de elementer, der oprettes af React (typisk beskrevet ved hjælp af JSX, som er introduceret i dette kapitel).

Rekvisitter

Attributter af en komponent sendes til den, når den er oprettet og kaldes rekvisitter. Disse rekvisitter af en React-komponent er typisk tilgængelige i komponentfunktionerne via denne kontekst som this.props. I visse tilfælde, såsom statsløse komponenter eller konstruktører, bruger du ikke dette til at få adgang til dem, men vi gør det ikke i dette kapitel, så du har brug for en bog dedikeret til React for at lære de andre mønstre at kende. Denne struktur giver dig mulighed for at sende data ned fra overordnede komponenter til underordnede komponenter, og du kan bruge disse data til at ændre, hvordan komponenten gengives. Dette ser du detaljeret, når vi går ind i koden.

Stat

Mens rekvisitter sendes ned til en komponent, gemmes og ændres tilstanden af ​​en komponent internt i komponenten. Som this.props, der er en tilsvarende this.state, der giver dig den aktuelle tilstand. Når du ændrer tilstand (ved hjælp af this.setState i en komponent) udløses det automatisk en genudgivelse, medmindre du har ændret shouldComponentUpdate (en livscyklusmetode, der er behandlet i det næste afsnit).

Livscyklusmetoder

React-komponenter afslører livscyklusmetoder, der tændes, når komponenten oprettes og opdateres og modtager dens rekvisitter. De er utroligt nyttige og endda nødvendige i visse tilfælde, som vi kan se senere. Du har for eksempel shouldComponentUpdate, som giver dig mulighed for at specificere logikken for, om komponenten gengives igen, når den modtager nye rekvisitter eller tilstand. Der er også willComponentUpdate og didComponentUpdate for at tilføje funktionalitet til din komponent, før eller efter den opdateres, sammen med lignende metoder til, når komponenten først monteres eller afsluttes (og et par flere). Jeg kommer ind på disse metoder, da de gælder for vores behov for datavisualisering, men jeg vil ikke røre ved dem alle.

react-create-app: opsætning af din applikation

En af udfordringerne ved moderne udvikling er at få dit miljø oprettet. Heldigvis er der et kommandolinjeværktøj, der får dig i gang, og det understøttes af React-teamet: create-react-app

I OSX kan du åbne dit terminalvindue og køre følgende kommandoer:

npm installere -g create-react-app
Opret-reager-app d3ia
cd d3ia /
npm start

Det er så let at opsætte din React-app. Hvis du navigerer til localhost: 3000, vil du se siden med kedelpladen Opret-reaktion-app nedenfor. Hvis du har problemer eller har brug for instruktioner til Windows, kan du se https://github.com/facebookincubator/create-react-app.

Standardsiden, som oprettes-reager-app udsættes for.

Sammen med at starte din nodeserver, der kører koden, skaber dette al den struktur, du har brug for for at opbygge og distribuere et React-program, som vi vil bruge det til at opbygge vores dashboard. Denne struktur indeholder en package.json-fil, der refererer til alle moduler, der er inkluderet i dit projekt, og som vi har brug for at tilføje et par flere moduler for at gøre vores dashboard. Vi tilføjer moduler ved hjælp af NPM, og selvom vi kunne inkludere hele D3-biblioteket og fortsætte kodningen som vi har, er det bedre for dig at installere de enkelte moduler og forstå, hvordan import af disse moduler fungerer. Kør følgende i dit projektmappe for at installere d3-skala-modulet:

npm i –SE d3-skala

Denne kommando (npm i er forkortelse til npm installation) installerer den nyeste version af d3-skalaen (som giver os adgang til alle de vidunderlige skalaer, vi har brugt i de sidste otte kapitler), og –SE-mærket gemmer den nøjagtige version til din package.json, så når du vil installere dette program andetsteds, er d3-skala installeret. Sammen med d3-skala skal du gøre det samme med følgende moduler:

d3-form
d3-svg-legend
d3-matrix
d3-geo
d3-udvælgelse
d3-overgang
d3-børste
d3-akse

Ved at installere moduler individuelt som denne reducerer du mængden af ​​kode, du vil implementere med din applikation, reducerer belastningstiden og forbedrer vedligeholdelsesevnen.

JSX

JSX henviser til JavaScript + XML, et integreret JavaScript- og HTML-kodesprog, der giver dig mulighed for at skrive HTML inline med din JavaScript-kode. Det kræver, at koden transpileres til almindelig JavaScript - din browser kan ikke køre JSX oprindeligt - men så længe du har konfigureret din transpilering (som reaktion-oprette-app allerede gør for os) kan du skrive kode som denne:

const data = ['en', 'to', 'tre']
const divs = data.map ((d, i) => 
{d}
)
const wrap = 
className = 'wrapper'> {divs} 

Og du kan oprette en matrix med tre div-elementer, som hver har den tilsvarende streng fra din array som indhold. Bemærk et par ting, der foregår her. For det første, når vi begynder at skrive i HTML, er vi nødt til at bruge krøllede seler (med fed skrift til vægten ovenfor) for at komme ud af det, hvis vi vil lægge js der. Hvis jeg for eksempel ikke havde lagt krøllede seler omkring d, ville alle mine divs have haft bogstavet “d” som deres indhold. Et andet er, at stil er et objekt, der sendes til et element, og at objektet har brug for CSS-nøgler, der normalt er slangetaske (som margin-venstre), der er omdannet til kamelhus (marginLeft). Når vi laver en række elementer, har hver brug for en "nøgle" -egenskap, der giver den en unik nøgle (som den valgfri nøgle, når vi bruger .data () med D3). Endelig, når du vil indstille et elements CSS-klasse, skal du bruge className, fordi klassen er reserveret.

Der er mere ved JSX, men det skulle være nok til at give dig mening i den kode, du vil se. Da jeg første gang så JSX, var jeg overbevist om, at det var en frygtelig idé og planlagde kun at bruge de rene JavaScript-rendering-funktioner, som React har (du behøver ikke bruge JSX til at bruge React), men efter et par uger faldt jeg ind elsker det. Evnen til at oprette elementer på farten fra data appellerede virkelig til mig på grund af min erfaring med D3.

Traditionel D3-gengivelse med React

Udfordringen med at integrere D3 med React er, at React og D3 begge ønsker at kontrollere DOM. Hele select / enter / exit / update / opdateringsmønsteret med D3 er i direkte konflikt med React og dets virtuelle DOM. Hvis du kommer til React fra D3, er det at give op dit greb om DOM et af disse "kolde, døde hænder" øjeblikke. Den måde, de fleste bruger D3 med React, er at bruge React til at opbygge strukturen i applikationen og til at gengive traditionelle HTML-elementer, og når det kommer til datavisualiseringsafsnittet, passerer de en DOM-container (typisk en ) over til D3 og brug D3 til at oprette og ødelægge og opdatere elementer. På en måde ligner det den måde, vi plejede at bruge Java-applets eller Flash til at køre en sort boks på din side, mens resten af ​​din side gengives separat. Fordelen ved denne metode til at integrere React og D3 er, at du kan bruge den samme type kode, som du ser i alle de centrale D3-eksempler. Den største vanskelighed er, at du er nødt til at oprette funktioner i forskellige React-livscyklusbegivenheder for at sikre dig, at dit viz opdateres.

Nedenstående liste viser en simpel søjlediagramkomponent bygget ved hjælp af denne metode. Opret denne komponent i din src / mappe og gem den som BarChart.js. I React skilles komponentfilnavne og funktionsnavne typisk fra andre kodefiler og funktioner ved at bruge camelcase og aktivere det første bogstav som dette.

BarChart.js

import React, {Component} fra 'react'
import './App.css'
import {scaleLinear} fra 'd3-skala'
import {max} fra 'd3-array'
import {select} fra 'd3-valg'
klasse BarChart udvider komponent {
   constructor (rekvisitter) {
      super (rekvisitter)
      this.createBarChart = this.createBarChart.bind (dette)
   }
   componentDidMount () {
      this.createBarChart ()
   }
   componentDidUpdate () {
      this.createBarChart ()
   }
   createBarChart () {
      const node = this.node
      const dataMax = max (this.props.data)
      const yScale = skalaLinær ()
         .domæne ([0, dataMax])
         .range ([0, this.props.size [1]])
   vælge (node)
      .selectAll (rect ')
      Data, (this.props.data)
      .gå ind()
      .append (rect ')
   
   vælge (node)
      .selectAll (rect ')
      Data, (this.props.data)
      .Afslut()
      .fjerne()
   
   vælge (node)
      .selectAll (rect ')
      Data, (this.props.data)
      .style ('fyld', '# fe9922')
      .attr ('x', (d, i) => i * 25)
      .attr ('y', d => this.props.size [1] - yScale (d))
      .attr ('højde', d => ySkala (d))
      .attr ('bredde', 25)
   }
render () {
      return  this.node = node}
      bredde = {500} højde = {500}>
      
   }
}
eksport standard BarChart

Et par forklaringer på, hvad der foregår i koden her:

  • Da vi importerer D3-funktioner fra moduler, har de ikke d3. præfiks, i stedet for er de den importerede funktion som skalaLinear.
  • I konstruktøren skal du binde komponenten som kontekst til eventuelle nye interne funktioner, hvis du vil have adgang til this.props eller this.state i denne funktion (dette behøver ikke gøres for nogen eksisterende livscykelfunktioner).
  • Ved at udføre this.createBarChart i componentDidMount og componentDidUpdate, når komponenten først monteres eller modtages nye rekvisitter / tilstand afbryder søjlediagramfunktionen.
  • this.node er indstillet i egenskaben ref for svg-elementet og fungerer som en henvisning til den faktiske DOM-knude, der er genereret af React, så du kan overdrage den DOM-knude til din D3-funktionalitet.
  • størrelse og data sendes som rekvisitter til komponenten, så du får adgang til dem med this.props.size og this.props.data til din D3-kode.
  • Render returnerer bare et SVG-element, der venter på din D3-kode. Nedenfor ser vi, hvordan du bruger React til at generere hele diagrammet.

At foretage disse ændringer og gemme dem viser ikke øjeblikkelig virkning, fordi du ikke importerer og gengiver denne komponent i App.js, som er den komponent, der oprindeligt blev gengivet af din app. Skift App.js for at matche den følgende liste.

Henvisning til BarChart.js i App.js
import React, {Component} fra 'react'
import './App.css'
import BarChart fra './BarChart'
klasse App udvider komponent {
   render () {
   Vend tilbage (
      
      
      

d3ia-betjeningspanel

             
                        )    } }
eksporter standardapp

Ændringerne her er, at vi importerer vores nyoprettede komponent (BarChart.js), og vi overfører nogle data og størrelse til den komponent (det er sådan, vi får adgang til den på props.data og props.size i createBarChart) .

Når du gemmer App.js med disse ændringer, kan du se noget ret cool, hvis du har din server kørt: Den opdaterer automatisk siden for at vise dig, hvad der er i figuren herunder. Det er Webpack - modulbundteren inkluderet i create-react-app - automatisk opdatering af din app baseret på ændringer i din kode.

Din første React + D3-app med et simpelt søjlediagram gengivet i din app.

Du kan allerede forestille dig forbedringer som skalering af bjælkerne, så de passer til bredden, som vi ser senere. Men nu skal vi gå videre til den anden metode til gengivelse af datavisualisering ved hjælp af D3 og React.

Reager for elementoprettelse, D3 som visualiseringskerne

I stedet for at bruge ref til at få den faktiske DOM-knude og videregive den DOM-knude til D3, kan du bruge D3 til at generere alle de nødvendige tegneinstruktioner og bruge React til at oprette de faktiske DOM-elementer. Der er udfordringer med denne tilgang til at oprette animerede overgange og elementer, der kan trækkes, men ellers er det at foretrække, fordi det vil oprette kode, der vil være mere vedligeholdelig af dine mindre D3-tilbøjelige kolleger.

Koden herunder viser, hvordan vi kan gøre dette for at genskabe et af kortene, der er lavet i kapitel Geospatial data visualisering i D3.js i Action. I dette eksempel importerer vi geografiske data (i dette tilfælde world.js) i stedet for at indlæse geodata, som traditionelle eksempler viser. Det gøres ved at transformere geojson .js-filen ved at tilføje en lille ES2015-eksport-syntaks til begyndelsen af ​​JSON-objektet (du kan se koden på github-repo). Koden i den næste fortegnelse ligner det, vi har set før, undtagen nu bruger vi den til at oprette JSX-elementer, der repræsenterer hvert land, og vi inkluderer geodata i stedet for at bruge en XHR-anmodning.

WorldMap.js
import React, {Component} fra 'react'
import './App.css'
importer verdensdata fra './world'
import {geoMercator, geoPath} fra 'd3-geo'
klasse WorldMap udvider komponent {
   render () {
      const projektion = geoMercator ()
      const pathGenerator = geoPath () .projektion (projektion)
      const lande = worlddata.features
         .map ((d, i) => )
   retur 
   {lande}
   
   }
}
eksport standard WorldMap

I stedet for at fikle med async-opkald (f.eks. Ved hjælp af d3.json) kan vi bare importere kortdataene, da de ikke ændrer sig. Jeg synes, det er bedre at omdanne statiske aktiver til .js og importere dem i stedet for at bruge XHR-anmodninger. Vi bruger native Array.map til at kortlægge matriserne til svg: sti-elementer og udfylde attributten d for hvert af disse sti-elementer ved hjælp af D3s geofunktionalitet. Denne række stier er lige faldet ned i nogle krøllede parenteser inde i svg, og det er alt, hvad der er at oprette et koropleth-kort i React with D3. Det er næsten nøjagtigt det samme som det databindende mønster, vi ser i D3, bortset fra at vi bruger native Array.map til at kortlægge de enkelte dataelementer til DOM-elementer på grund af JSX's magi.

Et grundlæggende kort, der er gengivet via React og JSX med D3, der giver tegneinstruktionerne.

I min egen praksis foretrækker jeg at bruge denne metode, fordi jeg finder livscyklusbegivenhederne i React såvel som den måde, den skaber og opdaterer og ødelægger elementer til at være mere omfattende end at håndtere dem via D3. Efterhånden som React og andre rammer som det modnes, bliver emnerne med at udvikle interaktivitet og animation mindre og mindre vanskelige.

I kapitel 9 i D3.js i handling fortsætter dette eksempel med tilføjelsen af ​​en streamgraf, børste, responsiv størrelse og mere. Men dette viser dig de to vigtigste måder at nærme sig integration af D3 og React.