Published
Edited
Jun 22, 2021
3 forks
5 stars
Insert cell
Insert cell
Insert cell
Insert cell
data = d3.csv(
"https://raw.githubusercontent.com/allisonhorst/palmerpenguins/master/inst/extdata/penguins.csv",
d3.autoType // this will automatically convert the CSV text into numbers and dates as appropriate
) // d3.csv() returns a JavaScript Promise, which the notebook cell displays when it resolves
Insert cell
Inputs.table(data)
Insert cell
Insert cell
scatter(data)
Insert cell
function scatter(data, {
width = 640,
height = 400,
margin = 50,
x = (d) => d.bill_length_mm,
y = (d) => d.flipper_length_mm,
} = {}) {
const xScale = d3.scaleLinear()
.domain(d3.extent(data, x))
.range([margin, width - margin])

const yScale = d3.scaleLinear()
.domain(d3.extent(data, y))
.range([height - margin, margin])
var svg = d3.create("svg")
.attr("width", width)
.attr("height", height)

let xg = svg.append("g").attr("transform", `translate(0,${height-margin})`)
let xAxis = d3.axisBottom(xScale) // this sets up the axis component
xAxis(xg) // this renders SVG elements into the "g" container

let yg = svg.append("g").attr("transform", `translate(${margin},0)`)
let yAxis = d3.axisLeft(yScale) // this sets up the axis component
yAxis(yg) // this renders SVG elements into the "g" container

svg.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
.attr("r", 5)

let domElement = svg.node()
return domElement
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
ydim
Insert cell
y = d => d[ydim]
Insert cell
scatter(data, { y: y })
Insert cell
Insert cell
function scatterUpdate(data, {
width = 640,
height = 400,
margin = 50,
x = (d) => d.bill_length_mm,
y = (d) => d.bill_depth_mm,
} = {}) {
const xScale = d3.scaleLinear()
.domain(d3.extent(data, x))
.range([margin, width - margin])

const yScale = d3.scaleLinear()
.domain(d3.extent(data, y))
.range([height - margin, margin])
var svg = d3.create("svg")
.attr("width", width)
.attr("height", height)

let xg = svg.append("g").attr("transform", `translate(0,${height-margin})`)
let xAxis = d3.axisBottom(xScale) // this sets up the axis component
xAxis(xg) // this renders SVG elements into the "g" container

let yg = svg.append("g").attr("transform", `translate(${margin},0)`)
let yAxis = d3.axisLeft(yScale) // this sets up the axis component
yAxis(yg) // this renders SVG elements into the "g" container

// when the function is first called, we render as before. This will add all our circles
svg.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
.attr("r", 5)

let domElement = svg.node()

// This allows us to update the DOM without re-rendering everything
domElement.update = function(updateY) {
// TYPE YOUR CODE HERE
}

return domElement
}
Insert cell
Insert cell
Insert cell
chart = scatterUpdate(data) // the chart variable is a DOM element that gets rendered by this cell
Insert cell
chart.update(y) // we call the update function we attached to it whenever the y dimension is updated
Insert cell
Insert cell
Insert cell
species = new Set(data.map(d => d.species))
Insert cell
colorScale = d3.scaleOrdinal()
.domain(species)
// let's use the 3 colors from the Palmer project graphics
.range([
"rgb(185,100,198)",
"rgb(47,110,116)",
"rgb(240,137,51)"
])
Insert cell
function scatterFullUpdate(data, {
width = 640,
height = 400,
margin = 50,
x = (d) => d.bill_length_mm,
y = (d) => d.bill_depth_mm,
} = {}) {
const xScale = d3.scaleLinear()
.domain(d3.extent(data, x))
.range([margin, width - margin])

const yScale = d3.scaleLinear()
.domain(d3.extent(data, y))
.range([height - margin, margin])
var svg = d3.create("svg")
.attr("width", width)
.attr("height", height)

let xg = svg.append("g").attr("transform", `translate(0,${height-margin})`)
let xAxis = d3.axisBottom(xScale) // this sets up the axis component
xAxis(xg) // this renders SVG elements into the "g" container

let yg = svg.append("g").attr("transform", `translate(${margin},0)`)
let yAxis = d3.axisLeft(yScale) // this sets up the axis component
yAxis(yg) // this renders SVG elements into the "g" container

// when the function is first called, we render as before. This will add all our circles
svg.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
.attr("fill", d => colorScale(d.species))
.attr("r", 5)

let domElement = svg.node()

// This allows us to update the DOM without re-rendering everything
domElement.update = function(updateX, updateY) {
x = updateX
y = updateY
xScale.domain(d3.extent(data, x))
yScale.domain(d3.extent(data, y))
xg.transition().call(xAxis)
yg.transition().call(yAxis)

// because the circles have been added before, this will update them
svg.selectAll("circle")
.data(data)
.join("circle")
.transition() // This line is where all the magic happens
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
.attr("fill", d => colorScale(d.species))
.attr("r", 5)
}

return domElement
}
Insert cell
Insert cell
swatches({ color: colorScale })
Insert cell
fullChart = scatterFullUpdate(data)
Insert cell
Insert cell
Insert cell
fullChart.update(d => d[xdim2], d => d[ydim2])
Insert cell
Insert cell
function scatterMouse(data, {
width = 640,
height = 400,
margin = 50,
x = (d) => d.bill_length_mm,
y = (d) => d.flipper_length_mm,
} = {}) {
const xScale = d3.scaleLinear()
.domain(d3.extent(data, x))
.range([margin, width - margin])

const yScale = d3.scaleLinear()
.domain(d3.extent(data, y))
.range([height - margin, margin])
let svg = d3.create("svg")
.attr("width", width)
.attr("height", height)

svg.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
.attr("fill", d => colorScale(d.species))
.attr("r", 5)
.style("cursor", "pointer")
.on("mouseover", (evt, d) => {
// console.log("over", evt, d)
tooltip.style("display", "")
tooltip.attr("x", evt.layerX + 10)
.attr("y", evt.layerY + 5)
.text(d.species)

// this tells Observable that the data value of this "view" has been updated
svg.property("value", d)
.dispatch("input")
})
.on("mouseout", (evt, d) => {
tooltip.style("display", "none")
})
.on("click", (evt, d) => {
// no matter what environment you are in, you can always console.log :)
console.log("clicked!", evt, d)
})

// we will do the absolute bare minimum tooltip, as simple as you'd think it would be...
// making a good tooltip is almost always a significant investment
let tooltip = svg.append("text")
.style("display", "none")
.style("pointer-events", "none")

let domElement = svg.node()
return domElement
}
Insert cell
viewof value = scatterMouse(data)
Insert cell
value
Insert cell
Insert cell
Insert cell
function scatterBrush(data, {
width = 640,
height = 400,
margin = 50,
x = (d) => d.bill_length_mm,
y = (d) => d.flipper_length_mm,
} = {}) {
const xScale = d3.scaleLinear()
.domain(d3.extent(data, x))
.range([margin, width - margin])

const yScale = d3.scaleLinear()
.domain(d3.extent(data, y))
.range([height - margin, margin])
let svg = d3.create("svg")
.attr("width", width)
.attr("height", height)

let circles = svg.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
.attr("fill", d => colorScale(d.species))
.attr("r", 5)
.style("cursor", "pointer")

let brush = d3.brush()
.on("start brush end", brushed)

svg.call(brush)

function brushed(event) {
// console.log("event", event)
let selection = event.selection
let value = [];
// selection is the top left and bottom right points of the brush
if (selection) {
const [[x0, y0], [x1, y1]] = selection;
value = circles
.attr("r", 2)
// we find all the points that are within the bounding box of the brush using the scaled values
.filter(d => x0 <= xScale(x(d)) && xScale(x(d)) < x1 && y0 <= yScale(y(d)) && yScale(y(d)) < y1)
.attr("r", 6)
// this method returns the array of data from this (filtered) selection
.data();
} else {
circles.attr("r", 3);
}
// update the "dataflow" to be the filtered data
svg.property("value", value).dispatch("input");
}

svg.property("value", []).dispatch("input") // set the initial value to an empty array
let domElement = svg.node()
return domElement
}
Insert cell
viewof brushed = scatterBrush(data)
Insert cell
brushed
Insert cell
Inputs.table(brushed)
Insert cell
Insert cell
Insert cell
Insert cell
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