5 Just Enough JS

Basics: IDVW, pp. 36-52

Before JavaScript ES6, the ‘var’ keyword was used to declare a variable. Variables declared using the ‘var’ keyword are either globally or functionally scoped, they do not support block-level scope. Therefore, in JavaScript ES6, the ‘let’ keyword and ‘const’ keyword were introduced.

The ‘let’ keyword deals with a block scope. It can be reassigned but cannot be redeclared.

The ‘const’ keyword is also blocked scoped. It cannot be reassigned and cannot be redeclared.

As a general rule, you should always declare variables with ‘const’, if you realize that the value of the variable needs to change, go back and change it to ‘let’.

For more detailed information regaring ‘var’, ‘let’ and ‘const’, please see Difference between var, let, and const keyword in JavaScript

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

5.1 Arrays of arrays

Open the JavaScript Console

// try me in the Console 
const 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
const 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
const 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:

// take the square root of each element
const x = [3, 5, 7];     // try me
x.map(Math.sqrt);
// multiply each element by 3
[4, 10, 12].map(d => d*3);     // try me
// multiply each element by 3
[4, 10, 12].map(function(d) {return d*3;});     // try me
// multiply each element by its index
[10, 20, 30, 40].map((d, i) => d*i);     // try me

R: Sum two arrays

# sum two arrays
x <- 1:3
y <- 4:6
x + y
## R output ##  [1] 5 7 9

JavaScript: Sum two arrays

// sum two arrays
const x = [1, 2, 3];
const y = [4, 5, 6];
x + y                         // try me... what went wrong?
// sum two arrays
const x = [1, 2, 3];
const y = [4, 5, 6];
x.map((d, i) => d + y[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 D3 sorting

Use d3.sort() rather than plain JavaScript options.

const 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:

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

https://github.com/d3/d3/blob/main/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

const 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

const 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

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