Published
Edited
Apr 19, 2022
1 fork
Importers
4 stars
Insert cell
Insert cell
Insert cell
<svg width=940 height=200 style="border: 1px solid gray">
<rect />
</svg>
Insert cell
Insert cell
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/scatterplot
function Scatterplot(data, {
x = ([x]) => x, // given d in data, returns the (quantitative) x-value
y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
r = 3, // (fixed) radius of dots, in pixels
width = 640, // outer width, in pixels
height = 400, // outer height, in pixels
xType = d3.scaleLinear, // type of x-scale
xDomain, // [xmin, xmax]
yType = d3.scaleLinear, // type of y-scale
yDomain, // [ymin, ymax]
fill = "none", // fill color for dots
stroke = "currentColor", // stroke color for the dots
strokeWidth = 1.5, // stroke width for dots
} = {}) {

// Compute default domains.
if (xDomain === undefined) xDomain = d3.extent(data, x);
if (yDomain === undefined) yDomain = d3.extent(data, y);

// Construct scales and axes.
const xRange = [20, width - 20];
const yRange = [height - 30, 20];
const xScale = xType(xDomain, xRange);
const yScale = yType(yDomain, yRange);
const xAxis = d3.axisBottom(xScale).ticks(width / 80);
const yAxis = d3.axisLeft(yScale).ticks(height / 50);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

svg.append("g")
.attr("transform", `translate(0,${height - 30})`)
.call(xAxis)
.call(g => g.select(".domain").remove())

svg.append("g")
.attr("transform", `translate(${20},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
svg.append("g")
.attr("fill", fill)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
.attr("r", r);

return svg.node();
}
Insert cell
data = FileAttachment("bandcamp-oneday.csv").csv({ typed: true })
Insert cell
Scatterplot(data.filter(d => d.country == "Netherlands"), {
x: d => d.utc_timestamp,
y: d => d.amount_paid,
xType: d3.scaleTime
})
Insert cell
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/scatterplot
function AnimatedScatterplot(data, {
x = ([x]) => x, // given d in data, returns the (quantitative) x-value
y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
r = 3, // (fixed) radius of dots, in pixels
width = 640, // outer width, in pixels
height = 400, // outer height, in pixels
xType = d3.scaleLinear, // type of x-scale
yType = d3.scaleLinear, // type of y-scale
fill = "none", // fill color for dots
stroke = "currentColor", // stroke color for the dots
strokeWidth = 1.5, // stroke width for dots
} = {}) {

// Construct scales and axes.
const xRange = [20, width - 20];
const yRange = [height - 30, 20];
let xScale = xType(d3.extent(data, x), xRange);
let yScale = yType(d3.extent(data, y), yRange);
const xAxis = d3.axisBottom(xScale).ticks(width / 80);
const yAxis = d3.axisLeft(yScale).ticks(height / 50);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

svg.append("g")
.attr("transform", `translate(0,${height - 30})`)
.call(xAxis)
.call(g => g.select(".domain").remove())

svg.append("g")
.attr("transform", `translate(${20},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
svg.append("g")
.attr("fill", fill)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
.attr("r", r);

return Object.assign(svg.node(), {
update(data) {
xScale = xType(d3.extent(data, x), xRange);
yScale = yType(d3.extent(data, y), yRange);
let selection = svg.selectAll("circle")
.data(data)

selection.enter()
.append("circle")
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
.attr("r", 0)
.attr("fill", fill)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.transition().duration(750)
.attr("r", r)
selection
.transition()
.delay(750)
.duration(750)
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
selection.exit()
.transition().duration(750)
.attr("r", 0)
.remove()
}
})

return svg.node();
}
Insert cell
chart = AnimatedScatterplot(data.filter(d => d.country == "Netherlands"), {
x: d => d.utc_timestamp,
y: d => d.amount_paid,
xType: d3.scaleTime
})
Insert cell
Insert cell
viewof country = Inputs.select(new Set(data.map(d => d.country)), { label: "select a country", value: "Netherlands" })
Insert cell
chart.update(data.filter(d => d.country == country))
Insert cell
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/scatterplot
function BrushScatterplot(data, {
x = ([x]) => x, // given d in data, returns the (quantitative) x-value
y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
r = 3, // (fixed) radius of dots, in pixels
width = 640, // outer width, in pixels
height = 400, // outer height, in pixels
xType = d3.scaleLinear, // type of x-scale
xDomain, // [xmin, xmax]
yType = d3.scaleLinear, // type of y-scale
yDomain, // [ymin, ymax]
fill = "none", // fill color for dots
stroke = "currentColor", // stroke color for the dots
strokeWidth = 1.5, // stroke width for dots
} = {}) {

// Compute default domains.
if (xDomain === undefined) xDomain = d3.extent(data, x);
if (yDomain === undefined) yDomain = d3.extent(data, y);

// Construct scales and axes.
const xRange = [20, width - 20];
const yRange = [height - 30, 20];
const xScale = xType(xDomain, xRange);
const yScale = yType(yDomain, yRange);
const xAxis = d3.axisBottom(xScale).ticks(width / 80);
const yAxis = d3.axisLeft(yScale).ticks(height / 50);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

svg.append("g")
.attr("transform", `translate(0,${height - 30})`)
.call(xAxis)
.call(g => g.select(".domain").remove())

svg.append("g")
.attr("transform", `translate(${20},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
let circles = svg.append("g")
.attr("fill", fill)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(x(d)))
.attr("cy", d => yScale(y(d)))
.attr("r", r);

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", r);
}
// update the "dataflow" to be the filtered data
svg.property("value", value).dispatch("input");
}

return svg.node();
}
Insert cell
viewof brushed = BrushScatterplot(data.filter(d => d.country == "Netherlands"), {
x: d => d.utc_timestamp,
y: d => d.amount_paid,
xType: d3.scaleTime
})
Insert cell
Inputs.table(brushed)
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