Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions 06-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ cat_list.push({weight : 6 , past_weight_values : [5.9, 5.3, 6.1], name : 'Snowba

This process is called 'nesting'.

> # Nesting {.challenge}
> ## Nesting {.challenge}
> 1. Append the array by a third cat, not entering a name or weight.
> 1. Do all animals have to have the same attribute fields?
> 1. Use the console of your browser to read the values of your object.
Expand Down Expand Up @@ -124,7 +124,7 @@ to convert it into a string.
JavaScript provides an easy way to do this. Data can get converted using
`JSON.stringify()`.

> # Arrays of objects {.challenge}
> ## Arrays of objects {.challenge}
> 1. Append a third cat to the array, not entering a name or weight.
> 1. Do all animals have to have the same attribute fields?
> 1. Use the console of your browser to read the values of your object.
Expand All @@ -149,7 +149,7 @@ When the map function is called using `cat_list.map`, it loops through all eleme
in `cat_list`, calls each temporarily `cat`, and creates a new list `dog_list`, which
will have one dog per cat. The function `concat` simply concatenates two strings.

> # How much do dogs weigh? {.challenge}
> ## How much do dogs weigh? {.challenge}
> 1. Let's assume dog misters weigh twice as much as their feline version. Make
> the new array reflect this.

Expand Down
42 changes: 32 additions & 10 deletions 07-d3setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ The first thing we need, is of course our data, which is stored in 'nations.json
D3 provides a handy function to read in `json`-files:

~~~{.d3}
d3.json("resources/nations.json", function(nations) { }
d3.json("code/nations.json", function(nations) { });
~~~

This line probably needs a little explanation and we'll go through it bit by bit:

* `d3.json()` is called the function call. In this case, we have a function that reads in a json file, parses it, and is also able to do something with the parsed data on the way.
* The first argument `"resources/nations.json"` tells the function where to get the data we want to have parsed.
* The first argument `code/nations.json"` tells the function where to get the data we want to have parsed.
* `function(...){...}` is called the callback function. It is a so-called 'inline' function, which means it has no name (we're only operating in the object space here). This also means we can't use this function anywhere else in our code. The code we put inside the curly brackets is the code that's run once d3.json() is called and the data is loaded.
* D3 assigns the name `nations` to the parsed object it returns. We can only use 'nations' within the callback function, this means our code only knows of `nations` inside the curly brackets.
* What seems unusual, but is actually quite common, is that this function call doesn't return anything. It is simply executed and displayed (if we tell it to), but no value is returned.
Expand Down Expand Up @@ -90,7 +90,7 @@ fill now.
We'll have a picture frame (an SVG-element), our drawing area (a g-element), and in
that drawing area, we'll have separate elements for both axes and the area for our circles.

Firt, we need to link the JavaScript and HTML environement so that we have writing access
First, we need to link the JavaScript and HTML environment so that we have writing access
to the HTML.
To do this, we use the `.select()`. This lets us grab an element by specifying its ID.

Expand All @@ -111,9 +111,20 @@ This is equivalent to writing:
<p id="chart_area"> <svg> </svg> </p>
~~~

in the HTML file. We chose to append because we now have access to the SVG element without the need to seperately select it by ID.
in the HTML file. We chose to append because we now have access to the SVG element without the need to separately select it by ID.

We also create the canvas inside the frame:
We can open our HTML file in a browser and see that only the heading is actually visible on the page.
It is normal as our drawing is currently empty.
To materialize our empty drawing and be sure it is here, let's add a thick red border around it, by adding the following in the `main.css` file:

~~~{.css}
svg {
border: 5px solid red;
}
~~~

Refreshing the page should make it clear that our empty drawing actually exists.
Now, we also create the canvas inside the frame:

~~~{.js}
// Create canvas inside frame.
Expand All @@ -124,7 +135,7 @@ Let's set up the dimensions for our elements that we want to use:

~~~{.js}
// Set margins, width, and height.
var margin = {top: 19.5, right: 19.5, bottom: 19.5, left: 39.5};
var margin = {top: 21.5, right: 21.5, bottom: 21.5, left: 39.5};
var frame_width = 960;
var frame_height = 350;
var canvas_width = frame_width - margin.left - margin.right;
Expand All @@ -139,6 +150,8 @@ frame.attr("width", frame_width);
frame.attr("height", frame_height);
~~~

Refreshing the page will show us the new size for the SVG drawing.

The canvas element will have to fit nicely into the frame. To make it fit, we set
a transform attribute and use the translate function.

Expand All @@ -147,12 +160,21 @@ a transform attribute and use the translate function.
canvas.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
~~~

> # Time for a challenge I think! {.challenge}
> ## Time for a challenge I think! {.challenge}
> Let us consolidate our understanding of SVGs and D3 selectors.

> 1. Add a circle element to the HTML using the SVG markup we learnt in lesson 3.
>
> 1. Create a new SVG element and a circle element using the SVG markup we learnt in lesson 3.
> 2. Use `d3.select` to get a reference to the circle.
> 3. Once the circle reference is obtained, make the radius 40px, the border black and the colour green.

>
> HINT: use the `attr` and `style` methods on the circle object obtained.

> ## Understanding the "transform". {.challenge}
>
>
> 1. Use Javascript code to Create a new circle inside our "canvas", positioned in 0, 0, with a radius of 5px.
> 2. Check that the circle now shows up.
> 3. Play with the values that are put for the "translate" transform in order to understand what is the reference frame used.
>
> HINT: refresh your page in your browser every time you do a modification.

4 changes: 2 additions & 2 deletions 08-d3enter.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ canvas.append("g")
We add a transform attribute to move the axis to the bottom of the plotting area (instead of having it across the top). There are a number of transform options, but here we are just using `translate` and pass in the amount to shift the axis in the x and y directions, respectively. Here we shift it only in the y direction (i.e. down) by an amount given by height of the canvas.
We also give it a class, just in case we might want to select the axis later in our code.

> # We might need a y-axis, too {.challenge}
> ## We might need a y-axis, too {.challenge}
> 1. Create a linear scale for the y-axis, with 10 being the minimum and 85 being the maximum value. Then, add the axis to the canvas.

We're slowly getting there. Having our two axes, we can now finally add our data.
Expand Down Expand Up @@ -112,7 +112,7 @@ The attributes `cx` and `cy` define the position of the centre of the circle and
arbitrary number... for now.


> # A new dimension {.challenge}
> ## A new dimension {.challenge}
> Change the code so that the radius of the circles represents the population. First, create a 'sqrt' scale with a minimum of 0 and a maximum of 5e8. The range should be between 0 and 40. Also, don't forget to include a mapping function for the scale for population.


Expand Down
56 changes: 30 additions & 26 deletions 09-d3exit.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ Our plot is pretty busy. We might not want to display everything all the time.
The goal for this lesson is to update the plot based on what kind of data we want to
display.

First, we will reorganize our Javascript program.
It currently mixes two concerns:

- one-time initialization: setting up the SVG element and its sub elements, positioning and sizing them, etc;
- data manipulation (everything related to the `dot` variable), that we actually might want to execute again when data changes.

To be able to execute the data-manipulation code multiple times, we will isolate it in a new function that we decide to name `refreshData`, and call this function (so that the code is actually executed).

~~~{.js}
// define the function with the data-manipulation code
function refreshData() {
var dot = data_canvas.selectAll(".dot").data(nations, function(d){return d.name});

dot.enter().append("circle").........
}

// call the function once
refreshData();
~~~

Now we can resume our original task: plotting only a sub-part of the data.
First, we need to find a way to filter our data. We use the function `filter` to do this.
Similar to previous functions (e.g. `map`), this function iterates over each of the elements in the array `nations`, temporarily calling it `nation`.
It only includes elements in the new array `filtered_nations` if the function evaluates to 'true' for that element. Here this will be the case for nations whose population in 2009 was larger than 10,000,000.
Expand All @@ -25,7 +46,7 @@ var filtered_nations = nations.filter(function(nation){
});
~~~

> # Filtering by region {.challenge}
> ## Filtering by region {.challenge}
> You might have noticed that our data contains information about the region in
> which a country is.
>
Expand Down Expand Up @@ -66,10 +87,10 @@ if (this.checked) { // adding data points
}
~~~

This `if`-statement gets executed every time a checkbox is checked. To add the data points, we can use the `push`-function, which adds one object to an array at a time.
First, we filter the nations we want to add, calling them `new_nations`. Next, we are looping through all new nations and add one at a time to the array `filtered_nations`.
This `if`-statement gets executed every time a checkbox is checked. To add the data points, we can use the `concat`-function, which concatenates two arrays and returns a new array.
First, we filter the nations we want to add, calling them `new_nations`. Next, we concatenate this new array to the existing one and save it back to the array `filtered_nations`.

We also have to initialise `filtered_nations` at the top of our script. Remember that there is a difference between the object and the name space, so in order to keep `nations` the way it is, we need to map the values instead of just using `=`.
We also have to initialise `filtered_nations` at the top of our script, or at least before our `refreshData` function. Remember that there is a difference between the object and the name space, so in order to keep `nations` the way it is, we need to map the values instead of just using `=`.

~~~{.js}
var filtered_nations = nations.map(function(nation) { return nation; });
Expand All @@ -87,29 +108,14 @@ Whereas before `enter()` was used to append new elements to the plot, `exit()` i

A good, brief explanation of this linking between data and elements on the page can be found [here](http://bost.ocks.org/mike/join/). This article discusses the three important functions used for this: `enter`, `exit`, and a third function `update` that we will get to shortly.

> # Removing elements {.challenge}
> ## Removing elements {.challenge}
> 1. Using an `else` case after the `if` statement, create a filter that removes elements from `filtered_data` that correspond to the checkbox that was just unchecked. (i.e. `else { filtered_nations = <--- fill in this bit --->}`).

> # Another new dimension {.challenge}
> ## Another new dimension {.challenge}
> 1. Have the colour of circles represent the region. Use category20() to make a scale. You will then need to add `.style("fill", function(d) { <-- fill in this bit ---> });` to the enter() function.

As a last step, let's move `enter()` and `exit()` into a separate function. This will become useful when we want to update the data from different elements on the page.

~~~{.js}
function update() {
var dot = data_canvas.selectAll(".dot").data(filtered_nations, function(d){return d.name});

dot.enter().append("circle").attr("class","dot")
.style("fill", function(d) { return colorScale(d.region); });
.attr("cx", function(d) { return xScale(d.income[d.income.length-1]); }) // this is how attr knows to work with the data
.attr("cy", function(d) { return yScale(d.lifeExpectancy[d.lifeExpectancy.length-1]); })
.attr("r", function(d) { return rScale(d.population[d.population.length-1]); });

dot.exit().remove();
}
~~~

This means that we now have to call the update function from our event listener after updating `filtered_nations` based on the checkbox change:
As we have isolated the data-processing code in the `refreshData` function, we can now refresh the data every time the checkbox is clicked.
This means that we now have to call the `refreshData` function from our event listener after updating `filtered_nations` based on the checkbox change:

~~~{.js}
d3.selectAll(".region_cb").on("change", function() {
Expand All @@ -120,12 +126,10 @@ d3.selectAll(".region_cb").on("change", function() {
} else { // remove data points from the data that match the filter
filtered_nations = filtered_nations.filter(function(nation){ return nation.region != type;});
}
update();
refreshData();
});
~~~

In order to create the plot when we first load the page, we will also have to call `update()` outside of our event listeners once.

By the end of this lesson, your page should look something like this:

<iframe src="http://isakiko.github.io/D3-visualising-data/code/index09.html" width="1000" height="600"></iframe>
25 changes: 18 additions & 7 deletions 10-d3update.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ To get the index (rather than the actual year), we can simply subtract the first
var year_idx = parseInt(document.getElementById("year_slider").value)-1950;
~~~

Updating the year becomes quite simple. All we need to do is add another event listener that changes the year the moment we touch the slider. The event we want to listen for is called `input`. We then execute the `update()` function we wrote earlier.
Updating the year becomes quite simple. All we need to do is add another event listener that changes the year the moment we touch the slider. The event we want to listen for is called `input`. We then execute the `refreshData()` function we wrote earlier.

~~~{.js}
d3.select("#year_slider").on("input", function () {
year_idx = parseInt(this.value) - 1950;
update();
refreshData();
});
~~~

So far, the update function only knows how to handle new data (`.enter`) and removed data (`.exit`), but not what to do when we update data.
So far, the `refreshData` function only knows how to handle new data (`.enter`) and removed data (`.exit`), but not what to do when we update data.
In addition to `d3.enter()` and `d3.exit()`, D3 also offers `d3.transition` to handle updating data. First, we need to define how to transition between data points. We might want to interpolate between to values linearly over the duration of 200 ms, like this:

~~~{.js}
Expand All @@ -64,7 +64,7 @@ dot.transition().ease("linear").duration(200)
> * elastic(a, p) - simulates an elastic band; may extend slightly beyond 0 and 1.
> * [more here](https://github.com/mbostock/d3/wiki/Transitions#d3_ease)

> # Play time {.challenge}
> ## Play time {.challenge}
> D3 is incredible versatile. Try out different transitions and if you have time, maybe try drawing rectangles instead of circles.

Next, we might want to create a tooltip. Let's go have a look at what's already out there.
Expand Down Expand Up @@ -147,15 +147,26 @@ function calc_mean(region_data) {
}
~~~

> # The master challenge {.challenge}
> ## The master challenge {.challenge}
> It's time to put together everything you've learned. Write code that displays (and updates) the mean values that we just computed as little crosses in the graph for the different regions.

> # ...style! {.challenge}
> ## ...style! {.challenge}
> Add axis labels and make the fonts pretty.

> # Using different data formats {.challenge}
> ## Using different data formats {.challenge}
> What if you don't have your data in JSON format? Change your code to load in nations.csv instead of nations.json and have it produce the same plot.

> ## Generate the checkboxes automatically {.challenge}
> Currently, we have manually created checkboxes in the HTML file.
> Using the `region_names` variable that is a list of all region names, create the checkboxes using javascript and/or d3.

> ## Get the region names from the data {.challenge}
> The `region_names` array is currently filled in manually.
> You want to replace this by an automatic extraction from the actual data file.
>
> - using the `nations` data and the `map` function, extract the `region` of each nation,
> - using the `d3.set` function that creates a set (without duplicate values) and the `.values` function on a set (that return an array), filter the `region_names` array so that it does not contain duplicates anymore

By the end of this lesson, your page should look something like this:

<iframe src="http://isakiko.github.io/D3-visualising-data/code/index10.html" width="1000" height="600"></iframe>