Public
Edited
Feb 25
Insert cell
Insert cell
Insert cell
// Try to change the value of "radius" and hit "shitf+enter". You will see the "area" cell changes automatically

radius = 7
Insert cell
// The variable "area" depends on "radius", its value will be updated whenever "radius" changes

area = Math.PI * Math.pow(radius, 2)
Insert cell
Insert cell
Insert cell
{
let length = 3
return length * length
}
Insert cell
Insert cell
Insert cell
// Declare an array with square brackets "[]"

visualChannels = ['position', 'length', 'angle', 'area', 'color', 'curvature']
Insert cell
// Get the length of an array

visualChannels.length
Insert cell
// Access an item in an array

'Most effective channel: ' + visualChannels[0]
Insert cell
Insert cell
// Concatenate two arrays

moreVisualChannels = visualChannels.concat(['color hue', 'color saturation', 'color luminance'])
Insert cell
// To sort an array, we use "sort"

moreVisualChannels.sort()
Insert cell
/******************************************************************************
* TODO: *
* Try to create two lists of colors, then concatenate them, and sort in *
* alphabetical order! *
* The two lists are: *
* red, green, blue *
* cyan, magenta, yellow, black *
******************************************************************************/
{
let list1 = ['red', 'blue', 'green']
let list2 = ['cyan', 'magenta', 'yellow', 'black']

let list3 = list1.concat(list2)
list3.sort()
return list3
}
/******************************************************************************
* END OF YOUR CODE *
******************************************************************************/
Insert cell
Insert cell
Insert cell
// Declare an object

typesOfData = ({
ordered: [1, 2, 3, 4, 5],
categorical: ['strawberry', 'apple', 'orange']
})
Insert cell
// To access an attribute of an object, we simply use "."

typesOfData.categorical
Insert cell
Insert cell
Insert cell
// To modify an attribute of an object, we simply assign a value to it

{
let typesOfData = {
ordered: [1, 2, 3, 4, 5],
categorical: ['strawberry', 'apple', 'orange']
}
typesOfData.categorical = ['strawberry', 'apple', 'orange', 'banana']
return typesOfData.categorical
}
Insert cell
/******************************************************************************
* TODO: *
* Try to modify the values of the "ordered" attribute to 1, 10, 100, 1000! *
******************************************************************************/
{
typesOfData.ordered = [1, 10, 100, 1000]
return typesOfData.ordered
}
/******************************************************************************
* END OF YOUR CODE *
******************************************************************************/
Insert cell
Insert cell
// If statement

{
let color = 'cyan'
let hex = ''

if (color === 'cyan') {
hex = rgb2hex(0, 255, 255)
} else if (color === 'magenta') {
hex = rgb2hex(255, 0, 255)
} else if (color === 'yellow') {
hex = rgb2hex(255, 255, 0)
} else {
hex = rgb2hex(255, 255, 255)
}
return hex
}
Insert cell
// Loop through items in an array

{
let rgb = ['red', 'green', 'blue']
let rgbValues = [[255, 0, 0], [0, 255, 0], [0, 0, 255]]
let rgbHex = []
for (let i = 0; i < rgb.length; i++) {
let [r, g, b] = rgbValues[i]
// append an item to the array
rgbHex.push(rgb2hex(r, g, b))
}
return rgbHex
}
Insert cell
/******************************************************************************
* TODO: *
* Try to find the hex codes of cyan, magenta, yellow and black! *
******************************************************************************/
{
let rgb = ['cyan', 'magenta', 'yellow', 'black']
let rgbValues = [[0, 255, 255], [255, 255, 0], [0, 0, 255],[255, 255, 255]]

let rgbHex = []
for (let i = 0; i < rgb.length; i++) {
let [r, g, b] = rgbValues[i]
rgbHex.push(rgb2hex(r, g, b))
}

return rgbHex
}
/******************************************************************************
* END OF YOUR CODE *
******************************************************************************/
Insert cell
Insert cell
// Declare a function with keyword "function"

function int2hex (n) {
return n >= 16 ? n.toString(16) : '0' + n.toString(16)
}
Insert cell
// Calling the "int2hex" function

int2hex(255)
Insert cell
// Alternatively, we can declare a function using arrow

rgb2hex = (r = 0, g = 0, b = 0) => {
return '#' + int2hex(r) + int2hex(g) + int2hex(b)
}
Insert cell
rgb2hex(64, 224, 208)
Insert cell
// Converting color name to hex code

function rgbColor2hex (color) {
if (color === 'red') {
return rgb2hex(255, 0, 0)
} else if (color === 'green') {
return rgb2hex(0, 255, 0)
} else {
return rgb2hex(0, 0, 255)
}
}
Insert cell
rgbColor2hex('red')
Insert cell
/******************************************************************************
* TODO: *
* Try to use arrow function expression to create a function that takes color *
* name as input and return hex code for cyan, magenta, yellow and black! *
******************************************************************************/
getHex = (color = " ") => {
if (color === 'cyan') {
return rgb2hex(0, 255, 255)
} else if (color === 'magenta') {
return rgb2hex(255, 255, 0)
} else if (color === 'yellow') {
return rgb2hex(0, 0, 255)
} else {
return rgb2hex(255, 255, 255)
}
}

/******************************************************************************
* END OF YOUR CODE *
******************************************************************************/
Insert cell
Insert cell
// Built-in array function "map"
// It calls the "rgbColor2hex" function for each item in the array

['red', 'green', 'blue'].map(rgbColor2hex)
Insert cell
/******************************************************************************
* TODO: *
* Try to map the "cyan", "magenta", "yellow", "black" over the function you *
* have created above to get their hex codes! *
******************************************************************************/

['cyan', 'yellow', 'magenta', 'black'].map(getHex)

/******************************************************************************
* END OF YOUR CODE *
******************************************************************************/
Insert cell
Insert cell
Insert cell
// Import d3, "@5" specifies the version we want

d3 = require('d3@7')
Insert cell
import { legend } from "@mbostock/mortality-due-to-alcohol-use-disorder"
Insert cell
Insert cell
<svg width=120 height=120>
<circle cx=60 cy=60 r=50 stroke='black' fill='none' /> <!-- outter circle -->
<circle cx=60 cy=60 r=10 stroke='black' fill='none' /> <!-- inner circle -->
<line x1=10 x2=50 y1=60 y2=60 stroke='black' /> <!-- left horizontal bar -->
<line x1=70 x2=110 y1=60 y2=60 stroke='black' /> <!-- right horizontal bar -->
</svg>
Insert cell
Insert cell
// Linear scale, mapping 0 - 40 to 0 - 100

linearScale = d3.scaleLinear()
.range([0, 100])
.domain([0, 40])
Insert cell
[10, 20, 30, 40].map(n => linearScale(n))
Insert cell
/******************************************************************************
* TODO: *
* Try to make a scale that maps 0 - 12 to 0 - 720! e.g. 1.5 -> 90 *
******************************************************************************/


linearScale2 = d3.scaleLinear().range([0, 12]).domain([0, 720])

/******************************************************************************
* END OF YOUR CODE *
******************************************************************************/
Insert cell
// We also use scale when encoding values with color
// Remember that color scale can be diverging, sequential and categorical
// the following demonstrate how to build a sequential color scale

colorScale = d3.scaleLinear()
.interpolate(() => d3.interpolateOranges)
.domain([0, 40])
Insert cell
[10, 20, 30, 40].map(n => colorScale(n))
Insert cell
legend(colorScale)
Insert cell
/******************************************************************************
* TODO: *
* Try to make a diverging color scale with "d3.interpolateRdYlBu" color *
* scheme with domain [0, 100]! *
******************************************************************************/
colorScale2 = d3.scaleLinear()
.interpolate(() => d3.interpolateRdYlBu)
.domain([0, 100])

/******************************************************************************
* END OF YOUR CODE *
******************************************************************************/
Insert cell
Insert cell
Insert cell
Insert cell
// Width of available space of an output cell on Observable notebook
// We use it as the maximum width of our drawing "canvas", it is a global
// variable, declared and set by Observable notebook

width
Insert cell
{
// While width is given, we need to define the height of our drawing "canvas"
const height = 60
// Define our svg
const svg = d3.select(DOM.svg(width, height))
// Make some empty space around
// Try to set the margins to zeros and see what happens!
const margin = { left: 30, top: 10, right: 10, bottom: 20 }
const xScale = d3.scaleLinear()
// Instead of mapping (0 - 40) to (0 - 100), we map (0 - 40) to (30 - 945) to fill the width
.range([margin.left, width - margin.right])
.domain([0, 40])
// Append it to svg
svg.append('g').call(d3.axisBottom(xScale))
// Return it, and Observable will show it as output
return svg.node()
}
Insert cell
Insert cell
{
const height = 600
const svg = d3.select(DOM.svg(60, height))

const margin = { left: 30, top: 10, right: 10, bottom: 20 }
const yScale = d3.scaleLinear()
// For y-axis, we map (0 - 40) to (580 - 10) to fill the height
// If you wonder why we are mapping the values reversely, 0 -> 580 and 40 -> 10,
// as in the coordination system of our screens, the top left corner is (0, 0)
// and bottom left corner is (0, 600)
.range([height - margin.bottom, margin.top])
.domain([0, 40])
// Append it to svg
svg.append('g')
.call(d3.axisLeft(yScale))
// Need to shift the axis a little bit in order to show the tick labels
// See more about "transform" in the cell below
.attr('transform', `translate(${margin.left},0)`)
return svg.node()
}
Insert cell
Insert cell
Insert cell
{
const height = 60
const svg = d3.select(DOM.svg(width, height))
const margin = { left: 30, top: 10, right: 10, bottom: 20 }

const xScale = d3.scaleLinear()
.range([margin.left, width - margin.right])
.domain([0, 40])
svg.append('g')
.call(d3.axisBottom(xScale))
.attr('transform', `translate(0,${height - margin.bottom})`)
// ----- The above are the same as plotting x-axis in the previous section -----
// Add circles to svg, more explanation in the cell below
svg.selectAll('circle')
.data([10, 20, 30, 40])
.enter()
.append('circle')
.attr('cx', d => xScale(d))
.attr('cy', 20)
.attr('r', 10)
.attr('fill', 'SteelBlue')
return svg.node()
}
Insert cell
Insert cell
/******************************************************************************
* TODO: *
* Try to draw the 4 circles vertically with y-axis! *
* Also try to change their color, radius, stroke-color, etc.! *
******************************************************************************/

{
const height = 600
const svg = d3.select(DOM.svg(60, height))

const margin = { left: 10, top: 10, right: 15, bottom: 20 }

const yScale = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain([0, 40])
svg.append('g')
.call(d3.axisRight(yScale))
.attr('transform', `translate(${margin.right}, 0)`)
svg.selectAll('circle')
.data([1, 25, 30, 40])
.enter()
.append('circle')
.attr('cy', d => yScale(d))
.attr('cx', 20 + margin.right)
.attr('r', 8)
.attr('fill', 'Black')
return svg.node()
}

/******************************************************************************
* END OF YOUR CODE *
******************************************************************************/
Insert cell
Insert cell
Insert cell
// Example dataset grows in exponential scale

exponentialData = _.range(0, 5).map(x => ({ x, y: 5 * Math.pow(2, x) }))
Insert cell
d3.extent(exponentialData.map(d => d.x))
Insert cell
d3.extent(exponentialData.map(d => d.y))
Insert cell
/******************************************************************************
* TODO: *
* Try to make another exponential dataset with exponents of 3 and use *
* d3.extent to find the minimum and maximum values of the y values! *
******************************************************************************/

{
let exponentialData2 = _.range(0, 5).map(x => ({ x, y: 5 * Math.pow(3, x) }))
return d3.extent(exponentialData2.map(d => d.y))
}


/******************************************************************************
* END OF YOUR CODE *
******************************************************************************/
Insert cell
Insert cell
{
const height = 600
const svg = d3.select(DOM.svg(width, height))
const margin = { left: 30, top: 10, right: 10, bottom: 20 }
const xScale = d3.scaleLinear()
.range([margin.left, width - margin.right])
.domain(d3.extent(exponentialData.map(d => d.x)))
svg.append('g')
.call(d3.axisBottom(xScale))
.attr('transform', `translate(0,${height - margin.bottom})`)
const yScale = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain(d3.extent(exponentialData.map(d => d.y)))
svg.append('g')
.call(d3.axisLeft(yScale))
.attr('transform', `translate(${margin.left},0)`)
svg.selectAll('circle')
.data(exponentialData)
.enter()
.append('circle')
// Circles are distributed across x-axis
.attr('cx', d => xScale(d.x))
// Across y-axis as well, and it becomes two dimensional
.attr('cy', d => yScale(d.y))
.attr('r', 10)
.attr('fill', 'SteelBlue')
return svg.node()
}
Insert cell
Insert cell
// Load pokemon_tsne.csv from the tutorial repository on GitHub

pokemonBaseStats = d3.csv('https://raw.githubusercontent.com/leoyuholo/learning-vis-tools/master/tutorial06/lab6/pokemon_tsne.csv', d => _.mapValues(d, s => isNaN(parseFloat(s)) ? s : parseFloat(s)))
Insert cell
/******************************************************************************
* TODO: *
* Try to plot the pokemon dataset! *
* Also try to encode with color, radius, stroke-color, etc.! *
******************************************************************************/

{
const height = 600
const svg = d3.select(DOM.svg(width, height))
const margin = { left: 30, top: 10, right: 10, bottom: 20 }
const xScale = d3.scaleLinear()
.range([margin.left, width - margin.right])
.domain(d3.extent(pokemonBaseStats.map(d => d.base_total)))
svg.append('g')
.call(d3.axisBottom(xScale))
.attr('transform', `translate(0,${height - margin.bottom})`)
const yScale = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain(d3.extent(pokemonBaseStats.map(d => d.capture_rate)))
svg.append('g')
.call(d3.axisLeft(yScale))
.attr('transform', `translate(${margin.left},0)`)
svg.selectAll('circle')
.data(pokemonBaseStats)
.enter()
.append('circle')
.attr('cx', d => xScale(d.base_total))
.attr('cy', d => yScale(d.capture_rate))
.attr('r', 10)
.attr('fill', 'Orange')
return svg.node()
}

/******************************************************************************
* END OF YOUR CODE *
******************************************************************************/
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more