D3 was probably sold to you under the banner of interactive visualization! And yet, hiding somewhere in the back of your head, you’re saying to yourself come on dude, we haven’t done a single goddamn interactive thing. So today we’re going to do those goddamn interactive things.
When your mouse does something to an element on the page - hovers over it, clicks it, etc etc etc - these are called mouse events. D3 supports mouse events just the same as it supports everything else!
Clicking on a data point works like this:
rectangles.on('click', function(d, i) {
console.log("You clicked", d), i;
})
And hovering onto a data point works like this:
rectangles.on('mouseover', function(d, i) {
console.log("Your mouse went over", d, i);
})
d
is (as always) the data point, and i
is (as always) the index of the element you’re looking at.
Not so bad, right? Take a look at the following code, then open up the JS console to see what’s happening as you move your mouse over the circles.
<svg></svg>
<script>
var datapoints = [
{ 'title': 'Pride and Prejudice', 'author': 'Jane Austen', 'words': 120000, 'published': 1813 },
{ 'title': 'Cryptonomicon', 'author': 'Neal Stephenson', 'words': 415000, 'published': 1999 },
{ 'title': 'Great Gatsby', 'author': 'F. Scott Fitzgerald', 'words': 47094, 'published': 1925 },
{ 'title': 'Song of Solomon', 'author': 'Toni Morrison', 'words': 92400, 'published': 1977 },
{ 'title': 'White Teeth', 'author': 'Zadie Smith', 'words': 169000, 'published': 2000 }
];
var height = 200,
width = 400;
var svg = d3.select("svg").attr('height', height).attr('width', width);
var max_words = d3.max(datapoints, function(d) { return d['words']; });
var x_scale = d3.scale.linear().domain([1800, 2015]).range([0, width]);
var y_scale = d3.scale.linear().domain([0, 500000]).range([height, 0]);
svg.selectAll('circle')
.data(datapoints)
.enter()
.append('circle')
.attr('r', 10)
.attr('cx', function(d) { return x_scale(d['published']) })
.attr('cy', function(d) { return y_scale(d['words']) })
.on('click', function(d, i) {
console.log("click", d);
})
.on('mouseover', function(d, i) {
console.log("mouseover", d);
})
.on('mouseout', function(d, i) {
console.log("mouseout", d);
})
</script>
But that stuff’s not so fun, what’s fun is when you can change the element you’re clicking or hovering on.
In order to make a change, you need to use this thing called this
. Don’t ask me what it is or anything, just be a good kid and use console.log(this);
and look at it.
NOTE: You could I guess read something like this if you wanted.
this
in this particular instance is the circle you’re clicking or the rectangle you’re hovering or whatever random element you’re interacting with. As a result, you can use d3 to select is and then change its attributes, just like anything else
.on('click', function(d, i) {
d3.select(this)
.transition()
.attr('r', 20);
})
.on('mouseover', function(d, i) {
d3.select(this)
.transition()
.attr('fill', '#ff0000');
})
Try clicking and hovering on the elements below.
<svg></svg>
<script>
var datapoints = [
{ 'title': 'Pride and Prejudice', 'author': 'Jane Austen', 'words': 120000, 'published': 1813 },
{ 'title': 'Cryptonomicon', 'author': 'Neal Stephenson', 'words': 415000, 'published': 1999 },
{ 'title': 'Great Gatsby', 'author': 'F. Scott Fitzgerald', 'words': 47094, 'published': 1925 },
{ 'title': 'Song of Solomon', 'author': 'Toni Morrison', 'words': 92400, 'published': 1977 },
{ 'title': 'White Teeth', 'author': 'Zadie Smith', 'words': 169000, 'published': 2000 }
];
var height = 200,
width = 400;
var svg = d3.select("svg").attr('height', height).attr('width', width);
var max_words = d3.max(datapoints, function(d) { return d['words']; });
var x_scale = d3.scale.linear().domain([1800, 2015]).range([0, width]);
var y_scale = d3.scale.linear().domain([0, 500000]).range([height, 0]);
svg.selectAll('circle')
.data(datapoints)
.enter()
.append('circle')
.attr('r', 10)
.attr('cx', function(d) { return x_scale(d['published']) })
.attr('cy', function(d) { return y_scale(d['words']) })
.on('click', function(d, i) {
console.log("clicking on", this);
// transition the clicked element
// to have a radius of 20
d3.select(this)
.transition()
.attr('r', 20);
})
.on('mouseover', function(d, i) {
console.log("mouseover on", this);
// transition the mouseover'd element
// to having a red fill
d3.select(this)
.transition()
.attr('fill', '#ff0000');
})
</script>
Notice how the circles stay red after you stop hovering? That’s because mouseover
isn’t exactly the same as what you’d think “hover” means - instead, it’s what should happen when you mouse moves on top of the element. It doesn’t care at all about when you move off.
Of course, another event does - mouseout
! Easy enough to remember, right? To do a hover, more or less you need to have mouseover
make the hover effect and then have mouseout
remove it.
Let’s add a hover effect that goes away when you mouseout
the element.
<svg></svg>
<script>
var datapoints = [
{ 'title': 'Pride and Prejudice', 'author': 'Jane Austen', 'words': 120000, 'published': 1813 },
{ 'title': 'Cryptonomicon', 'author': 'Neal Stephenson', 'words': 415000, 'published': 1999 },
{ 'title': 'Great Gatsby', 'author': 'F. Scott Fitzgerald', 'words': 47094, 'published': 1925 },
{ 'title': 'Song of Solomon', 'author': 'Toni Morrison', 'words': 92400, 'published': 1977 },
{ 'title': 'White Teeth', 'author': 'Zadie Smith', 'words': 169000, 'published': 2000 }
];
var height = 200,
width = 400;
var svg = d3.select("svg").attr('height', height).attr('width', width);
var max_words = d3.max(datapoints, function(d) { return d['words']; });
var x_scale = d3.scale.linear().domain([1800, 2015]).range([0, width]);
var y_scale = d3.scale.linear().domain([0, 500000]).range([height, 0]);
svg.selectAll('circle')
.data(datapoints)
.enter()
.append('circle')
.attr('r', 10)
.attr('cx', function(d) { return x_scale(d['published']) })
.attr('cy', function(d) { return y_scale(d['words']) })
.on('mouseover', function(d, i) {
console.log("mouseover on", this);
// make the mouseover'd element
// bigger and red
d3.select(this)
.transition()
.duration(100)
.attr('r', 20)
.attr('fill', '#ff0000');
})
.on('mouseout', function(d, i) {
console.log("mouseout", this);
// return the mouseover'd element
// to being smaller and black
d3.select(this)
.transition()
.duration(100)
.attr('r', 10)
.attr('fill', '#000000');
})
</script>
Not so bad!
For a little inspiration, check out there two talks on making videogames more exciting: juice it or lose it and the art of screenshake.
You get the idea.