Dominate that D3 library!
A simple primer, Prepared by Zoe F
|
Setting up the html, CSS, and Javascript documents
- Create an HTML page called "myD3", set up with properly nested <html> tags, head and body areas. Nest a Title tag inside the head area.
- Create a Div inside the body area with the id "content_here". Inside the div, write the phrase "D3 is the best!"
- Create an external .css document called "D3_styles.css", and link to it from inside your html's head area
- Test your css link by using it to put some formatting on your "content_here" div. Let's make it bold, with a padding of 5px, and a border that is solid, green, and 1px thick.
- Create an external javascript file to hold our javascript called "D3_javascript.js" and link to it from inside your html's head area
- Test your javascript link by having it output "I made it in" to the console.log()
|
References:
<html>
<head>
<title>
<body>
<link>
<div>
<script>
console.log()
|
Add and test the D3 library
- We can download a library or link to it externally. We're going to link to the D3 library externally. In our head area, before we link to our "D3_javascript.js" file, let's include an external link to the D3 library.
- Let's test it by calling a function from the D3 library. In our javascript file, let's call the select () function on our "content_here" div. Not sure what that does? Check the reference!
d3.select('#content_here')
- Hmm, we're not seeing anything. That's because the select function by itself doesn't do anything! Let's chain it to a function that takes what's selected, and adds an html tag to it. In our case, let's add some <p> tags! Since we just want to add that tag after our existing content already inside the div, let's use the append() function. Change the line of code to read:
d3.select('#content_here').append('p')
- Hmm! We're still not seeing a lot! Use the Inspect ability of the console to prove to yourself that we now have extra P tags sitting inside our div by looking at the elements area, and accordioning out the "content_here" div.
- Let's chain one last function to our list here, let's add some content inside our <p> tags using the D3 text() function. Here's all three functions chained together. We are selecting the div by its ID, appending a 'p' tag, and then adding some text into it! Not sure of any of those functions? Check the reference!
d3.select('#content_here').append('p').text('Our First Paragraph using D3!');
- This is great, but our formatting is super lame. We could use the text() function to write out a bunch of html tags to format this text, but D3 wants to help us out. Let's chain the D3 function style() to the end of this, to dynamically add some formatting on the go!
d3.select("#content_here").append("p").text("Our First Paragraph using D3!").style({"font-size":"40px","font-family":"arial"});
- Wow, this is super long! Let's break this long chained D3 command into some more manageable bits for readability! First let's make a variable called "someText" to hold the new html and content we're creating.
var someText = d3.select('#content_here').append('p').text('Our First Paragraph using D3!');
- Then let's use that variable to add some formatting!
someText.style({'font-size':'40px','font-family':'arial'});
Now we've replaced our previous chained line of code with two much easier to read shorter lines.
|
<script>
D3 library
D3 reference
d3.select()
d3.append()
d3.text()d3.style()
|
Making some data
- We need some data for our visualization. We could read it in from an external csv file, just like we might read in an image, but for now we're going to create it directly as an array. Let's create a new variable called "data_values" to hold our array, and then assign it some numbers. To declare that a list of numbers is an array, we use brackets, and separate the numbers with commas.
var data_values = [5, 10, 30, 8, 45, 24, 16, 55, 60, 45, 32, 18, 11, 7];
- Now let's use D3 to bind the data to some html tags!
That means we're going to create some tags just like we did a moment ago, but now we're going to use the data in our array to affect how many tags, and what content they contain.
Let's start off by creating some p tags.
We're going to chain together some D3 functions again. Make sure to look up each function in the reference to make be certain you know exactly what it's doing.
selectAll() acts just like select(), except it, yes, selects all of the elements that match, not just the first one like select() does. We want the following to act on all the p tags we're about to create.
Next we're going to warn it that some data is coming with a
data() function. That will tell it where to find our data, the name of whatever array we give it as an argument (in our case, the array data_values.) It will then execute all of our code chained afterwards, once for each element. So each subsequent function will happen 14 times!
Next we're going to chain an enter () function. This is where the magic
happens - enter() reads in each number in the array!
Then let's chain an
append() function just like before, so that for each data item we add a p tag.
Lastly, let's chain a text() function to put the phrase "D3 rocks" inside each of those p tags. All together, all of our chained functions look like this:
var myTags = d3.selectAll("p").data(data_values).enter().append("p").text("D3 rocks");
- Let's add a bit of formatting. Use the same functions we used above in our first D3 test to make all our new "D3 rocks" text 20px, with font-family of arial.
- This certainly looks cool - we certainly have an element for each data point, but let's prove to ourselves that each of these really does represent a piece of data. Instead of displaying the phrase "D3 rocks" for each using our text() function, let's display the value itself! To do this, let's nest an entirely new function definition inside of our text() function, a function that will return the data value.
We nested functions a lot in jquery, but this is much simpler. Text() is being called multiple times, once for every single value in the array. All that our function needs to do is take the value with which it's being called as its argument, and pass it back! We could do all kinds of other fun things to that argument within this function - multiply it, perform some kind of if/else evaluations, but let's just keep it easy. Replace "D3 rocks" with our new simple function that outputs the value with which it's called
text(function(value){ return value; });
Note, if this looks too confusing, add in some returns and linebreaks to make it easier to read as a function just like we did with jquery. Maybe this would be easier:
text(
function(value){
return value;
}
); |
Arrays d3.selectAll()
data()
enter()
|
Creating an SVG elements in HTML
- SVG elements are the natural partner for D3 because they're an easy way to make shapes in a browser without much work. But you don't need D3 to work with SVG elements, or even javascript! They're just another type of html tags. Think of them kind of like tables, with careful nesting required. Let's prove it to ourselves. First we'll need somewhere for our SVG shapes to appear. In your html file, just after you close your <div>, Use the <SVG> tag to create a grid that's 500px by 500px. We'll use that grid to tell the browser where to draw our shapes.
<svg width="500" height="500">
- Now let's draw some shapes inside our SVG element. Remember, our grid is 500x500. Our origin is in the top left, just like in a book. Start with a diagonal line that goes from (10,10) to (140, 140). Watch your nesting!
<line x1="10" y1="10" x2="140" y2="140"></line>
- Let's add a circle at (100,100) and give it a radius of 50px. Then let's fill it with blue.
<circle cx="200" cy="200" r="50" fill="blue"></circle>
- Let's say we want to give both of these elements - the line and the circle - a red outline. We know from CSS when you want to style multiple things, it's always best to only write out your style once. That way you can change them both at the same time. Let's use the <g> tag to group these two elements together. Wrap both of them in the new tag, and let's give the whole group a stroke!
<g stroke="red" stroke-width="5"> What other attributes can you add to your elements and your group? |
<SVG>
<line>
<circle>
<g>
|
Creating SVG elements with D3
- Alright, let's use our D3 to dynamically create us some SVG elements, and make a simple bar chart. Let's comment out all the SVG tags in our html, and make them dynamically in our javascript page instead. First we have to make the grid. We're going to select() it directly into our "content_here" div, adding it to the content already there. That means we'll append() an SVG tag just like we earlier appended a p tag.
Then we're going to use the attr() function to add the attribute to give it the width and height attribute we would otherwise put in the html. This time let's make the width, (the x measure), to be 450px and the height (the y measure) to be 400px
var mySVG = d3.select('#content_here').append('svg').attr({'width':450,'height':400});
If it's esier, use linebreaks to put the elements on different lines like we did before with our nested function.
- Let's use D3 to draw us a rectangle on our grid, and chain it with the attr() function to add some width and height. Just like <circle>, an SVG rectangle is drawn using the html tag <rect>. Let's use that, and let's assign it to a new variable "bars", so that we can manipulate it easily without all that chaining going on.
var bars = mySVG.append("rect").attr({"x":"30px", "y":"30px", "width":"30px", "height":"100px"});
Should we make it blue? I think we should! Chain or use the variable, it's up to you! Here's how it looks if we use the variable.
bars.attr("fill", "blue");
|
attr() |
Using Data to dynamically create SVG elements
- Let's do it! We'll use our javascript array, and this time instead of binding it to <p> tags, let's bind it to some rectangles to make our bar chart. Lets comment out the single rectangle we just made and its formatting, and replace it with some dynamically created ones.
We're taking the rectangle code we just made. Then we're chaining it to the binding code we wrote earlier, the one that creates html tags from each data point in an array. Our html tag in this case is <rect>
var bars = mySVG.selectAll("rect").data(data_values).enter().append("rect").attr("width","25px").attr("height", function(d){ return d; });
Now each rectangle is 25px wide, but the height of each rectangle is based on the earlier function we wrote that just returns whatever value the process is using at that moment to create the shape.
- But wait! It still looks like we only have one rectangle! That's because all 14 of the shapes are being created on top of each other - they're all there, but they're covering each other up! Let's move them apart a little bit so that we can make out the individual shapes, using the variable we've assigned it to. We will need to change the x attribute for each rectangle - the horizontal point where we start drawing the rectangle, so that we have some space. But how do we know how far to move them over?
We know that D3 passes the current data value to us because we're using it to create the height, the y value, for our rectangles. But D3 also passes a second value, called the index. That's what point in the array that value inhabits. Or to put it another way, it's the number of loops through the array that its taken to get to drawing that rectangle.
In our array, the first element, 5, is at index value 0. The second value, 10, is at index value 2, all the way up to our 14th value, 7, which is at index value 13 (because remember we're starting at 0, not 1.) Let's use the index i to calculate how far over each rectangle needs to be from where it is now!
We're going to update all of the rectangle's location attributes. Their y will stay where it is, but the x we're going to write a little inline function to return, just like we did before. But this time, instead of just returning the value, we're going to return the index multiplied by 30px. 30 seems like a nice number, because our bars are 25 px wide - so that extra 5 px should give us some space between each. In other-words, we are setting the new x to the results of a custom function that takes the data point and the index, and returns the index times 30 for each data point.
bars.attr("x", function(d,i){ return i*30; }); Feel free to add any line breaks you want to make it easier to read for you.
- Now this looks like a chart! But aren't they a little, um...stubby? Let's make our bars longer my multiplying each height value by 5! We can either chain another attribute modification to the end of our spacing-out code above using a dot, or just use the variable "bars" to add our scaling request on in a second line of code. Up to you! Here's what the previous line would look like chained:
bars.attr("x", function(d,i){ return i*30; }).attr("height", function(d){ return d*5; });
- Looking good! But wait, why are our bars upside down?
Remember, our point of origin, (0,0), is in our top left. That's how html reads - top left to bottom right. But we're used to seeing graphs with the point of origin in the bottom left, with numbers getting bigger towards the top! Let's flip this sucker so that the rectangles start at the bottom, not the top.
We know that our SVG grid is 400 px tall. So we can get a new y value for the point of origin for each rectangle by subtracting our new elongated height from the height of the grid. Again, you can chain it, but this is starting to become a really long piece of code, so let's use the variable to put it on a new line.
bars.attr("y", function(d){ return 400-d*5; });
That looks better! Note, if you wanted to be really spiffy, you could have saved the height of the grid in a variable so that you could change all of these size functions at once instead of looking for every place you use the size 400px :)
- Let's set one more attribute - let's give it a color. Chain or new line, whatever you prefer.
bars.attr("fill", "steelblue");
Or you can use a hex code for that color value instead of the quick color name "steelblue"
- See if you can change one more attribute of this graph using the attr(); function. Perhaps give it a stroke, change its opacity, whatever you want!
|
SVG rect
Color names
|
Adding new chart elements
- Alright! This is all well and good, but it's a bit simple. Let's dynamically let D3 make this look a little snazzier. Let's highlight the max and min bars here to make them easier to find. We're going to use
D3's max() and min() functions. Look up in the reference what they do! Then, let's create some variables and let D3 dynamically assign them to the values we need.
var myMax = d3.max(data_values);
var myMin = d3.min(data_values);
Want to make sure that these are working? How about reading their results to the console.log()?
- Alright! Now let's change the color of the rectangles based on whether or not they are the min, max, or neither. We're going to use D3's filter() function. Read up about it in the reference to see how it works!
First, let's set the color of the max - the longest rectangle, to green. Reading from right to left starting with our custom function, we're going to make a function call using d, which remember is the value of the data point we're on right now (in the same way that i is the iterator we're on right now).
If "myMax" is equal to d, that is, if we're on the biggest data point, they we're going to return it to filter. Then we're going to use the attr() function we've used many times before to change its fill attribute to green
bars.filter(function(d){ return d==myMax; }).attr("fill","green");
- Now let's do the same for min, coloring its fill attribute red!
bars.filter(function(d){ return d==min; }).attr("fill","red");
Woo hoo!
|
d3.max()
d3.min()
filter() |
Check in 3: Not sure if your code is right? Catch up by copy and pasting the source code. |
Extra Credit
- Add the data label for each bar in our chart!
you can add them below or above, your choice!
- Try reading in the data into your array from an external source such as a csv file!
- Make each of these bars interactive so that the user can click on them and make something happen. Perhaps the color changes? Or a div pops up that says the information inside of it?
|
|