5 Just Enough JS

Basics: IDVW, pp. 36-52

objects, arrays, arrays of objects, functions (and other things)

5.1 Arrays of arrays

Open the JavaScript Console

// try me in the Console 
var array_dataset = [[100, 75, 30], [200, 125, 20]];

d3.select("svg#arrays")
  .selectAll("circle")
  .data(array_dataset)
  .enter()
  .append("circle")
    .attr("cx", d => d[0])
    .attr("cy", d => d[1])
    .attr("r", d => d[2])
    .attr("fill", "red");
svg#arrays

5.2 Arrays of objects

// Try me in the Console
var object_dataset = [
  {cx: 100, cy: 150, fill: `red`},
  {cx: 200, cy: 100, fill: `blue`}
  ];

d3.select("svg#objects")
  .selectAll("circle")
  .data(object_dataset)
  .enter()
  .append("circle")
    .attr("cx", d => d.cx)
    .attr("cy", d => d.cy)
    .attr("r", "30")
    .attr("fill", d => d.fill);
svg#objects

See also: JavaScript Array of Objects Tutorial

5.3 .map()

What’s the issue?

In R many operations are vectorized:

sqrt(3)
## R output ##  [1] 1.732051
x <- c(3, 5, 7)
sqrt(x)
## R output ##  [1] 1.732051 2.236068 2.645751

Not so in JavaScript:

Math.sqrt(3);     // Try me in the Console
var x = [3, 5, 7];     // Try me in the Console
Math.sqrt(x);          // Doesn't work...

5.3.1 Simple arrays

Use .map() to operate on each array element separately. The concept is similar to lapply() or purrr::map(), but unlike in R, it’s needed for simple arrays.

R

x <- c(3, 5, 7)
sqrt(x)
## R output ##  [1] 1.732051 2.236068 2.645751

JavaScript

Do something to every element of a simple array:

var x = [3, 5, 7];     // try me
x.map(Math.sqrt);
[4, 10, 12].map(d => d*3);     // try me
[4, 10, 12].map(function(d) {return d*3;});     // try me
[10, 20, 30, 40].map((d, i) => d*i);     // try me

5.3.2 Arrays of arrays

Do something to the first item of every element of a nested array:

[[1, 2], [3, 4]].map(d => Math.sqrt(d[0]))  // try me

Sum up all items in each element of the array:

[[1, 2, 3], [4, 5, 6]].map(d => d[0] + d[1] + d[2]); // try me

Created a nested array out of a simple array:

[10, 20, 30].map(d => [d, Math.pow(d, 2)]);

5.3.3 Create arrays of objects

Create an array of objects out of a simple array (note the parentheses around the object):

[10, 20, 30].map(d => ({n: d, nsq: Math.pow(d, 2)}));  // try me
[10, 20, 30].map((d, i) => ({index: i, value: d}));  // try me

5.4 Sorting

Sorting in JavaScript sorts by character and modifies the original array2:

var x = [3, 1, 5, 12, 7];     // try me
x.sort();
x;     // try me

If we want to sort by number, not character, we can pass the comparator function d3.ascending to .sort():

var x = [3, 1, 5, 12, 7];     // try me
x.sort(d3.ascending);

d3.sort() will produce the same results:

var y = [3, 1, 5, 12, 7];     // try me
d3.sort(y);

5.5 D3 statistics

link to API

D3 brings us back to familiar ground with functions that take an array and return a single value. Here are D3 functions with the same names and behavior as their R equivalents:

R D3
min(x) d3.min(x)
max(x) d3.max(x)
sum(x) d3.sum(x)
mean(x) d3.mean(x)
median(x) d3.median(x)


A few with different names:

R D3
range(x) d3.extent(x)
var(x) d3.variance(x)
sd(x) d3.deviation(x)


d3.quantile() takes a single value for p, not an array as in R. (In earlier versions of D3 it was necessary to sort the array before finding quantiles, but this is no longer the case.)

R D3
quantile(x) d3.quantile(x, p)

Thus for a single quantile we have:

var x = [12, 34, 1, 43, 90, 72];      // try me
d3.quantile(x.sort(d3.ascending), .25);

https://github.com/d3/d3/blob/master/API.md#statistics

5.6 D3 + .map()

D3 statistics functions combined with .map() can be helpful in a variety of situations.

Vectorizing a parameter, for example to mimic quantile(x) in R:

R

x <- c(1, 12, 34, 43, 72, 90);  
quantile(x)
## R output ##     0%   25%   50%   75%  100% 
## R output ##   1.00 17.50 38.50 64.75 90.00

JavaScript

var x = [1, 12, 34, 43, 72, 90];      // try me
[0, .25, .5, .75, 1].map(p => d3.quantile(x, p));

Sum up the first item of all elements in an array of arrays:

R

l <- list(c(100, 200, 40), c(300, 150, 20))
sum(purrr::map_dbl(l, ~.x[1]))
## R output ##  [1] 400

JavaScript

var dataset = [[100, 200, 40], [300, 150, 20]];     // try me
d3.sum(dataset.map(d => d[0]));

Sum up all items in each array to create a simple array:

R

l <- list(c(100, 200, 40), c(300, 150, 20))
purrr::map_dbl(l, ~sum(.x))
## R output ##  [1] 340 470

JavaScript

var dataset = [[100, 200, 40], [300, 150, 20]];     // try me
dataset.map(d => d3.sum(d));

  1. If you don’t wish for that to happen, you’ll need to make a deep copy.↩︎