D3 Tips & Tricks - marcoFijan/frontend-data GitHub Wiki
Basics of D3
Append
Om svg vormen te creeëren gebruik je .append. Er zijn verschillende keywords om vormen te maken:
.append("svg") //create (the) svg
.append("rect") //create retangle
.append("circle") //create circle
.append("g") // group multiple svg's together
Selection
Voordat je elementen kan appenden, moet je selecteren waaraan je de svg aan wilt appanden:
.select("rect") // select one retrangle
.selectAll("rect") // select all rectangles
Attribute
Om je sellect aan te passen, gebruik je attributes. Hieronder zijn de belangrijkste voorbeelden:
.attr('width', 300) // de breedte van de chart in pixels
.attr('height', 200) // de hoogte van de chart in pixels
.attr('y', d => scaleX(d.id)) // Verdeel de bars over de y as
.attr('x', 300) // Positie van de x as in pixels
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') // om je margin mee te geven
Je kunt ook css eigenschappen meegeven. Maar het liefst doe je dit in css zelf. Wel kun je een class meegeven zodat je in css eigenschappen kunt aanpassen:
.attr('class', 'className')
Speciale attributen
Er zijn ook attributen die hun eigen 'naam' hebben omdat ze vaak voorkomen of een speciale handeling uitvoeren. Hieronder een paar voorbeelden:
.text('Een titel') // text attribute voor titels en Axis-beschrijving
.transition().duration(500) // add a transition
.call(d3.axisBottom(scaleX)) // 'call' the axis at the bottom add a scale and show it on screen
Data Join
De data kan worden gevisualiseerd door datajoining te gebruiken. Hier geef aan welke elementen gevisualiseerd moeten worden en hoe ze eruit moeten zien.
Data join bestaat uit 3 onderdelen:
- Enter: Er zijn meer 'dataregels' dan DOM-elementen > zorg ervoor dat er genoeg DOM-elementen zijn voor alle dataregels
- Exit: Er zijn meer DOM-elementen dan dat er data is, verwijder de DOM-elementen die te veel aangemaakt zijn
- Update: Dezelfde DOM-elementen zijn aanwezig maar de waarden zijn gewijzigd > teken opnieuw deze elementen...
svg.selectAll("rect").data(data) //Select all rectangles, and join them with the data
.enter().append("rect") // make for every datarow a rectangle
.attr("width", 300) // the width
.attr("height", 300) // the height
.attr("x", 20) // the position of x
.attr("y", 30) // the position of y
Schalen van data
Data kan op verschillende wijze geschaald worden. Voor het schalen wordt er gebruik gemaakt van domain en range
- Domain: Het bereik tussen de minimale en maximale waarden van de data > Data Space
- Range: Het bereik van de minmale en maximale waarden in de chart > Screen Space In javascript ziet dit er zo uit:
const xScale = scaleLinear()
.domain([0, max(data, d => d.population)])
.range([0, width])
In totaal zijn er 12 verschillende manieren om te schalen binnen D3. Deze kunnen worden opgesplitst in de volgende categorieën:
- Continuous input and continuous output
- Continuous input and discrete output
- Discrete input and discrete output
Continuous input and continuous output
Voor Continuous input and continuous output zijn er verschillende schalen.
scaleLinear()
Met scaleLinear() geef je met getallen aan wat de waarden zijn en in welke waarde deze terug gegeven moeten worden. scaleLinear() is de bekendste en wordt het meest gebruikt doordat veel charts schalen met hoogtes van waarden.
const yScale = d3.scaleLinear()
.domain([0, 10])
.range([0, 800]);
yScale(0); // returns 0
yScale(5); // returns 400
yScale(10); // returns 800
Een leuke extra eigenschap van scaleLinear() is dat hij ook kleurwaarde kan gebruiken om te schalen:
const yScale = d3.scaleLinear()
.domain([0, 10])
.range(['yellow', 'red']);
yScale(0); // returns "rgb(255, 255, 0)"
yScale(5); // returns "rgb(255, 128, 0)"
yScale(10); // returns "rgb(255, 0, 0)"
scalePow()
Met scalePow() kun je exponentiële grafieken maken. Eigenlijk doet dit hetzelfde als scaleLinear(), alleen heb je bij scalePow nog de optie om een exponent toe te voegen (x^k). Dit doe je dan ook door .exponent() te gebruiken voordat je je domain en range meegeeft:
const yScale = d3.scalePow()
.exponent(0.5)
.domain([0, 100])
.range([0, 30]);
yScale(0); // returns 0
yScale(50); // returns 21.21...
yScale(100); // returns 30
scaleSqrt
Met scaleSqrt kunnen er makkelijk cirkels gemaakt worden en die van grootte voorzien aan de hand van data. Zo kun je met de grootte bijvoorbeeld laten zien hoe hoog de datawaarde is. Grote cirkel = hoog, kleine cirkel = laag. Uiteraard kun je dit ook omdraaien door de domain of range aan te passen.
const yScale = d3.scaleSqrt()
.domain([0, 100])
.range([0, 30]);
yScale(0); // returns 0
yScale(50); // returns 21.21...
yScale(100); // returns 30
scaleLog
Met scaleLog kun je data visualiseren met de log() functie erin. De x-as wordt dan meegenomen in de log(). Dit is een perfecte schaal wanneer je data op de as exponentieel moet weergeven (10, 100, 1000, 10.000, etc).
const yScale = d3.scaleLog()
.domain([10, 100000])
.range([0, 600]);
yScale(10); // returns 0
yScale(100); // returns 150
yScale(1000); // returns 300
yScale(100000); // returns 600
scaleTime()
ScaleTime lijkt heel erg op scaleLinear alleen neemt dan tijd als waarde. Dit kunnen minuten zijn, seconde, dagen, maanden of een combinatie van deze eigenschappen. ScaleTime wordt voornamelijk gebruikt voor de x-as maar is ook te gebruiken op de y-as wanneer je meerdere dagen op een rij beschikbaar hebt.
const yScale = d3.scaleTime()
.domain([new Date(2016, 0, 1), new Date(2017, 0, 1)])
.range([0, 700]);
yScale(new Date(2016, 0, 1)); // returns 0
yScale(new Date(2016, 6, 1)); // returns 348.00...
yScale(new Date(2017, 0, 1)); // returns 700
scaleSequential
Met scaleSequential kun je continuous waarden weergeven met een preset. d3 Komt zelf met verschillende presets maar het is ook mogelijk om zelf een preset, interpolator, te maken. In die interpolator geef je inputs tussen 0 en 1 verschillende waarden mee. Dit kunnen kleuren zijn, nummers of zelfs strings.
const yScale = d3.scaleSequential()
.domain([0, 100])
.interpolator(d3.interpolateRainbow);
yScale(0); // returns 'rgb(110, 64, 170)'
yScale(50); // returns 'rgb(175, 240, 91)'
yScale(100); // returns 'rgb(110, 64, 170)'
Extra functies voor Continuous input and continuous output
Clamping
Het is mogelijk om een hogere range te geven dan dat de domain heeft.
.domain([0, 10])
.range([0, 100])
Om er voor te zorgen dat je geen foutieve waarden krijgt wanneer je domain(-10) of domain(20) opvraagt, kun je scaleType.clamp(true) gebruiken. Dan worden alleen de minimale of maximale waarden van de range gegeven als er waarden boven de domain gevraagd worden.
Nice
Vaak eindigd je domain niet op een perfect mooi rond getal. Om toch je data mooi te visualiseren met als hoogste waarde een hogere waarde dan de maximale waarde van je domain, kun je nice() gebruiken. Door .nice() te zetten na je .domain en .range notatie wordt je as mooi afgerond opgesteld.
Meerdere segmenten
Het is mogelijk om in je range en domain ook een langere array mee te geven. d3 Zorgt er dan voor dat hij de domain tussenwaarden koppelt aan de range. De domain moet dan uiteraard wel in chronologische volgorde geschreven worden.
const yScale = d3.scaleLinear()
.domain([-10, 0, 10])
.range(['red', '#ddd', 'blue']);
invert()
Je kunt waarden omdraaien door bij het aanroepen van de scale, .invert() te gebruiken.
const yScale = d3.scaleLinear()
.domain([0, 10])
.range([0, 100]);
yScale.invert(50); // returns 5
yScale.invert(100); // returns 10
Continuous input and discrete output
Er kan ook een output gegeven worden in range die niet gelijk is met de domain. Dit zijn continuous input and discrete output schalen.
scaleQuantize()
Met scaleQuantize kun je meerdere discrete output waardes geven in de range. d3 Splitst die waardes dan automatisch over de domain.
const yScale = d3.scaleQuantize()
.domain([0, 100])
.range(['lightblue', 'orange', 'lightgreen', 'pink']);
yScale(10); // returns 'lightblue'
yScale(30); // returns 'orange'
yScale(90); // returns 'pink'
scaleQuantize() 'clamped' de waarden automatisch. Als er yScale(110) wordt weergegeven wordt deze bijvoorbeeld gewoon roze.
scaleQuantile()
Met scaleQuantile() doe je eigenlijk andersom dan wat je bij scaleQuantize doet. Hier geef je meerdere domain waarden, maar minder range waarde. d3 Verdeeld dan vervolgens de eigenschappen van de range over de waarden van domain.
const myData = [0, 5, 7, 10, 20, 30, 35, 40, 60, 62, 65, 70, 80, 90, 100];
const yScale = d3.scaleQuantile()
.domain(myData)
.range(['lightblue', 'orange', 'lightgreen']);
yScale(0); // returns 'lightblue'
yScale(20); // returns 'lightblue'
yScale(30); // returns 'orange'
yScale(65); // returns 'lightgreen'
scaleThreshold()
Met scaleThresshold kun je aangeven wat wel en niet binnen de domain zit. Wanneer je buiten de domain iets laat zien, kun je dit aantonen met bijvoorbeeld kleur. Zo kun je als je buiten de domain zit grijs tonen, en als je binnen de domain zit een kleur. Dit is misschien voor mij een leuke om te kijken of er genoeg parkeerplaatsen zijn ten opzichte van de hoeveelheid er nodig zijn in die provincie.
const yScale = d3.scaleThreshold()
.domain([0, 50, 100])
.range(['#ccc', 'lightblue', 'orange', '#ccc']);
yScale(-10); // returns '#ccc'
yScale(20); // returns 'lightblue'
yScale(70); // returns 'orange'
yScale(110); // returns '#ccc'
Discrete input and discrete output
Deze schalen kun je gebruiken wanneer er niet schaalbare elementen gebruikt worden. Dus strings in de range en strings in de domain.
scaleOrdinal()
Met scaleOrdinal kun je eigenschappen meegeven per waarde in de array. De waarden komen uit de domain en de eigenschappen geef je mee in de range. Het is mogelijk om in de array van de range een patroon mee te geven. Bijvoorbeeld zwart, grijs, grijs. Het eerste element van de domain is dan zwart, tweede en derde element grijs, vierde weer zwart en vijfde en zesde element weer grijs.
const myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
const yScale = d3.scaleOrdinal()
.domain(myData)
.range(['black', '#ccc', '#ccc']);
yScale('Jan'); // returns 'black';
yScale('Feb'); // returns '#ccc';
yScale('Mar'); // returns '#ccc';
yScale('Apr'); // returns 'black';
Je kunt hier ook een piechart mee maken
scaleBand
scaleBand verdeeld de domainwaarden over de as in de range. In de range geef je aan hoeveel 'ruimte' je hebt voor de waarden in de domain, En in de domain zitten dan de niet schaalbare waarden. Zoals maanden, soorten, type enzovoort. scaleBand wordt vaak gebruikt voor de x-as voor een staafdiagram.
const xScale = d3.scaleBand()
.domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
.range([0, 200]);
xScale('Mon'); // returns 0
xScale('Tue'); // returns 40
xScale('Fri'); // returns 160
Om de breedte van de scaleBand waarde op te vragen, gebruik je bandwidth().
xScale.bandwidth(); // returns 40
Daarnaast is het ook mogelijk om padding toe te voegen. Dit kan per band / staaf (met .paddingInner). Of tussen rond je collectie van bands / staven (.paddingOuter).
Let er wel op dat paddingOuter en paddingInner gebruik maken van percentages van de breedte van de band. xScale.paddingInner(0,05) is dus 5 procent van de breedte van de band
scalePoint()
scalePoint verdeelt net zoals scaleBand de elementen in de array van de domain gelijk over een as. Alleen maakt scalePoint gebruik van cirkels in plaats van band.
const xScale = d3.scalePoint()
.domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
.range([0, 500]);
xScale('Mon'); // returns 0
xScale('Tue'); // returns 125
xScale('Fri'); // returns 500
Om de afstand te verkrijgen tussen de stippen, kun je step() gebruiken.
pointScale.step(); // returns 125
Tips en Tricks
- Om data te converteren van strings naar integers, gebruik je de +variable notatie:
data.forEach(d => {
d.population = +d.population
})
- Maximale waarde ophalen uit data met max()
max(_dataArray_, _function-that-returns-wanted-value_)
///////////////////////////////////////
max(data, d => d.population)