D3 og lærred i 3 trin

Bindingen, lodtrækningen og interaktiviteten

Lad os sige, at du bygger en datavisualisering ved hjælp af D3 og SVG. Du kan ramme et loft, når du prøver at vise flere tusinde elementer på samme tid. Din browser begynder muligvis at puste under vægten af ​​alle disse DOM-elementer.

Nå her kommer HTML5 lærred til redning! Det er meget hurtigere, så det kan løse din browsers puffeproblemer.

Men du kan hurtigt finde dig selv skræmmet. Fordi D3 og lærred fungerer lidt anderledes end D3 og SVG - især når det kommer til tegning og tilføjelse af interaktivitet.

Men frygt ikke - det er ikke så kompliceret. Enhver oplevelse, du har haft med at opbygge visuals med D3 og SVG - eller henvende dig til D3 med en anden renderer - vil hjælpe dig enormt.

Denne tutorial er bygget på skuldre fra giganter, der allerede har dækket lærred godt. Jeg lærte disse tre tutorials udvendigt, og jeg anbefaler, at du også gør:

  • Arbejde med D3.js og lærred: Hvornår og hvordan fra Irene Ros
  • Nåle, høstacks og Canvas API fra Yannick Assogba
  • Læring fra en D3.js-afhængig ved at starte med Canvas fra Nadieh Bremer

Så hvorfor fortsætte med at læse dette, da? Når jeg vil lære noget nyt, hjælper det mig meget at se på det samme emne fra lidt forskellige vinkler. Og denne tutorial er en lidt anden vinkel.

Denne tutorial dækker også de tre vigtigste trin: binding af data, tegningselementer og tilføjelse af interaktivitet - og det gør alt dette på én gang med en tilføjet trin-for-trin-manual til at konfigurere dig.

Hvad bygger vi?

Et gitter med smukke farver

Et gitter med (mange) firkanter. Deres farver har ikke nogen dyb betydning, men ser de ikke smukke ud? Den vigtige bit er, at du kan opdatere det (for at dække indbinding og opdatering af data), at det har mange elementer (op til 10.000 for, at lærred kan udbetale), og at du kan holde musepekeren over hver firkant for at vise kvadratspecifik information (interaktivitet). Du kan lege med det her på en fuld skærm eller her med al kode

Den mentale model

Før vi faktisk dykker ind, lad os hurtigt gå tilbage og forstå konceptuelt, hvad vi gør, når vi opretter elementer med D3 for at trække dem til skærmen. Spring over dette, hvis du bare vil lave ting.

Det første trin, når du bruger D3, involverer normalt ikke tegning - det involverer at forberede alle dine elementer, du vil tegne. Det er lidt som at bygge nogle LEGO. Du kan rippe åbne boksen og begynde at bygge noget, eller du kan først se på manualen og opbygge den i henhold til planen. Manualen er din mentale model, en plan eller opskrift på, hvad du vil bygge.

En mental model vendte materiale (Mike, 2009 https://creativecommons.org/licenses/by/2.0/)

Hvad er D3's model? Bortset fra det store antal nyttige funktioner og metoder, der beregner positioner, omformer datasæt (layouterne) og genererer funktioner, der for eksempel tegner stier for os, har D3 en model til, hvordan elementernes liv skal udvikles på skærmen . Det har en bestemt måde at tænke på livscyklussen for hvert enkelt element.

Mindre æterisk injicerer du data i en endnu ikke eksisterende DOM, og D3 skaber nye elementer efter dit valg pr. Data, du injicerer. Normalt et element pr. Datapoint. Hvis du vil injicere nye data i DOM, kan du gøre det, og D3 identificerer, hvilke elementer der skal oprettes nyligt, hvilke elementer der har lov til at blive og hvilke elementer der skal pakkes sammen og forlade skærmen.

D3 bruges normalt sammen med SVG eller undertiden med HTML-elementer. I dette ortodokse tilfælde kan du se dataene i DOM, når du f.eks. Vælger at se på dem gennem konsollen. Du kan gribe den, du kan flytte den op eller ned i DOM, og du kan - vigtigst - tilføje interaktivitet til hvert element, du gerne viser, for eksempel en værktøjstip.

Men - på nedsiden - du kan ikke vise en masse elementer. Hvorfor? Fordi jo flere elementer du skubber ind i DOM, desto sværere skal browseren arbejde for at få vist dem alle. Lad dem også bevæge sig rundt, og browseren er nødt til konstant at beregne dem igen. Jo mere knapper browseren bliver, jo lavere er din billedhastighed eller FPS (Frames Per Second), som måler, hvor mange rammer browseren kan male hvert sekund. En billedfrekvens på 60 er god og muliggør en flydende oplevelse, så længe der ikke er gået glip af rammer - en billedhastighed på alt under 30 kan svare til en ujævn tur. Så når du vil vise flere elementer, kan du vende tilbage til lærred.

Hvorfor lærred? Canvas er et HTML5-element, der leveres med sin egen API til at male på det. Alle elementer, der tegnes på lærredselementet, manifesteres ikke i DOM og sparer en masse arbejde for browseren. De tegnes i øjeblikkelig tilstand. Dette betyder, at de gengivne elementer ikke gemmes i DOM, men dine instruktioner trækker dem direkte til en bestemt ramme. DOM kender kun det ene lærredselement; alt på det er kun i hukommelsen. Hvis du vil ændre dine lærredselementer, skal du tegne scenen til den næste ramme.

Problemet med dette er selvfølgelig, at du ikke kan kommunikere direkte med disse ikke-materielle elementer, der lever i hukommelsen. Du er nødt til at finde en måde at tale indirekte med dem på. Det er her D3-modellen kommer ind såvel som brugerdefinerede eller 'virtuelle' DOM-elementer. Hvad du laver som hovedregel er:

  1. Bind dine data til brugerdefinerede DOM-elementer. De bor ikke i DOM, men kun i hukommelsen (i en 'virtuel' DOM) og beskriver livscyklussen for disse elementer på en kendt D3-måde.
  2. Brug lærred til at tegne disse elementer.
  3. Tilføj interaktivitet med en teknik kaldet 'picking'.

Lad os gøre det.

Dataene

Før vi begynder at kode, lad os producere nogle data. Lad os sige, at du vil have 5.000 datapunkter. Så lad os oprette en matrix med 5.000 elementer, som hver er et objekt med kun en enkelt egenskabsværdi, der bærer elementets indeks. Sådan opretter du det med d3.range (). d3.range () er en D3-hjælpefunktion, der opretter en matrix baseret på dens argument:

var data = [];
d3.range (5000) .forEach (funktion (el) {
  data.push ({value: el});
});

Sådan ser dataene ud i konsollen

Gys!

Lærredsbeholderen og dens værktøjer

Lærredselementet er et HTML-element. Det er konceptuelt meget ligesom ethvert SVG-forældreelement, som jeg i det mindste normalt tilføjer til en simpel containerdiv som i:

Så lad os føje det til din container med D3 som i ...

var bredde = 750, højde = 400;
var canvas = d3.select ('# container')
  .append ( 'lærred')
  .attr ('bredde', bredde)
  .attr ('højde', højde);
var context = canvas.node (). getContext ('2d');

Du skal også tilføje konteksten, som er lærredets værktøjskasse. Kontekstvariablen er fra nu af objektet med alle egenskaber og metoder, børster og farver, du har brug for at tegne på lærredet. Uden konteksten ville lærredselementet forblive tomt og hvidt. Det er alt hvad du skal installere - et lærred og dets værktøjer ...

Base af Stilfehler - Eget arbejde, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=5899171; blå Lego af David Lofink, 2008 https://creativecommons.org/licenses/by/2.0/

HTML

... er enkel. Den vigtigste HTML-struktur på dit websted er:


Farvede gitter



... tager tal mellem 1 og 10k

Javascript-strukturen

På et øverste niveau har du kun brug for 2 funktioner:

databind (data) {
  // Bind data til brugerdefinerede elementer.
}
lodtrækning () {
  // Tegn elementerne på lærredet.
}

Temmelig lige fremad.

Bind elementerne

For at binde data til elementerne opretter du først et basiselement til alle dine brugerdefinerede elementer, du vil fremstille og tegne. Hvis du kender D3 godt, skal du tænke på det som en erstatning for SVG-elementet:

var customBase = document.createElement ('brugerdefineret');
var brugerdefineret = d3.select (customBase);
// Dette er din SVG-erstatning og overordnede til alle andre elementer

Derefter tilføjer du nogle indstillinger til dit gitter. Kort sagt giver disse indstillinger dig mulighed for at tegne et gitter af firkanter. 100 firkanter bygger en 'pakke', og der er en linjeskift efter 10 pakker (eller efter 1.000 firkanter). Du kan justere dette til forskellige 'parceling' af firkanterne eller forskellige linjebrydende. Eller bare ikke bekymre dig om det. Jeg foreslår sidstnævnte ...

// Indstillinger for et gitter med 10 celler i træk,
// 100 celler i en blok og 1000 celler i træk.
var groupSpacing = 4;
var cellSpacing = 2;
var offsetTop = højde / 5;
var cellSize = Math.floor ((bredde - 11 * groupSpacing) / 100) - cellSpacing;

Lad os nu starte den databindende mission. Lad os først få nødvendighederne ud af vejen og oprette en farveskala, du anvender på dine firkanter lidt senere.

funktionsdatabind (data) {
// Få en skala for farverne - ikke vigtigt, men pænt.
colourScale = d3.scaleSequential (d3.interpolateSpectral)
                .domæne (d3.extent (data, funktion (d) {return d;}));

Lad os nu forbinde dine data til den 'erstatning-SVG', du kaldte brugerdefineret ovenfor, og tilføje endnu ikke-eksisterende tilpassede elementer med klassen.

var join = custom.selectAll ('custom.rect')
  Data, (data);

Du indtaster de brugerdefinerede elementer (husk at intet kommer ind i DOM, alt dette er i hukommelsen).

var enterSel = join.enter ()
  .append ( 'custom')
  .attr ('klasse', 'rekt')
  .attr ("x", funktion (d, i) {
    var x0 = Math.floor (i / 100)% 10, x1 = Math.floor (i% 10);
    returgruppe Rum * x0 + (celleafstand + cellestørrelse) * (x1 + x0 * 10); })
  .attr ("y", funktion (d, i) {
  var y0 = Math.floor (i / 1000), y1 = Math.floor (i% 100/10);
  returgruppe Rum * y0 + (celleafstand + cellestørrelse) * (y1 + y0 * 10); })
  .attr ('bredde', 0)
  .attr ('højde', 0);

Når et element kommer ind i din model, giver du det bare en x og en y position samt en bredde og en højde på 0, som du vil ændre i det kommende opdateringsvalg ...

Du fletter indtastningsvalget i opdateringsvalget og definerer alle attributter til opdateringen og indtast valg. Dette inkluderer en bredde og en højdeværdi samt en farve fra den farveskala, du har bygget tidligere:

tilslutte
  .merge (enterSel)
  .transition ()
  .attr ('bredde', cellestørrelse)
  .attr ('højde', cellestørrelse)
  .attr ('fillStyle', funktion (d) {return colourScale (d);});

To ting at bemærke om denne sidste linje. Når du arbejder med SVG, ville denne linje være

.style ('farve', funktion (d) {return colourScale (d);})

Men med lærred bruger du .attr (). Hvorfor? Din største interesse her er at finde en smertefri måde at overføre nogle element-specifikke oplysninger. Her vil du overføre en farvestreng fra databind () til funktionen draw (). Du bruger elementet simpelthen som et fartøj til at transportere dine data til det sted, hvor de bliver gengivet til lærredet.

Det er en meget vigtig forskel: Når du arbejder med SVG eller HTML, kan du binde data til elementer og tegne eller anvende stilarter på elementerne i et trin. I lærred har du brug for to trin. Først binder du dataene, så tegner du dataene. Du kan ikke style elementerne, mens du er bindende. De findes kun i hukommelsen, og lærred kan ikke styles via CSS-stilegenskaber, hvilket er nøjagtigt, hvad du får adgang til, når du bruger .style ().

Først kan dette virke begrænsende, da du kan gøre mindre i et trin, men det er konceptuelt næsten renere og giver dig også en vis frihed. .attr () giver os mulighed for at sende alle nøgleværdipar på rejsen. Du kan f.eks. Bruge andre metoder som HTML .dataset-egenskaben, men .attr () klarer sig fint.

Bemærk, at vi ikke siger farve, men udfyldStyle. For at være ærlig kunne du bruge farve, eller du kan bruge chooChooTrain her. Du behøver kun at huske dette, når du henter oplysningerne senere under tegningen. Da lærred imidlertid bruger en egenskab kaldet fillStyle til stilelementer, ser det ud til at være mere passende i dette tilfælde.

Endelig definerer du også udgangsvalget og beslutter, hvad der skal ske med afslutende elementer.

var exitSel = join.exit ()
  .transition ()
  .attr ('bredde', 0)
  .attr ('højde', 0)
  .fjerne();

Det er det! Du kan lukke din databind () -funktion og gå videre ...

} // databind ()

Dette er ikke rigtig skræmmende fra D3, da det er stort set nøjagtigt det samme. Du har nu oprettet din datamodel, den måde, applikationen tænker på data på. Hvert element får de egenskaber, det skal trækkes via .attr () -funktionerne, og hvert element tildeles en livscyklustilstand afhængigt af de indsprøjtede data. Vores standard D3-model.

Tegning af elementerne

Af Kristina Alexanderson, 2011 https://creativecommons.org/licenses/by-nc-nd/2.0/

Nu skal du skrive tegnefunktionen for at få elementerne på skærmen. Lad os bare bemærke her, at der ikke er sket noget endnu. Du har endnu ikke ringet til databind (), fordi du først skal finde en måde at tegne det på lærredet på. Så her går vi ... Funktionen draw () behøver ikke at tage nogen argumenter i dette tilfælde:

funktionstegn () {

Som nævnt flygtigt ovenfor, skal du passe på at rengøre lærredet, hver gang du tegner igen. DOM er materiale, idet du når du tegner et rekt-element på det, og du ændrer dets x-værdi, vil det bevæge sig i x-retningen, og DOM sørger automatisk for dette træk (eller genmaling) automatisk.

Hvis du flytter en rect fra x = 0 til x = 1 på et bestemt tidspunkt (efter et tastetryk for eksempel), vil browseren flytte rect fra 0 til 1 inden for et kryds eller ramme-maling (som er ca. 16ms lang ). Hvis du flytter det fra 0 til 10, vil det gøre det på et tidspunkt afhængigt af varigheden, du bad om, at denne overgang skulle ske, måske 1 pixel pr. Kryds, måske 8 pixel pr. Kryds (for mere at læse dette blogindlæg).

Men det vil fortælle pixlen ved 0, at rektet er forsvundet, og pixlen ved 1, at der er en rekt nu. Canvas gør ikke dette. Du skal fortælle lærred, hvad du skal male, og hvis du maler noget nyt, skal du fortælle det om at fjerne den forrige maling.

Så lad os starte med at rydde op i alt, hvad der måtte være på lærredet, før du tegner. Sådan gør du:

context.clearRect (0, 0, bredde, højde); // Ryd lærredet.

Enkel.

Din tur…

  1. ... få fat i alle elementer for at gøre det
  2. loop gennem alle elementer og
  3. tage de oplysninger, du har gemt i databind () -funktionen for at tegne elementet:
// Tegn hvert individuelt tilpasset element med deres egenskaber.
var elementer = custom.selectAll ('custom.rect');
// Grib alle elementer, du har bundet data til i databind () -funktionen.
Elements.each (funktion (d, i) {// For hvert virtuelt / brugerdefineret element ...
  var node = d3.selekt (dette);
  // Dette er hvert enkelt element i løkken.
  
  context.fillStyle = node.attr ('fillStyle');
  // Her henter du farven fra den enkelte hukommelsesnode og indstiller fillStyle til lærredsmalningen
  context.fillRect (node.attr ('x'), node.attr ('y'), node.attr ('bredde'), node.attr ('højde'));
  // Her henter du nodenes placering og anvender den på fillRect-kontekstfunktionen, som vil udfylde og male firkanten.
}); // Slyng gennem hvert element.

Og det er det! Du kan lukke funktionen draw ()

} // lodtrækning ()

Da jeg begyndte med lærred efter et stykke tid, hvor jeg havde lyst til at dykke ned i det, hævdede denne enkelhed virkelig mit humør.

Der er dog ikke sket noget i browseren endnu. Vi har værktøjerne i databind () og draw () -funktionen, men intet er endnu trukket. Hvordan gør du det? Hvis du bare ville tegne et statisk billede eller et billede, skal du bare ringe til:

databind (data);
tegne();

Dette ville binde dataene til de brugerdefinerede elementer, som ville leve i hukommelsen og derefter tegne dem - en gang!

Men du har overgange. Husk ovenfor: Når du skrev databind () -funktionen, overførte du cellebredde og højde fra 0 til deres størrelse samt farven fra sort (standard) til det respektive elements farve. En standard D3-overgang varer 250 millisekunder, så du skal tegne firkanterne mange gange i disse 250 ms for at få en jævn overgang. Hvordan gør du det?

Det er igen enkelt. Du ringer bare til databind (data) for at oprette vores brugerdefinerede elementer, før du gentagne gange ringer draw (), så længe det tager overgangen til at køre. Så i vores tilfælde mindst 250 ms. Du kan bruge setInterval () til dette, men vi burde virkelig bruge requestAnimationFrame () for at være så performant som muligt (for mere at læse dette). Der er nogle få måder at bruge det på, men at holde sig inden for D3-ånden foreslår jeg at du bruger d3.timer (), som implementerer requestAnimationFrame () såvel som at være ligetil at bruge. Så her går vi:

// === Første opkald === //
databind (d3.range (værdi)); // Bygg de brugerdefinerede elementer i hukommelsen.
var t = d3.timer (funktion (forløbet) {
  tegne();
  hvis (forløbet> 300) t.stop ();
}); // Timer, der kører tegnefunktionen gentagne gange i 300 ms.

d3.timer () ringer opkaldet gentagne gange, indtil det er gået (hvilket er den givne tid i millisekunder fra instantiation) er forbi 300, og derefter stoppes timeren. I disse 300 millisekunder kører det lodtrækningen () ved hvert kryds (omtrent hver 16ms). draw () ser derefter på hvert elements attributter og tegner dem i overensstemmelse hermed.

Sådan fungerer en overgang på lærred. Du kalder tegningsfunktionen lige efter bindingsfunktionen mange gange. Uanset hvad din D3-model er indstillet til overgang (positioner, farver, størrelser) tegnes mange gange med små trinvise ændringer for hver tegning

Bemærk, at tegning () skal komme lige efter databind () -funktionen. Du kan ikke bede maskinen om at køre databind (), derefter gøre noget andet i et sekund og derefter kalde tegning (). For efter 1 sekund er de overgangstilstande, der er beregnet af din databind () -funktion alle allerede overført. Udført, støvet og glemt.

Det er det! Du har bundet data til brugerdefinerede elementer, og du har trukket dem til lærredet.

Lad brugeren opdatere antallet af firkanter

For at give brugeren muligheden for at gentage denne bedrift med et brugerdefineret antal elementer (ok, semi-brugerdefineret med maksimalt 10.000) tilføjer du følgende lytter og håndterer til din tekstindtastningsfelt:

// === Lyttere / håndlere === //
d3.select ('# text-input'). on ('keydown', funktion () {
if (d3.event.keyCode === 13) {
// Gør kun noget, hvis brugeren rammer retur (keycode 13).
  if (+ dette.værdi <1 || + dette.værdi> 10000) {
  // Hvis brugeren går under 1 eller højere end 10 000 ...
     
    d3.select ('# text-forklare'). classed ('alarm', sand);
    // ... fremhæv bemærkningen om rækkevidden og vende tilbage.
    Vend tilbage;
  } andet {
  // Hvis brugeren indtaster et fornuftigt tal ...
    d3.select ('# text-forklare'). classed ('alarm', false);
    // ... fjerne potentielle advarselsfarver fra noten ...
    værdi = + dette.værdi; // ... indstil værdien ...
    databind (d3.range (værdi)); // ... og bind dataene.
    var t = d3.timer (funktion (forløbet) {
      tegne();
  
      hvis (forløbet> 300) t.stop ();
    }); // Timer, der kører tegnefunktionen gentagne gange i 300 ms.
  
  } // Hvis brugeren hits tilbage.
}); // Tekstinput lytter / handler

Her er det igen, vores farverige gitter af lærred kvadrater, klar til at blive opdateret og tegnet igen:

Interaktivitet

Den største 'smerte' med lærred i sammenligning med SVG eller HTML er, at der ikke er nogen materielle elementer, der bor i DOM. Hvis der var, kunne du bare registrere lyttere til elementerne og tilføje håndlere til lytterne. For eksempel kan du udløse en musestik over et SVG-rekt-element, og når lytteren trigger, kan du gøre noget mod rekt. Som at vise dataværdier, der er gemt med rect i en værktøjstip.

Med lærred skal du finde en anden måde at gøre en begivenhed hørt på vores lærredselementer. Heldigvis er der en række kloge mennesker, der tænkte på en indirekte, men logisk måde.

Så hvilken interaktivitet ønsker vi? Som nævnt ovenfor lad os gå efter en værktøjstip, og lad os antage, at du vil vise kvadratindekset i en værktøjstip, så snart du holder musepekeren over elementet. Ikke meget spændende, men nøglen er, at du kan få adgang til de data, der er bundet til elementet ved at holde musen hen over det.

Picking

Der er et par trin involveret (dog alle logiske). Men kort sagt bygger du to lærreder for at opnå dette. Et vigtigt lærred, der producerer vores visuelle og et skjult lærred (som i vi ikke kan se det), der producerer det samme visuelle. Nøglen her er, at alle elementer på det andet lærred vil være nøjagtigt den samme position i forhold til lærredets oprindelse sammenlignet med det første lærred. Så firkant 1 begynder på 0,0 på det vigtigste lærred såvel som på det skjulte lærred. Firkant 2 starter på 8,0 på det vigtigste lærred såvel som på det skjulte lærred osv.

Der er kun en vigtig forskel. Hvert element på det skjulte lærred får en unik farve. Vi opretter et objekt (eller rettere sagt en tilknyttet matrix eller kort til kortfattethed), der forbinder hver unik farve til hvert enkelt elements data.

Hvorfor? For næste vedhæfter vi en museflyttelytter til hovedduken for at hente en strøm af musepositioner. På hver museposition kan vi bruge en lærred-egen metode til at "vælge" farven på denne nøjagtige position. Så ser vi bare farven op i vores associerende matrix, og vi har dataene! Og vi flyver ...

Af Kenny Louie, 2010 https://creativecommons.org/licenses/by/2.0/

Du kan sige, "ja, mine firkanter har allerede fået en unik farve, jeg kan bruge dem?" Og faktisk kunne du bruge dem. Din interaktivitet vil dog gå ud af vinduet, så snart du beslutter dig for at fjerne dine firkanter fra farverne. Så du skal sørge for altid at have et lærred - det skjulte lærred - der har et garanteret sæt unikke farver til firkanterne.

Lad os anvende denne teknik trin for trin. Den kode, du har opbygget indtil videre, kan forblive som den er - du tilføjer bare den, mens du følger med.

1. Forbered det skjulte lærred

Lad os først oprette det skjulte lærred, der vil huse vores visuelle med en unik farve pr. Kvadrat.

1.1 Opret skjult lærredselement og indstil dets CSS til {display: none; }.

// Omdøb hovedlærredet, og tilføj en 'mainCanvas'-klasse til det.
var mainCanvas = d3.select ('# container')
  .append ( 'lærred')
  .classed ('mainCanvas', sandt)
  .attr ('bredde', bredde) .attr ('højde', højde);
 
// nyt -----------------------------------
// Tilføj det skjulte lærred og giv det klassen 'HiddenCanvas'.
var HiddenCanvas = d3.select ('# container')
  .append ( 'lærred')
  .classed ('HiddenCanvas', sandt)
  .attr ('bredde', bredde)
  .attr ('højde', højde);

Faktisk satte jeg ikke lærredet til skjult i dette eksempel for at vise, hvad der foregår. Men for at gøre det skal du bare tilføje .hiddenCanvas {display: none; } til din CSS og gerningen er udført.

1.2 Byg kontekstvariablen i funktionen draw (), og send to argumenter til funktionen: lærredet samt en boolsk kaldet 'skjult', der bestemmer hvilket lærred vi bygger (skjult = sandt || falske) som i:

funktionstegn (lærred, skjult) {

1.3 Du skal nu tilpasse alle tegnefunktioner til at inkludere de to nye draw () argumenter. Så fra nu af kalder du ikke bare draw () du kalder enten draw (mainCanvas, falsk) eller draw (HiddenCanvas, sandt)

2. Anvend unikke farver på de skjulte elementer, og kortlæg dem

Her, kære læser, kommer den vigtigste del af vores drift, motoren på vores lastbil, krydderiet i vores suppe.

Af Andrew Becraft, 2007 https://creativecommons.org/licenses/by-nc-sa/2.0/

2.1 Inkluder en funktion til at generere en ny unik farve, hver gang den bliver kaldt (via Stack Overflow)

// Funktion til at oprette nye farver til plukningen.
var nextCol = 1;
funktion genColor () {
  
  var ret = [];
  if (nextCol <16777215) {
    
    ret.push (nextCol & 0xff); // R
    ret.push ((næsteCol & 0xff00) >> 8); // G
    ret.push ((næsteCol & 0xff0000) >> 16); // B
    nextCol + = 1;
  
  }
var col = "rgb (" + ret.join (',') + ")";
retur col;
}

genColour () producerer en farvedefinerende streng i formen rgb (0,0,0). Hver gang det kaldes, øges R-værdien med én. Når den når 255, øges den G-værdien med 1 og nulstiller R-værdien til 0. Når den når r (255,255,0), øges den B-værdien ved at 1 nulstille R og G til 0 og så videre.

Så i alt kan du have 256 * 256 * 256 = 16.777.216 elementer for at bevare en unik farve. Jeg kan dog forsikre dig om, at din browser dør på forhånd. Selv med lærred (webGL-tutorial at følge).

2.2 Opret kortobjektet, der holder styr på, hvilket brugerdefineret element, der har hvilken unik farve:

var colourToNode = {}; // Kort for at spore farve på noder.

Du kan tilføje funktionen genColour (), hvor du vil, i dit script, så længe det er uden for databind () og tegne () -funktionsomfanget. Men vær opmærksom på, at din kortvariabel skal oprettes inden og uden for databind-funktionen ().

2.3 Tilføj en unik farve til hvert brugerdefineret element som for eksempel .attr ('fillStyleHidden') og
2.4 opbygge kortobjektet under oprettelse af elementer

Her bruger du din 'color-canon' genColour () i vores databaseind ​​() -funktion, når du tildeler fillStyle til vores elementer. Da du også har adgang til hver datapoint, mens det er bundet til hvert element, kan du bringe farve og data sammen på dit colourToNode-kort.

tilslutte
  .merge (enterSel)
  .transition ()
  .attr ('bredde', cellestørrelse)
  .attr ('højde', cellestørrelse)
  .attr ('fillStyle', funktion (d) {
    retur farveskala (d.værdi);
  });
  // nyt ----------------------------------------------- ------
  
  .attr ('fillStyleHidden', funktion (d) {
    hvis (! d.hiddenCol) {
      d.hiddenCol = genColor ();
      colourToNode [d.hiddenCol] = d;
    }
    // Her tilføjer du (1) en unik farve som egenskab til hvert element
    // og (2) kortlægge farven til noden i farvenToNode-kortet.
    retur d.hiddenCol;
});

2.5 Du kan nu farve elementerne i henhold til det lærred, som funktionen draw () gengiver. Du tilføjer en betinget af fillStyle i funktionen draw (), der anvender farverne til vores visuelle på det vigtigste lærred og de unikke farver på det skjulte lærred. Det er en enkel enforing:

context.fillStyle = skjult? node.attr ('fillStyleHidden'): node.attr ('fillStyle');
// Nodefarven afhænger af det lærred, du tegner.

Det vigtigste lærred ligner naturligvis det samme:

Lad os endelig tilføje noget interaktivitet og begynde med at tegne det skjulte lærred, hver gang vi flytter musen på vores vigtigste lærred.

3. Tag farverne op med musen

3.1 Først skal du blot registrere en lytter til det vigtigste lærred og lytte til musebevægelser.

d3.select ('. mainCanvas'). on ('mousemove', funktion () {
});

Hvorfor mousemove? Da du ikke kan registrere lyttere med individuelle firkanter, men skal bruge hele lærredet, vil du ikke være i stand til at arbejde med mouseover eller -out begivenheder, da de kun udløses, når du indtaster lærredet ikke elementerne. For at få musepositionen på dit lærred kan du gøre musemove eller klikke / mousedown.

d3.select ('. mainCanvas'). on ('mousemove', funktion () {
  tegne (skjultCanvas, sandt); // Tegn det skjulte lærred.
});

På denne måde er den første ting, som vores bruger udløser, når han mouses over det vigtigste lærred, ubevidst at oprette det skjulte lærred. Som sagt ville dette lærred i produktionen være skjult, men til vores uddannelsesmæssige formål ønsker vi at se det og faktisk udløse det skjulte lærred, der skal tegnes, når musen bevæger sig over det vigtigste lærred sådan:

Farverne på det vigtigste lærred varierer fra sort til rød, fra rgb (0,0,0) til rgb (255,0,0), og så ser det ud som om det samme interval fra sort til rødt gentages. Nu er farven imidlertid fra en lidt grønnere sort, netop fra rgb (0,1,0) til rgb (255,1,0):

Zoom ind i det første par hundrede firkanter, her er farverne på den første, den 256. og den 257. firkant:

3.3 Da vores skjulte lærred strukturelt er en kulstofkopi af vores vigtigste lærred, vil alle de skjulte lærredselementer være på samme placering som elementerne på vores vigtigste lærred. Så du kan nu bruge musens x- og y-positioner, du indsamler fra lytteren på det vigtigste lærred til at etablere den samme placering på det skjulte lærred. Tilbage i lytteren tilføjer du:

d3.select ('. mainCanvas'). on ('mousemove', funktion () {
  
  // Tegn det skjulte lærred.
  tegne (skjultCanvas, sandt);
  // Hent musepositioner fra det vigtigste lærred.
  var mouseX = d3.event.layerX || d3.event.offsetX;
  var musY = d3.event.layerY || d3.event.offsetY; });

Bemærk her vi tager egenskaberne event.layerX og event.layerY, der returnerer musepositionen inklusive rulle. Dette kan gå i stykker, så brug offsetX som et tilbageslag (eller bare brug offsetX).

3.4 Picking: Canvas giver i høj grad adgang til de pixeldata, som musen svæver over med getImageData () -funktionen og dens .data-egenskab. I fuld blomst ser det ud som:

getImageData (posX, posY, 1, 1) .data.

Det returnerer en matrix med fire tal: R, G, B og alfa værdien. Da du flittigt byggede kortet ColourToNode, der tildeler elementdataene til hver af dets skjulte farver, kan du nu få adgang til dette elements data ved blot at slå farven op på kortet!

d3.select ('. mainCanvas'). on ('mousemove', funktion () {
  // Tegn det skjulte lærred.
  tegne (skjultCanvas, sandt);
  // Hent musepositioner fra det vigtigste lærred.
  var mouseX = d3.event.layerX || d3.event.offsetX;
  var musY = d3.event.layerY || d3.event.offsetY;
// nyt -----------------------------------------------
  // Hent værktøjskassen til det skjulte lærred.
  var HiddenCtx = HiddenCanvas.node (). getContext ('2d');
  // Vælg farven fra musepositionen.
  var col = HiddenCtx.getImageData (mouseX, mouseY, 1, 1) .data;
  // Streng derefter værdierne på en måde, som vores kortobjekt kan læse det.
  var colKey = 'rgb (' + col [0] + ',' + col [1] + ',' + col [2] + ')';
  // Hent dataene fra vores kort!
  var nodeData = colourToNode [colKey];
  console.log (nodeData);
});

Og ved at logge nodeData til konsollen returneres et objekt hver gang du hover over en firkant:

Data pr. Knudepunkt viser nu den værdi, der udgør de originale data, samt nøglen HiddenCol, der viser denne nodes farve på det skjulte lærred:

3.5 Endelig - og det er en formalitet - tilføjer du værktøjstip

d3.select ('. mainCanvas'). on ('mousemove', funktion () {
  // Tegn det skjulte lærred.
  tegne (skjultCanvas, sandt);
  // Hent musepositioner fra det vigtigste lærred.
  var mouseX = d3.event.layerX || d3.event.offsetX;
  var musY = d3.event.layerY || d3.event.offsetY;
  // Hent værktøjskassen til det skjulte lærred.
  var HiddenCtx = HiddenCanvas.node (). getContext ('2d');
  // Vælg farven fra musepositionen.
  var col = HiddenCtx.getImageData (mouseX, mouseY, 1, 1) .data;
  // Streng derefter værdierne på en måde, som vores kortobjekt kan læse det.
  var colKey = 'rgb (' + col [0] + ',' + col [1] + ',' + col [2] + ')';
  // Hent dataene fra vores kort!
  var nodeData = colourToNode [colKey];
  
  console.log (nodeData);
  // nyt -----------------------------------------------
  if (nodeData) {
  // Vis værktøjstip kun når der er nodeData fundet af musen
    d3.select ( '# værktøjstip)
      .style ('opacitet', 0,8)
      .style ('top', d3.event.pageY + 5 + 'px')
      .style ('venstre', d3.event.pageX + 5 + 'px')
      .html (nodeData.value);
  } andet {
  // Skjul værktøjstipet, når musen ikke finder nodeData.
  
    d3.select ('# værktøjstip') .stil ('opacitet', 0);
  
  }
}); // lærred / lytter af lærred

Det er det! Du har visualiseret et stort antal elementer på lærred - mere end du ville have kunnet nyde problemfri med SVG. Du brugte stadig D3s livscyklusmodel, og du tilføjede en vis interaktivitet for at få adgang til de data, der er knyttet til hvert element. Disse tre trin skal give dig mulighed for at gøre stort set noget eller i det mindste mere end det, du er vant til, når du arbejder med D3 og SVG.

Der er en trinvis vejledning fra bunden til interaktivt D3 / lærred på min blog, der tillader interne sidekoblinger. På denne måde kan du se hele processen i en visning og nemt klikke dig igennem den:

Klik for at komme til manualen

... og her er den fulde kode igen.

Jeg håber, at du nød at læse dette og venligst hilse og / eller ...

lars verspohl www.datamake.io @lars_vers https://www.linkedin.com/in/larsverspohl

… Er altid taknemmelig for en lignende eller for et følger, han kan vende tilbage.