Interactive Notes

While you might know the theory of clicking and hovering and general interaction, you might not have any good ideas about how to use that just yet.

Let’s fix that.

Adding in notes

When we last left our visualization, it was just a bunch of circles that changed color when you moused over them.

Open example in new window

<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) {
      // 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) {
      // return the mouseover'd element
      // to being smaller and black
      d3.select(this)
        .transition()
        .duration(100)
        .attr('r', 10)
        .attr('fill', '#000000');
    })
</script>

Open example in new window

But what is each datapoint? How many pages is it? Who wrote it?

One way to solve this problem is by having an area wholly dedicated to information. Let’s throw in a div that will contain all of our info. While we could in theory position it anywhere, let’s just keep it up top at first.

<div>
  <h3>Book Title</h3>
  <h4>Book Author</h4>
  <p>Published in: <strong>year</strong></p>
  <p>Word count: <strong>published</strong></p>
</div>

And in action:

Open example in new window

<div>
  <h3>Book Title</h3>
  <h4>Book Author</h4>
  <p>Published in: <strong>year</strong></p>
  <p>Word count: <strong>published</strong></p>
</div>
<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) {
      // 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) {
      // return the mouseover'd element
      // to being smaller and black
      d3.select(this)
        .transition()
        .duration(100)
        .attr('r', 10)
        .attr('fill', '#000000');
    })
</script>

Open example in new window

In order to change what’s in our infobox, we’ll need to update its text. And in order to update the text, we need a way to select the text. class and/or id to the rescue!

Let’s overdo it a little:

<div class="infobox">
  <h3 class="title">Book Title</h3>
  <h4 class="author">Book Author</h4>
  <p>Published in: <strong class="published">year</strong></p>
  <p>Word count: <strong class="words">published</strong></p>
</div>

Inside of our on('mouseover'), we are currently using this to change the size and color of the circle. We also have available to us d, the datapoint. You’ll need to keep this clear, so let’s repeat to ourselves:

d is the data point, and this is its representation on the page.

d is the data point, and this is its representation on the page.

d is the data point, and this is its representation on the page.

d is a simple key-value dictionary, so in order to get the title, we use d['title']. Author comes out of d['author'], etc. Those will be the values we put into the infobox.

Now that we know how to get the values from our data point, we just need to grab the elements from the infobox! d3.select will work fine here.

.on('mouseover', function(d, i) {
  // Select the title, use .text to set the content
  // d is the data point
  d3.select(".infobox .title").text(d['title']);
  // Leave the highlight part alone
  // d3.select(this) is the circle on the page
  d3.select(this)
    .transition()
    .duration(100)
    .attr('r', 20)
    .attr('fill', '#ff0000');
})

Now we just need to expand it to cover all of the data we’re looking for - the book’s title, author, publication year, and word count.

Open example in new window

<div class="infobox">
  <h3 class="title">Book Title</h3>
  <h4 class="author">Book Author</h4>
  <p>Published in: <strong class="published">year</strong></p>
  <p>Word count: <strong class="words">published</strong></p>
</div>
<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) {
      // Select the element by class, use .text to set the content
      d3.select(".infobox .title").text(d['title']);
      d3.select(".infobox .author").text(d['author']);
      d3.select(".infobox .published").text(d['published']);
      d3.select(".infobox .words").text(d['words']);
      // 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) {
      // return the mouseover'd element
      // to being smaller and black
      d3.select(this)
        .transition()
        .duration(100)
        .attr('r', 10)
        .attr('fill', '#000000');
    })
</script>

Open example in new window

Looking good! Now our only problem is that our notes are always saying something, even when we aren’t hovering.

We have two CSS-based choices to hide it, one is display: none; and the other one is visibility: hidden;.

  • display: none will completely remove it from the page, and the space it was taking up will have other stuff move into it (namely, the svg). Then when it comes back it’ll push the svg down.
  • visibility: hidden will make it seem invisible. The notes will still take up the space, the svg will still be a little bit below, and then when the notes come back the svg won’t jump around.

In this case we like visibility: hidden;. In order to make it work we need to do three things:

  1. Hide the info box as soon as the page loads
  2. Show the info box when mouseover happens
  3. Hide the info box when mouseout happens

Hide by default:

Let’s manually set the style of the infobox to be invisbile. I know I’ve said a thousand times to never write styles like this, but… just this once!

<div class="infobox" style="visibility: hidden;">
  <h3 class="title">Book Title</h3>
  <h4 class="author">Book Author</h4>
  <p>Published in: <strong class="published">year</strong></p>
  <p>Word count: <strong class="words">published</strong></p>
</div>

Show during mouseover:

Change the visiblity to be visible when the mouse goes over an element

d3.select(".infobox").style('visibility', 'visible');

Hide after mouseout:

Change visibility to be hidden when it goes off

d3.select(".infobox").style('visibility', 'hidden');

What have we got now?

Open example in new window

<div class="infobox" style="visibility: hidden;">
  <h3 class="title">Book Title</h3>
  <h4 class="author">Book Author</h4>
  <p>Published in: <strong class="published">year</strong></p>
  <p>Word count: <strong class="words">published</strong></p>
</div>
<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) {
      // Select the element by class, use .text to set the content
      d3.select(".infobox .title").text(d['title']);
      d3.select(".infobox .author").text(d['author']);
      d3.select(".infobox .published").text(d['published']);
      d3.select(".infobox .words").text(d['words']);
      // Show the infobox
      d3.select(".infobox").style('visibility', 'visible');
      // 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) {
      // Hide the infobox
      d3.select(".infobox").style('visibility', 'hidden');
      // return the mouseover'd element
      // to being smaller and black
      d3.select(this)
        .transition()
        .duration(100)
        .attr('r', 10)
        .attr('fill', '#000000');
    })
</script>

Open example in new window

Tada!

Want to hear when I release new things?
My infrequent and sporadic newsletter can help with that.