Sheng Long
11/09/2023
D3.js
-- what is it and why do we use SVG D3.js
?<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="700"
height="1000"
id="svgExample">
<defs id="mydefinedLineID"/>
<g id="horz_line">
<path
d="M 100 100 L 300 100"
id="myPath"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1"/>
</g>
</svg>
This is what code for a black line looks like
editing style directly allows us to change image directy
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<div id="content"></div>
</body>
</html>
d3.select()
or d3.selectAll()
: d3.select
only selects the first matching element d3.selectAll
selects all matching elements in document orderd3.selectAll("p").select("b")
selection.attr()
or selection.style()
For example, change the background red
d3.select(document.body).style("background", "red");
Or: make a clicked paragraph red
d3.selectAll("p").on("click", (event) => d3.select(event.currentTarget).style("color", "red"));
.append
and .insert
methods..remove
..each
method lets you call a function for each element of a selection.The .call
method allows a function to be called into which the selection itself is passed as the first argument.
.call
is useful where you want a reusable function that operates on a selection.
There are also filtering .filter
and sorting .sort
methods
d3.select(container)
.selectAll(element-type)
.data(array)
.join(element-type)
1. Select #content
and add svg
element with g
container element to it:
2. Join an array of data set to circles and add it to the SVG element
Observe there is nothing because the circles don't have any attributes.
4.
There are four methods for loading data: d3.csv()
, d3.json()
, d3.tsv()
, d3.xml()
. We'll use d3.csv()
for this tutorial.
Typically we have arrays of objects (rather than arrays of numbers) that we need to join with the visual elements. D3 can request a CSV file and transform it into an array of objects.
Typical format for requesting data:
url = "..."
d3.csv(url).then(function(data) { console.log(data) });
d3.csv
accepts the URL as its first parameter and returns a promise object..then
method is called.
.then
method. The callback method is called when the file arrives. Its first parameter is the data.Linear scales map a continuous, quantitative input domain to a continuous output range using a linear transformation (translate and scale).
To apply a position encoding:
const x = d3.scaleLinear([10, 130], [0, 960]);
x(20); // 80
x(50); // 320
To apply a color encoding:
const color = d3.scaleLinear([10, 100], ["brown", "steelblue"]);
color(20); // "rgb(154, 52, 57)"
color(50); // "rgb(123, 81, 103)"
This is mapping x, which takes value in [10, 130] to [0, 960];
domain \(\to\) codomain
const x = d3.scaleBand(["a", "b", "c"], [0, 960]);
x("a"); // 0
x("b"); // 320
x("c"); // 640
x("d"); // undefined
.attr("transform", "translate(tx,ty)")
d3.transform
is a helper function; possible other transformations include scale
, rotate
, etc. translate
is moving the graphic element tx, ty away from the "origin". width
and height
for an SVG element, we usually define it for the outer dimensions.
margin.bottom
margin.top
margin.left
margin.right
margin = ({top: 20, right: 30, bottom: 30, left: 40})
https://observablehq.com/@d3/d3-extent
i.e., how to get input range of scales
d3.min
returns the minimum value from the given iterable of valuesd3.max
returns the maximum value from the given iterable of valuesd3.extent
returns [min, max] in a single pass over the input
d3.csv
d3.extent
to obtain range of input
e.g., we can use D3's .rollup
function to group the data by any of the categorical properties.
For more, see https://observablehq.com/@d3/d3-group
let data = [
{"Title": "Adaptation", "Distributor": "Sony Pictures", "Genre": "Comedy", "Worldwide_Gross": 22498520, "Rating": 91},
{"Title": "Air Bud", "Distributor": "Walt Disney Pictures", "Genre": "Comedy", "Worldwide_Gross": 27555061, "Rating": 45},
{"Title": "Air Force One", "Distributor": "Sony Pictures", "Genre": "Action", "Worldwide_Gross": 315268353, "Rating": 78},
{"Title": "Alex & Emma", "Distributor": "Warner Bros.", "Genre": "Drama", "Worldwide_Gross": 15358583, "Rating": 11},
{"Title": "Alexander", "Distributor": "Warner Bros.", "Genre": "Adventure", "Worldwide_Gross": 167297191, "Rating": 16},
{"Title": "Ali", "Distributor": "Sony Pictures", "Genre": "Drama", "Worldwide_Gross": 84383966, "Rating": 67},
{"Title": "Alice in Wonderland", "Distributor": "Walt Disney Pictures", "Genre": "Adventure", "Worldwide_Gross": 1023291110, "Rating": 51},
{"Title": "Alive", "Distributor": "Walt Disney Pictures", "Genre": "Adventure", "Worldwide_Gross": 36299670, "Rating": 71},
{"Title": "All the King's Men", "Distributor": "Sony Pictures", "Genre": "Drama", "Worldwide_Gross": 9521458, "Rating": 11},
{"Title": "Amadeus", "Distributor": "Warner Bros.", "Genre": "Drama", "Worldwide_Gross": 51973029, "Rating": 96}
];
function sumWorldwideGross(group) {
return d3.sum(group, function(d) {
return d.Worldwide_Gross;
});
}
function doRollup(data) {
let groups = d3.rollup(data, sumWorldwideGross,
function(d) { return d.Distributor; },
function(d) { return d.Genre; }
);
// Get Sony Pictures
console.log(groups.get('Sony Pictures'));
// Get Drama within Sony Pictures
console.log(groups.get('Sony Pictures').get('Drama'));
}
doRollup(data);
d3.rollup
groups data
by Distributor
and Genre
;
it returns a nested map object
Each of the groups is then passed into sumWorldwideGross
which returns the sum of Worldwide_Gross
.
d3.select('svg')
.selectAll('circle')
.data(data)
.join('circle')
.attr('cy', 50)
.attr('r', 40)
.transition()
.duration(2000)
.attr('cx', function(d) {
return d;
});
d3.select('svg')
.selectAll('circle')
.data(data)
.join('circle')
.attr('cy', 50)
.attr('r', 40)
.transition()
.delay(2000)
.attr('cx', function(d) {
return d;
});
An easing function defines the change in speed of an element during a transition.
d3.select('svg')
.selectAll('circle')
.data(data)
.join('circle')
.attr('cy', 50)
.attr('r', 40)
.transition()
.ease(d3.easeBounceOut)
.attr('cx', function(d) {
return d;
});
.on("end", repeat)
.join
method:.join(
function(enter) {
...
},
function(update) {
...
},
function(exit) {
...
}
)
enter
is the enter selection which represents the elements that need to be createdupdate
is a selection containing the elements that are already in existence (and aren't exiting)exit
is the exit selection and contains elements that need to to be removedd3.format
allows parsing numbers and time for human consumption .on
method. This method takes two arguments:
d3.selectAll('circle')
.on('click', function(e, d) {
d3.select(this)
.style('fill', 'orange');
});
e:
DOM event object
d:
joined data
to modify e
using D3 you must first select it using d3.select(this)
.
Basically playing something on repeat with https://www.oreilly.com/library/view/introduction-to-d3js/9781800568259/video4_2.html
update
functionsGiven an svg
element containing a g
element:
<svg>
<g class="chart">
</g>
</svg>
and an array let myData = [10, 20, 30, 40, 50]
, one can join this array to circle
elements using
d3.select('.chart')
.selectAll('circle')
.data(myData)
.join('circle')
Since the .join
method returns a selection containing all of the joined elements, we can then update the HTML/SVG elements using .style
, .attr
and .text
<svg>
<g class="chart">
</g>
</svg>
and an array let myData = [10, 20, 30, 40, 50]
, one can join this array to circle
elements using
d3.select('.chart')
.selectAll('circle')
.data(myData)
.join('circle')
Since the .join
method returns a selection containing all of the joined elements, we can then update the HTML/SVG elements using .style
, .attr
and .text
Observe there is nothing plotting on the HTML
That's because we haven't specified any attributes for the circles.