ATHENA BRAUN: Hey, everyone.
Thanks so much for coming out today.
I'm going to walk you guys through a quick exercise with D3.
D3 is a JavaScript library, so it isn't a language itself.
It's actually using native JavaScript.
It was all built by one human being.
He's a very cool guy who was really good at this.
And his whole idea was that he wanted to put together
a library that would, in relatively few lines of code,
give you the ability to make really interesting, interactive,
and adaptable data visualizations.
So a lot of what we're going to be dealing with today
is what it's like to load in data, what it's
like to do that synchronicity versus asynchronously.
And then we're going to build a pretty simple bar graph here where we're
going to have some clicking capability.
And all of this is being generated dynamically.
So I'm going to step you guys through the code.
So you have an idea of what this looks like.
Feel free to stop me at any point.
But a few things that I do want to mention right off
the bat is that in order to get any of this to work
you are going to need a web server up and running.
So just keep that in mind.
The whole reason that any of this is running
is because I have a terminal window here where
I've already like typed in HTTP server.
And this is all running.
Good to go before I start looking at code?
Awesome.
Great.
So first thing first, is we're going to talk about index.html.
You don't have to do this in an index.html.
Since this is a pretty simple website with only one page,
I decided to put it in index.
But you could, of course, have data visualizations
on any of your website pages.
So we're going to have some typical boilerplate code here where
we see things like the metal char set, the meta names, the title.
And then immediately, I start including things like Bootstrap CSS, which
I use a little bit of.
Here, I've made an API call to Google to get some fancy fonts.
This is absolutely not D3 related.
And then I've included some of my own style sheets.
These are normal things you can do.
You don't have to do.
Have absolutely nothing to do with D3 itself.
It's more the stuff that we do in the body
and that we include at the bottom of the body that is actually
D3 specific where, even here, I've just sort of configured the page so
that I have a div for my header.
And then I have a div where most of my visualizations
and most of the interesting components of this page are going to exist.
So let's talk about that a little bit.
I'm using the Bootstrap container class here,
which basically lets me resize as I would like to.
So if you ever like click and drag the web page,
your data visualizations will resize accordingly.
So you don't end up with things that are like awkwardly cut off
or that start to look a little bit weird.
This is more of an aesthetic choice, as opposed to a functional choice,
but it ends up being a nice way to do this.
And when you're using anything within the container,
the next class that you have to have is row.
If you're ever interested in looking any of this up,
you can Google bootstrap grid layout, and you'll immediately
get documentation for this.
And what's important to know about the grid layout
is that the web page is split into 12 columns.
So you'll see here that I actually have two divs, each taking up six columns.
This is really just so that I could get a nice effect of splitting
the page in half between the data visualization itself
and any of the information that I was going to show.
You could absolutely configure this differently.
You could have your data visualization take up the entire width of the screen.
You could have it take up considerably less of the screen.
But for my purposes, this is what I wanted to do.
And what I really want to point out is that I
have made very judicious use of IDs here,
where within D3 we're going to have the capability to,
similar to native JavaScript, actually select components out of the DOM,
out of the document object model.
And the easiest way to do that is in fact to do it by ID.
So get very accustomed to using IDs.
And try to keep in mind that the IDs that you use
should be descriptive enough that, you know, you either don't forget them,
or when you're actually writing your JavaScript it's
easy enough to know what you're actually referring to within the HTML itself.
So you'll notice here I actually have two columns that are happening.
One where I've said, OK, I'm going to actually display the chart.
And one where I'm going to display the information.
And within the information, I've said, OK, I'm going to have a header.
And then I have this other div that I've said is going to be albums.
The other really important thing here that is now actually D3 specific
is that I need to be sure to include the D3 JavaScript library.
If I don't include this, we literally aren't working with D3 at this point.
In order to find this file, you can literally just Google D3 JavaScript.
And you will find a link in order to include it in a script tag.
I always highly recommend, whenever you can,
following that link that you would otherwise just include
and actually copying and pasting all of that source code into a file.
Because that way you never have to deal with latency
if your connection is slow, and now your JavaScript libraries aren't running.
So in fact, this file exists natively within my directory
here so that I don't have to deal with any of these issues of poor connection,
and now my visualizations aren't running.
And you know, of course, I have my Bootstrap JS.
I'm going to use a little bit of jQuery, which I think you all
saw in one of the most recent P sets.
I'll point it out, but it's really a very small use.
And then, finally, I'm going to use Q, which
is just another JavaScript library.
And the whole purpose of this is to make sure that my data gets loaded properly.
So having looked now at the JavaScript itself,
I've also included one more JavaScript file,
which is the file that I've actually written
and where any of the logic that I have done for my project
will actually exist.
For your projects, they might be a little more complicated,
where, in fact, you might have several JavaScript files that
are all interacting with each other.
Always be sure that you're including your JavaScript files.
It's one of my favorite debugging stories where I'm like, well,
I've written all of this code.
Nothing is working.
Nothing is showing up.
Why isn't it showing up?
And it's because I've forgotten to include the file.
So it's always good to keep this mental checklist of like,
have I included the files that I need to include.
So if we hop over to main.js, the first thing I really want to talk about
is this idea of asynchronous JavaScript loading where, in fact, I'm
going to be loading data sets that, again, exist natively on my computer.
And just to give you guys a quick idea of what those look like--
oh, goodness, well here we can actually open them up over here.
So you'll see I have two data sets here.
I'm just going to go ahead and open the artists data set.
So all of this data is coming out of the SQL lecture.
So some of it should look vaguely familiar.
Because it came out of the SQL lecture, I actually ended up
having to treat my data a little bit differently
to make things line up properly.
You'll remember that in the SQL lecture David
was talking about ways to make your database more efficient.
And as a result, rather than store an artist's name,
he would instead just store an integer representing an artist ID, which
is a perfectly valid way to do this.
And it was actually a very good opportunity for me
to show you guys how to use multiple data
sets that actually perhaps interact with each other.
But this is just one of the two data sets.
You'll notice that I've saved it in a CSV format.
This was out of convenience more than anything.
You could absolutely save it as a tab separated
values, which is similar to a CSV, but uses tabs as delimiters.
Just be consistent with what you're using.
And keep in mind that whatever type you use to save your data
is going to correspond directly to the methods
that you're going to be using to actually load that data.
So if we look at this step here, this is probably
one of the most important steps that we're going to have.
Where, in essence, I've said, OK, for each one of these data sets,
I want you to call D3.CSV on it.
What D3.CSV actually does is it calls the method that
literally reads in your CSV and saves it as an array of JavaScript objects.
Where each one of the objects-- and I'll show this to you in the Developer Tools
in Chrome in a second--
but each one of the objects is going to have a key that
is artist ID and a key that is artist.
And so each one of these lines here is going
to turn into its own JavaScript object.
So that's kind of the direct mapping that's happening there.
So I've said, OK, for each data set, please load it in.
And it's kind of this defer that is most interesting where we've
said once you load this one in, wait to do anything else,
but please also load in the next one.
And finally, this await line is the most important
where we've said, OK, once both of these defer statements are done,
meaning once you've loaded in both of those data sets, now
please call this other function.
And what ends up happening here is this was just a function that I wrote.
You could have done this as an anonymous function,
but it would start to get pretty ugly pretty quickly.
And so I went ahead and just gave it a name for simplicity's sake.
But what Q ends up doing, and Q is coming out of this Q JavaScript
library, it says, OK, whatever function you
pass to await needs to take at least one argument.
But it's going to be one plus however many defers you have.
So you'll notice here that artist.CSV is going
to immediately give us artist data.
I could have called this whatever I wanted.
I could have called it Foo.
I could have called it Llama.
It's just most convenient here to call it
Artist Data so that it is clear what I've actually loaded in.
And here, I've called this all Alb Data because I
was lazy and didn't want to type album.
But I could have easily done that.
The order here matters.
Right?
The second defer is going to correspond to this third argument here.
This first error is an error that potentially might be thrown
by either one of these two functions.
Right?
Either one of these two calls to D3.CSV.
If, for example, something was wrong in opening your CSV file,
or if it wasn't able to get through the whole file,
it will actually return an error.
And so the first thing you want to do is make sure
that you have actually checked the error.
If that error exists, we're going to go ahead and return.
As opposed to just console log, we're returning
here so that the entire program does not continue running.
But we're going to console log the error so that we as developers can try
to troubleshoot exactly what happened.
But assuming that that didn't happen, let's
step through the rest of the code.
So the first thing I've said is, OK, immediately I'm
going to have a problem because my data sets don't make a lot of sense.
And in fact, what I'm going to do here is
I'm going to go ahead and comment in this console log, which
is going to show us everything that's in Album Data.
And I'm going to go ahead and put in another console log, which
is going to show us Artist Data.
This is a cute trick.
This is the Print F debugging of JavaScript.
I highly recommend using it.
Nothing is more frustrating than not knowing
what your data looks like because it's then hard to make logic
to manipulate your data.
So the first thing I always like to do is actually
figure out what does my data look like.
So I always console log my data as soon as I've loaded it in.
So, in fact, if I save this and make sure that my server is still running,
I go back over here.
Refresh this page.
And then I'm going to go ahead and hope that the internet is working.
And in fact, you see right away that I have an error.
So this is why the console is super helpful.
First things first is just understanding how
to read what the console errors are telling you.
You'll see here the line where the error occurred.
So I know I should be looking at main JS line 22, where it has said to me,
OK, you've referred to something called Album Data.
And it's not defined.
So in fact, if I go back to line 22, I can immediately say, oh yeah,
I didn't call it Album Data.
I called it Alb Data.
Right?
I can go back, save it, refresh the page again.
And you'll notice that if you have no errors, things will load.
If you have errors, things will not load.
So this is always a good thing to do.
So these two things here are actually coming
immediately out of those console log statements that I did.
And in fact, let me make this a little bit bigger for us.
So Album Data, we see that it has 347 items.
Remember, I mentioned that it was an array of JavaScript objects.
One way you can immediately see that is that this first bracket here
is in fact a square bracket.
Right?
This tells me that it's an array, similar to an array in Python
or a list in Python or an array in C.
If you click the little dropdown arrow, it
does this nice thing where it segments it for you because otherwise it gets
kind of impossible to read and to see.
You can drop down again, and you can immediately see, OK,
this is what my data looks like.
Right?
Each one of these objects has this Album ID key and has this title key,
has this Artist ID key.
All right?
This will be good for me to know as I'm writing loops
to iterate through this data.
By the same token, I can look at the Artist Data.
And I can say, OK, Artist Data all has an ID.
And it has an artist that it corresponds to.
I happen to know, from the data that I loaded in, that an artist's ID of one
here corresponds to a same artist ID of one over here.
Right?
So For Those About To Rock, We Salute You,
I know that this is supposed to be an AC/DC album.
Right?
So keeping that in mind, I said to myself, OK,
what would be an interesting thing to visualize.
Once you've loaded in your data, and once you've
decided what your data should be, the next question
you should really be asking is, what am I
trying to visualize because that's going to dictate how
you're going to run through your code.
And so what I decided to visualize was actually
just the number of albums that were produced by a particular artist.
So you'll notice here on my y-axis, I've got just a number of albums.
And on my x-axis, I actually have the name of the artist
that this corresponds to.
I've added a little bit of extra functionality
that if you click on any of the bars, you can actually
see a list of these albums.
And we'll talk about that in a second.
But keeping that in mind, I said to myself,
OK, I'm going to need some way to count the number of times an artist ID occurs
given the number of albums.
Right?
So the first thing I said to myself is, OK, I'm
going to need to make some kind of mapping between an artist ID
and the actual name of the artist.
So what I did was I did the following.
I said, OK, I'm going to make an artist map, which is itself
going to be just a JavaScript object.
Or you can think of this more like a dictionary, which
is kind of how I was envisioning it as I wrote this code.
And the idea here is that we're going to iterate through all of the Artist Data.
And for each one of the JavaScript objects in the Artist Data,
right, so for each object that has an artist ID and it has an artist's name,
we're going to say, OK, here's that individual object.
And we're going to say, OK, within artist
map at the index that corresponds to that ID,
please save the name of that artist.
And this was just to make a quick easy mapping so that I
could refer to these objects later.
The more interesting stuff actually happens in this D3.nest.
So what D3.nest does is that it says to you,
OK, I'm going to iterate through all of this data for you.
And what I'm going to do is I'm going to build a brand new object where
the keys are going to correspond to something that already came out
of your data.
And the values, or the leaves as they like to call it,
are going to correspond to something else out of your data.
So what I've said here is, OK, I want a count by artist ID.
And the reason I wanted a count by artist
ID was because I had already made this mapping by artist ID
so that I could easily index into it.
So I said, OK, for each one of the pieces of data within my album data,
so this entry is actually tells you which
data you're going to be iterating through, I've said,
OK, the key is going to correspond to that particular object's artist ID.
I've then said, OK, I want you to roll up all of the leaves.
I think this is one of the most confusing lines in this code.
So let me just unpack it a little bit for you.
And I've left a comment just to be a little more clear.
But what we're basically saying here is what
rollup does is that it searches through every single one
of the items in your greater data set.
And it finds the ones that match the particular key.
So, in fact, what leaves is, if I were a console.log it,
leaves is actually an array of objects that match that key.
You can see then why that would be helpful for me
because what I'm really trying to do is count the number of objects that
correspond to a particular artist ID.
Therefore, by getting the length of that array, which is literally
the number of objects that have that artist ID,
this is how I've gone through and actually counted
how many things exist for this artist.
The real question here is, Athena, was this the only way to do it?
And the answer to that is no.
In fact, with D3, there's a lot of ways that you
can do a lot of the same operations.
But in fact, this is probably the most concise way to do it,
albeit maybe a little bit unclear if you're just
using it for the first time.
That being said, D3 actually has very good documentation.
And so if, on your computer now, you were to Google D3.nest example,
if you see anything from bl.ock, something like that, I look at it
and I read blocks.
So if you see anything from like blocks.org,
that's great examples of visualizations that people
have done that use different functionality out of D3.
I highly recommend taking a look at it.
So now I have this count by artist ID.
And I've said to myself, great, I have all of this information.
And I've said, all right, I probably want to make a bar graph.
And because I want to make a bar graph, what's important to me
is to make sure that my data is arranged in such a way
that it's going to be easy to display it.
So in fact, the next thing that I do is an in place sort.
In place here meaning that I actually call this function on this array
that I've been given.
And it will sort it in place for me.
The way sort works is as follows.
Sort expects an anonymous function.
And in fact, what source hands that anonymous function
is two side by side objects.
So we have object A and object B. It could be
that object A is greater than object B.
But in order to sort things in ascending order, what you really want
is the difference between object A and object B.
But keep in mind that object A really is an object.
And as a result, it has multiple keys.
The thing that I was interested in here was value.
And the reason I knew it was value, so for example,
like let's say I didn't know what I was going to get out of this count
by artist ID.
I could go ahead and say, OK, I'm going to a console.log it.
Right?
And this is just a cute trick I like to use
for console.log where you can actually label the thing that you're
about to log so that you can find it easily in the console.
So I can go ahead and save.
Go ahead and refresh.
And I can say, OK, like what did I get back.
Computer, come on you can do it.
All right.
So here it is, this artist ID count.
I can see immediately, again, that this first character here was a bracket.
So I know it's going to be an array.
And I can see that what I end up with is a bunch of objects
where they have the key key.
And the other key value where value is actually
the number of objects that were counted for this particular artist ID.
This was the only way for me to know that when I was sorting
I should be looking at the value field and not some other field.
I literally had to pull up the data and look at it in order to figure this out.
There is nothing wrong with doing that.
And in fact, I encourage you to do that.
So once I went ahead and sorted, I said, OK,
just for like display purposes here, I'm actually
going to cut down some of the data we had
because we have like 204 items of data.
And that just like gets a little bit ugly to display.
There are ways to get around this, but just
like to make this attractable example, I said, OK, I'm
going to just get rid of everything from index 0 to index 180.
This was perfectly arbitrary.
You absolutely don't have to do this, but it just
simplified a little bit of this process.
And then, finally, what I said was, OK, now that I have loaded all of my data,
and now that I have cleaned my data in a way that I am happy with,
I can go ahead and call a new function that is actually
going to create the visualization.
It's really important that you never try to create the visualization
before you've loaded all of your data.
Because, in fact, the way JavaScript works
is that it might try to create your visualization before your data has
finished loading.
As a result, if your data doesn't yet exist,
your visualization is going to fail.
And if it fails, you might have to refresh the page several hundred times
until you get lucky that maybe your data loads first and loads fast enough.
So in fact, you'll notice here that I never
tried to say, OK, create the visualization until I've made sure
that everything has been loaded in.
And this is super important.
Easier ways to do this include actually just calling D3.CSV.
And within that, pass it an anonymous function.
And then do all of your data creation within D3.CSV.
We can look at an example of that later if that's confusing.
But this is just one way that you can do it.
So now, I'm actually going to talk about the visualization itself.
But before I move on, any questions?
Yeah?
AUDIENCE: [INAUDIBLE]
ATHENA BRAUN: Yeah, absolutely.
So the question here was, just to give another quick example
of how D3.nest works.
So D3.nest is going to say I'm going to build you a new array of objects.
And the array of objects that I'm going to give you is going to have a key
and is going to have a value.
It wants to know, as it's searching through your data, what
key do I want to be matching against.
And so what it does is it says, OK, as I'm iterating through your data
I'm going to look at a particular datum.
And I'm going to get its artist ID.
And that is what I'm going to use as the key here.
Right?
Rollup is kind of the confusing part.
Where rollup kind of does this like searching count for you, where it says,
OK, I am holding this key.
Let's say the key is three.
And it's going to look through every single one of the objects
within this album data.
Right?
And it's going to say, does this object match this key?
If yes, I'm going to stick it into an array.
Does this object match?
If no, I'm going to discard it.
Does this one match?
Yes.
No.
Yes.
No.
And so at the end of the day, what rollup actually passes to this function
is an array of all of the objects that matched that key.
Another way to think of this is that each one of these album data
actually had an artist ID.
Right?
So if this artist ID matched the current key that I was holding,
this would be considered a match.
Once I have that array, I can do whatever I want with that array.
But because what I'm interested in is actually the count,
I know that by taking the length of the array, that
is really the same as saying how many albums corresponded to one artist.
And this final thing here, the order, I always think is a little bit weird.
But entries is actually where you specify the data set
that you want to iterate through.
Did that answer your question?
Great.
Anything else before I move on?
OK.
Great.
All right.
Let's talk about the visualization.
So there are fancier ways to do visualizations and less fancy ways.
I always like to start by initializing a margin object where you'll
notice that there's actually like a nice little bit of space
here along the side.
And there's some space over here.
And like admittedly, this name was the world's longest artist's name.
And so it is taking up more space than it should be.
But there's actually a little bit of space
wherein you can actually see that my visualization exists
within this nice confined rectangle.
The way I've configured that is through this margin object, where I can now
like calculate the width and the size of my actual visualization itself
by just changing values in one place, as opposed
to changing values in a bunch of different places.
So this was really more of a convenience thing more than anything,
but I highly recommend using it.
So this is a little bit of jQuery that I said I would point out to you guys.
Where, in essence, I've said, OK, I don't
want to hard code the width and the height of my visualization.
In fact, I want my visualization to conform
to whatever the width and the height of the element that it exists in is.
So that's a fancy way of saying if I've already
said that I'm going to put my visualization in a box that
is 500 by 600 pixels, in essence, what I want to happen here is this width
to equal 500 pixels, and this height to equals 600 pixels or the other way
around.
I don't quite remember which way I set it.
And in order to do that, I've said, OK, jQuery
please get me the HTML element that matches this ID where notice that I've
actually passed parent element in here.
So actually, it's worth noting that this create
vis could be used multiple times depending on the data
that I'm going to pass.
So that's worth noting.
But notice that I've said, OK, this visualization
is going to exist in the chart display column, which you'll remember
was the ID of this particular div here.
All right?
So this is why IDs were going to be super important.
All right.
So please get me the width of that, and then
go ahead and subtract this margin that I've gotten you,
both for the left and the right.
Where we said, OK, I know that this full width is going to be this whole box.
In fact, I want to constrain things a little bit in case
my visualization has like straggling items
because I don't want those to end up either off of the screen
or out of the parent element.
So it's absolutely worth subtracting margins here.
And you do the same thing for the height.
The other thing worth noting here is SVGs.
If you ever start reading about D3, you're
going to see this all over the place.
SVG stands for scalable vector graphics, which is really
just a fancy way of saying that a bunch of people got together and said,
yeah, graphics on the internet are really
hard because the minute you try to rescale them,
they lose all of their resolution.
Let's build something that isn't going to lose its resolution no matter how
big or how small you've made it.
And that is exactly what an SVG is.
D3 expects SVGs.
It will only ever append objects to SVGs.
And so as a result, the first thing you will always
find yourself doing whenever you deal with D3
is you will find yourself making what you can think of as the canvas SVG.
This is the SVG onto which all of your particular objects
that actually build up your visualization are going to end up.
So if we look at this, this D3.select is really
analogous to our document.QuerySelector where
we've said, OK, with D3, go ahead and find me the parent element.
And the first thing that I want you to do is actually append to it an SVG.
And we'll look at what the HTML looks like for this in a second.
Where now, D3 really just likes chaining syntax.
Where we said, OK, get me the parent element and append an SVG to it.
From this point, the like pointer that I'm holding
corresponds not to the parent element, but actually to the SVG itself.
And I've said, OK, for this SVG that I'm holding,
please give it the attribute of width.
And I've given a calculation for the width.
Please give it the attribute of height.
And I've given a calculation for height.
And then append to it this thing called a G group.
A G group is really just a nicer way in D3
to start building layers on top of layers
so that if you want to, for example, apply styling to a bunch of things,
if they're all on the same layer, you can apply the styling very easily.
You could absolutely have done this without using a G group.
But this was more as a kind of just like the rule of thumb
that people like to use.
And finally, what I've said is, OK, once you've
appended this SVG, go ahead and transform it,
which is really the same as saying, OK, I'm
going to actually shift it somewhere on the page.
And the shift that I want to do is a horizontal and a vertical translate
where I've said, OK, translate it to the right
this many pixels so that I get my margin.
And translate it down this many pixels.
OK?
All right.
Question on SVG?
You don't have to understand what it is as long as you
understand that it's a canvas.
At the end of the day, that's all this is.
Where if you think about it, this white block here,
you can't actually see, but is basically my SVG.
And everything that I've appended to it are just other elements
that I have put onto that canvas.
If that canvas wasn't there, I couldn't have put any of these elements on here.
The next thing you should notice is that really a bar graph
is just a bunch of rectangles.
Right?
And that is kind of exactly how D3 is going to treat visualizations.
Where D3 is going to say, well, like a line graph is really
just a couple of circles with a line running through it,
or a bar graph is really just a bunch of rectangles
of varying heights and widths.
And so when we're going to be making this visualization, we're going to say,
OK, I know that I need a bunch of rectangles,
and I know that I need them all to fit.
Right?
And this is where this idea of scaling starts to come into play.
But scaling is really hard, especially if you don't know off
the bat how many pieces of data you have,
what the min and max values of those data are, and like
remember that we didn't hard code the width and the height.
So in fact, your total scale could be changing depending
on what your parent element is.
So D3 has been really nice.
And it has actually come up with these scale generators
that let you give it a few parameters.
And it will actually generate the scale for you.
It takes out all of the hard thinking.
So there's several different kinds of scales that you can use.
Linear scales are, I think, the ones that come most easily to people
because it's literally take this value in this domain
and squish it to fit into the domain of my width or my height
or the range of my height and my width.
Ordinal scales are a little bit more tricky in D3,
but it's worth knowing how to use them if you ever
want to visualize something that isn't numeric.
Where ordinal scales will do the following.
Ordinal scales will figure out for you how many items you need to display.
It will figure out how wide each one of those display items need to be,
so in this case, our bar width.
And it will figure out the space that needs to exist between each of the bars
to make all of this work.
So you'll notice here that I've said, OK, the range of the values
that I need you to output are going to be from zero to my width.
And that makes sense.
Right?
Where I've said this is going to be the scale for my x-axis.
And so I know that my x-axis is going to run from the zero width pixel
to the pixel representing my width.
On the other hand, I've said, OK, between each of the blocks
go ahead and give me half a pixel worth of padding just
to make it look a little bit prettier.
I could make this bigger.
I could make this smaller.
This is really just up to taste.
And finally, it's the domain that kind of looks a little bit ugly.
Ranges are the values that the scale is going to output given a value.
And the domain are all of the values that your scale should expect to get.
When it comes to scales, the domain is always going to come from your data
itself.
So what we end up doing is the following.
I've said, OK, D3, please do me a favor and apply a function
to every single one of the items in my count data,
where the function that I want you to apply to an individual item
is going to do the following.
It's going to go ahead and figure out what the name of that artist
is using my artist map.
And keep in mind what I've done here is I've
said, OK, datum itself is going to be of the form key value.
And I know that the key is going to correspond to some artist ID.
Knowing that, I can say, OK, if I give my artist
map an artist ID, what it will return to me is actually the name of the artist.
So I've gone ahead and said, OK, get me all of the names of the artists that
correspond to all of the data.
Right?
That's exactly what this has done.
And it has actually built me a JavaScript object,
but really what I'm interested in here is all of the possible artist names
that I could have gotten.
And so by asking it, OK, you're a JavaScript object,
please tell me what all of your keys are.
I have, in essence said to domain, OK, here
is an array of every single possible artist name that you could ever get.
How it does the math to figure out which artist name
goes where, how wide the bars have to be is all abstracted away.
It's not important for you to know how it does it, simply that it does it.
Quite frankly, scales and axes are some of the hardest parts to debug in D3,
so don't feel bad if those aren't working quite the way
you want them to right away.
There's a lot of really great examples online.
The linear scale is a little bit easier, where we've said, OK, all of the items
that you're going to output are going to be
in the range from the height minus the margin and all the way
to the margin of the top.
What's really important to note in terms of graphics
is that, in fact, while your width actually moves in the direction
that you expect it to, so like zero starting on the left hand
side and the greater numbers moving to the right,
height is a little bit different.
In fact, the y-axis is inverted.
So zero for the y-axis starts in the top left hand corner and moves down.
So the greater values are actually further down within the graphic.
Why did they do this?
I'm not totally sure, but it can be a pain.
As a result, you'll often find yourself reversing heights
so that the values that you're getting out
are actually corresponding not to the height itself,
but the difference between the height that you've gotten for a bar
and the total height of the visualization, which
you can kind of see would correspond to the height of a bar
that we would be thinking of.
That's the only thing I want to note there.
The other thing I'll note here is that I needed the y scale to know
what its maximum value is going to be.
And so as a result, D3 gives me a max function,
which literally iterates through every single item in the count data.
And it returns like an array basically of all of the values.
And it picks the highest value.
Could I have done this another way?
Absolutely.
But since D3 gives me these tools, you should absolutely use them.
OK.
The scales themselves actually take care of the math,
but does not take care of any of the visuals that you see.
And so as a result, we actually have to set up the visuals ourselves.
Again, D3 gives you very easy methods for setting up axes,
where if I want an axis on the left, I can literally just call axis left.
If I want an axis on the bottom, I can go ahead and call axis on the bottom.
It will then ask you, OK, what is the scaling I should use?
And the nice thing is you can actually just pass it
the new scale that you have just made.
Right?
So this is quick and easy.
Once you've figured out the math for the scales,
the rest kind of comes naturally.
So we've said, OK, here's an object that is
going to correspond to my axis bottom, but it's not yet on the SVG.
And this is where we start to see this first instance of this method
append, where append is going to say, OK, tell me
exactly what I'm going to append.
And when I append on that thing, tell me the attributes that it has to have.
Again, by convention, we tend to append axes to G groups.
The reason we do this is that if we ever want to add color to our axes,
make things smaller on the axis, rotate the axis,
it's much easier to do that within a G group
than it is to actually try to find the pointer to the axis itself
once it's already on the canvas.
And there's this important method here specifically
for axes named call, where in essence you've said,
OK, I want you to append it to a G group.
I want you to give it these classes so that I can style it later.
And now I actually want you to call this method and make my axis.
This takes care of everything for me.
Once you've used this call operator, your axes just magically appear.
OK.
Same thing goes for the x-axis, where I've
done a little bit of fancier things.
One thing I do want to point out is that, in fact, everything
wanted to start like a little funky looking
where all of the names of the artists were actually horizontal.
And because there were so many artists, it starts to look kind of ugly.
Right?
So D3 is also a lot about like playing around and figuring out
how you can rotate things and shift things to make them look pretty.
So, for example, if I wanted all of those artist names
to be rotated 90 degrees from the horizontal
so that they would look perpendicular to the axis,
I can go ahead and set this to 90.
And so we can see that that looks a little bit like that.
I thought that this looked super ugly and was like hard to read.
And I was hoping that this would be short enough
to fit if I rotated it 75 degrees.
Surprise, surprise, it was not.
But aesthetically, I thought that 75 looked a little bit nicer.
This is the only reason I've picked 75.
This is purely an aesthetic choice.
OK.
So we've talked about the axis.
We've talked about the scale.
Once you've configured those, again, the rest is relatively easy.
Before I move on to the bars themselves, questions about axes or scales?
Because I think those are the harder things.
But if you have no questions, that's also fine.
Awesome.
OK.
Bars it is.
So remember that I said the D3 kind of likes
to break down what your visualization actually is.
And so what these are just a bunch of rectangles.
And so the thing that I'm actually going to append to my canvas
are really just rectangles.
When we're appending, let's call them like visualization items,
there's this cycle that D3 likes to use, where it's the like enter, update, exit
cycle.
Where we've said, OK, here's the data you're going to use.
Please enter it.
This is sort of this like weird semantic thing that D3 has adopted,
but it's very formulaic.
And you don't really have to understand what it's doing
as long as you remember the formula.
And the formula is always here's the data I'm going to give you.
Now enter into this state where we are going to manipulate this data.
So I've said, OK, I have this data.
I'm holding all of this data.
And what I'm actually going to do is I'm going to append the rectangle.
So you'll notice here that I actually have rectangle in two places.
Select All is actually looking for pointers to rectangles,
even if they don't exist.
In fact, even if they don't exist, you still
need to use Select All because what Select All will do
is it will actually create pointers for things that don't exist,
if your data calls for that.
And because I have no rectangles, my data
is obviously going to call for new rectangles to be made.
So I'm going to say, OK, we're going to append these rectangles.
And we're going to give them some attributes, like a class, a fill.
And then things start to get a little bit funny
where a lot of the things that we have to think about are
how wide do my rectangles have to be?
How tall do my rectangles have to be?
And where do I start drawing my rectangles?
Again, D3, and when it comes to graphics everything seems to do this,
actually starts drawing from the top left hand corner.
And so you have to say to yourself, OK, if I'm
drawing from the top left hand corner, how far right do I have to draw?
This is pretty easy because this axis moves in the direction we expect it to.
But how far down I have to draw, remember that all of these values
are actually inverted.
So when I talk about width, in fact, I can just
use this nice little method that comes right out
of my scale, where my scale has already taken into account
how many bars I'm going to need and what the spacing between the bars
is going to be.
And what the total width it has to work with is.
And so as a result, it can very easily give me
a calculation of how wide one particular bar has to be.
And so I didn't even have to think about this.
I could literally just say, hey, just tell me what
the bandwidth is for a particular bar.
Great.
Height is the one that's a little bit funny, again,
because we have to invert.
So I said, OK, for every one of the pieces
of data that you're going to get from this data that I've given you,
please look at that data, look at its value, which, remember,
corresponds to a count here.
And I'm going to say, OK, I need to know what
this is going to be in terms of pixels.
In terms of pixels, it's going to be whatever the scale has told me it is.
And now, I have to invert it.
And the process of inverting it is really the same
as subtracting this value from that minimum value at the top.
Sometimes it's a little bit easier to visualize that,
but just so that we can get through the rest of the example,
I'm going to just let you guys figure that out, or look at examples.
Again, very formulaic.
Once you figure it out, it's pretty straightforward.
On the other hand, if you ever do bar graphs where the bars go horizontally,
you don't have to do this.
You can actually just set a solid height for the bars.
And you don't have to figure out any of the weird stuff.
And then it's the width that's going to change.
And that kind of comes naturally the more of these you make.
So we've taken care of width.
And we've taken care of height.
The next question D3 is going to ask is it's
going to say, OK, tell me the x and y position
where I'm actually going to start drawing this bar.
Remember that this y position is still going
to have to correspond to whatever the scale has told me.
Right?
The scale is going to tell me relative to my axis,
where does something with a count of three have to start.
Where does something with a count of 21 have to start.
And so, again, I let my scale handle all of this.
Same thing goes for the x, where I've said, OK, the amount to the right
that I have to shift within my canvas is really
going to depend on every other piece of data that I already have.
And so as a result, I, again, leave this to my scale to figure out.
This math would be really hairy to figure out yourself.
And so I cannot extol enough the virtues of the D3 scales.
Notice here, again, that what I have said
is actually, OK, actually this syntax is a little bit funny.
You'll notice that I'm using this artist map again,
which seems a little bit weird because we kind of already
have this artist key.
The reason I had to do it this way was the following.
I said to my scale that the domain, meaning
any of the inputs it was going to get, were actually going to be artist names.
And so as a result, for a particular piece of data,
rather than hand the scale an artist ID, which would come back
undefined because this domain has no idea what a one corresponds to
or what a three corresponds to.
But it does know what an AC/DC corresponds to,
or a Santana corresponds to.
And so as a result, as I'm iterating through all of my data and count data
making these bars, when I actually need to use my x ordinal scale to figure out
the x position of my bars, I need to pass it a name and not just an ID.
And so we're back to using this artist map.
Finally, this is the last little mildly fancy thing, where you've said,
OK, great, I've made this visualization.
It's pretty.
It sits there.
But the whole idea behind 3D is to make things visual and interactive.
And to make things interactive, you can actually
start layering some of these JavaScript ideas, for example, listeners.
In this case, we're going to say, OK, let's actually add a click listener.
There are other listeners, like mouse over, mouse off.
These are things that I highly recommend googling.
But they work in kind of just the intuitive way
that you might expect buttons to work with just regular JavaScript
on a regular HTML page.
Where I've said, OK, if someone ever clicks on a particular bar,
please note that for that particular bar,
I want you to get whatever is going to get returned from this function call.
I could have written this entire function in here.
But for the sake of clarity and segmenting,
I made the decision to actually segment it away into another function, in case,
for example, my application ever grew.
And I wanted to use it on other places on the page.
But notice what I've passed is the artist name, the artist ID,
and the actual album data that I was dealing with there.
And in fact, this is the entire data set.
So to get that artist name, notice, again, that I'm using this artist map.
Just worth noting.
And basically, what we're going to say is, OK,
every time someone clicks on this bar, go ahead and run this function.
Up until this point, what we've done is literally just the following.
Right?
We've made axes such that they are calculated properly
for the amount of data that we're going to do.
Right?
But in fact, the very first thing we did was load in this data
and clean it in such a way that it was going to work
with what we wanted to visualize.
This included sorting the data.
If this were a more fancy and real world data set,
it might also include iterating through and taking care of anything
like Nan values.
Right?
Where there literally is no value.
These are things that you're going to have to think about
as you're visualizing your data.
How do you want to visualize a Nan?
Do you want to visualize it as a zero?
Or do you want to just literally cut it out of your data set?
And these are kind of like big philosophical questions
that are worth asking yourself as you visualize data.
In addition to all of that, remember that I actually cut down our data
set because I thought that fewer would be more interesting because it
would be easier to read.
And then keep in mind that we did a lot of math
to actually handle making the axes in addition to then drawing
the axes onto the canvas.
That final thing we did is actually dictating
a lot of what is happening over here.
Remember that JavaScript, one of its primary objectives in life
is to change the DOM.
And so, in fact, what's happening here is that every time I click,
I am actually appending new information to a div
that I had already set up over here.
If you're curious to see this in the Inspector,
you can remember that I had this just empty div with the ID albums.
And in fact, what that little function that I'm going to show you in a second
is going to do is it's going to find that div and append to it
an unordered list.
And what this is going to end up doing is
showing us more interesting information about the data
that we've interacted with from the bar graph.
So to see what that looks like, pretty quickly,
here's our just normal querySelector where I've said,
OK, find me this thing called the artist span.
For reference, again, if we go back to index,
we'll see that, in fact, I wanted some nice way to put the name of the artist
actually within this header.
So within this entire H3, I used a little trick
where I said, OK, I'm going to include a span, which is really
just an empty space in the HTML into which you can then
put other pieces of HTML.
In this case, I gave that span an ID.
But notice that there's nothing in the span, at least not at the beginning.
And again, I'll just point your attention
to this empty div with the ID albums.
If we go back to Main, notice that I said,
OK, I'm going to select that span.
And within its inner HTML, I'm going to go ahead and just append
that artist name.
And that is literally what is dictating something like Pearl Jam appearing
here, or Santana appearing here.
The unordered list is a little bit more complicated, but only slightly so,
where you can imagine that really what I was
doing as I was making this was I was saying,
OK, I know I need to start an unordered list tag.
And I need to end an unordered list tag.
And in fact, you'll notice here that I didn't even close the unordered list.
In fact, Chrome and HTML were smart enough
to know that if I opened an unordered list, it should probably close it.
But to be a little bit more precise in what we're doing, you can go ahead
and actually just close that tag just to kind of tie up loose ends.
But you'll notice that I said, OK, I'm going to iterate
through each one of the albums.
And I'm going to say, OK, if the album that I'm holding right now,
if that artist ID matches the ID of the thing that was clicked on,
I'm going to consider this a match.
And as a result, I'm going to say, OK, this album,
this title actually corresponded to the artist that my user clicked on.
And so I'm going to append it to this growing string of HTML,
which I can then display later.
If I console logged this UL list for you,
you would see that it's really just an ugly string of HTML.
It has two UL tags on the outside and a bunch of LIs on the inside,
where the thing within the LI is really just this album title.
Notice that I'm using this JavaScript string concatenation operator.
And in fact, I like it a lot because you can do really fancy things with it.
And finally, what I said is, OK, I've iterated through all of the data.
I've made this new string.
Let's go ahead and display it.
And to do that, I select that div again using its ID, which was albums.
I go ahead and say, OK, it's inner HTML is now
going to equal that ridiculous string that I just made.
And you'll notice that if we then actually inspected
this element, what we have done is we've inputted this entire string here.
And that is how we went ahead and made a very simple visualization.
Are there fancier things you can do with D3?
Absolutely.
You can do brushing.
You can do filtering.
You can make your visualizations move in cool ways
depending on what your user has decided to input in terms of parameters.
Because this is just a quick demonstration,
I didn't want to go through all of that today.
But, again, I highly recommend checking out some of the examples online.
And with that, I think I'm going to wrap up.
If there's any questions, I'm happy to take them.
Yeah?
AUDIENCE: [INAUDIBLE] that you were to do something like animate the bars
so that they rise up [INAUDIBLE],, or something
like turning the bars into points instead of bars.
ATHENA BRAUN: Yeah.
So that's a great question.
So the question here was how easy would it be to animate the bars?
Or how easy would it be to make these more like circles instead
of actual bars?
So those are kind of two separate questions.
In terms of the animation itself, what you can do is there is actually a D3--
I don't know what to call it.
I guess it's part of the chain called Transform.
So you would say like dot transform.
And what the transformation would do is it would actually
animate things for you.
Sometimes the transformations are a little finicky
because you have to put them in like exactly the right place.
And so what I would recommend doing for that is looking up examples.
I could probably guess it where it's supposed to go in this,
but I don't want to do that, because that's going to be messy.
On the other hand, how easy is it to make
these points instead of rectangles?
Super easy.
Where if you think about it, a point is really just a circle.
So instead of appending rectangles, what instead I could have done
was appended circles.
So I literally would just change this to circle.
But unlike rectangles, circles actually now we
need to have an idea of where it's center point is going to be.
And it has to have an idea of what its radius is going to be.
And the idea here is very similar to what you're doing with the scales
where you just need to configure your scales a little bit
differently to give you that point that you want.
But otherwise, it's actually fairly simple.
And again, great examples online.
Yeah.
All right.
Well, if there's no other questions, I'm going to wrap up.
I will gladly send this code.
Thanks so much for coming.
And this was CS50 question mark.