goal.svg
goal.svg
is an SVG document that shows the important SVG elements attributes we let D3.js create, update and delete.
As can be seen, we draw a bar chart with
<rect>
elements and label them with
<text>
elements:
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<style>
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400");
</style>
<rect height="40" width="500" y= "10" x="10" fill="#cd4ae7"/>
<rect height="40" width="350" y= "70" x="10" fill="#cd4ae7"/>
<rect height="40" width="400" y="130" x="10" fill="#cd4ae7"/>
<text font-family="Open Sans" font-size="30" y= "40" x="20" fill="#56ffbe">Text one</text>
<text font-family="Open Sans" font-size="30" y="100" x="20" fill="#56ffbe">Text two</text>
<text font-family="Open Sans" font-size="30" y="160" x="20" fill="#56ffbe">Text three</text>
</svg>
HTML/JavaScript source code
The
JavaScript source code defines an variable named
dataSets
which is an array of three data sets, each of which stores its data points in an array (i. e.
dataSets
is an array of arrays).
Each data point is an object which has a val
whose value controls the length of the individual bars when drawn, and a name
whose value will be used to label the bars.
The bar chart will be rendered in the
<svg>
element which is provided in the HTML document's
body.
When the button is clicked and showNextDataset()
is invoked, the function will display the next data in dataSets
.
First
- It selects all
<rect>
elements in the <svg>
(d3.select…
) …
- … and «combines» it with the current dataset (
.data(dataSets[nextDataset])
) …
- … and assigns the result to
allRects
allRects.enter()
then
- determines how many new rects need to be created (number of data points in current data set minus number of rects already existing ().
- Each rect, that needs to be created will then be created (
.append('rect')
) …
- … and for each newly created rect, the relevant attributes are set
attr('x', …)
etc.
Because after the second click on the mouse button, some rects will then already exist, these need to be updated (width
attribute) rather than created.
However, the newly created ones have a different value for the width
attribute.
The «merge» step sets the width
for both, the newly created and also the updated rect.
The rects that need updating are determined with newRects.merge(allRects)
.
Finally, when the new data set has less datapoints than the currently shown one, some rects need to be deleted.
The set of rects to be deleted is determined with allRects.exit()
.
The rects that need to be updated are determined with newRects.merge(allRects)
go.html
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<title>Enter merge exit</title>
<link rel ="stylesheet"
href="https://fonts.googleapis.com/css?family=Open+Sans:400">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
<script>
let dataSets = [
[ {name: 'foo', val: 173}, {name: 'bar', val: 280}, {name: 'baz' , val: 215} ],
[ {name: 'A' , val: 206}, {name: 'B' , val: 305}, {name: 'C' , val: 288}, {name: 'D' , val: 184}, {name: 'E', val: 125} ],
[ {name: 'one', val: 313}, {name: 'two', val: 127}, {name: 'three', val: 256}, {name: 'four', val: 284},]
];
let nextDataset = 0;
function showNextDataset() {
let allRects = d3.select('svg')
.selectAll('rect')
.data(dataSets[nextDataset])
;
//
// Create new rects
//
let newRects = allRects.enter()
.append('rect')
.attr('x' , '10' )
.attr('height', '40' )
.attr('fill' , '#cd4ae7')
.attr('y' , (d, i) => {console.log("i = " + i); return 10 + i*60;})
;
//
// Determine rects that need to be updated (new rects + existing rects)
//
let rectsToUpdate = newRects.merge(allRects);
rectsToUpdate
.transition()
.attr('width', (d, i) => {console.log("d.val = " + d.val); return d.val;})
;
//
// If number of rects is larger than data points,
// some need to be deleted
//
let rectsToDelete = allRects.exit();
rectsToDelete
.transition()
.attr('width' , 0)
.attr('height', 0)
.remove()
;
// --- Same thing for text ---
let allTexts = d3.select('svg')
.selectAll('text')
.data(dataSets[nextDataset])
;
let newTexts = allTexts.enter()
.append('text')
.attr('font-family', 'Open Sans')
.attr('font-size' , '0' )
.attr('height' , '40' )
.attr('x' , '20' )
.attr('fill' , '#56ffbe' )
.attr('y' , (d, i) => {console.log("i = " + i); return 40 + i*60;})
;
let textsToUpdate = newTexts.merge(allTexts);
textsToUpdate
.transition()
.attr('font-size', '30')
.text(d => d.name )
;
let textsToDelete = allTexts.exit();
textsToDelete
.transition()
.attr('font-size', 0)
.remove()
;
nextDataset = (++nextDataset) % dataSets.length;
}
</script>
</head>
<body onload="showNextDataset()">
<svg width=400 height=300 style="background-color:#f3f3f3">
</svg>
<p>
<button onclick="showNextDataset()">Show next dataset</button>
</body>
</html>