Public
Edited
Sep 30, 2022
1 fork
75 stars
Insert cell
Insert cell
plot({
width,
height: 300,
margin: {
top: 10,
bottom: 30,
left: 40,
right: 20,
},
x: ({width}) => d3.scaleBand(alphabet.map(d => d.letter), [0, width]).padding(0.1),
y: ({height}) => d3.scaleLinear([0, d3.max(alphabet.map(d => d.frequency))], [height, 0]),
color: () => d3.scaleSequential(d3.interpolateBlues).domain(d3.extent(alphabet.map(d => d.frequency))),
marks: [
barY(alphabet, {x: "letter", y: "frequency", color: "frequency" }),
],
})
Insert cell
plot({
width,
height: 300,
margin: {
top: 10,
bottom: 30,
left: 40,
right: 20,
},
x: ({width}) => d3.scaleLinear([0, d3.max(cars.map(d => d["economy (mpg)"]))], [0, width]),
y: ({height}) => d3.scaleLinear([0, d3.max(cars.map(d => d["power (hp)"]))], [height, 0]),
marks: [
dot(cars, {x: "economy (mpg)", y: "power (hp)"}),
],
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
svg({ width, height: 100 }, [
g({ x: 0, y: 20 }, rect([{x: 0, width: 10, y: 0, height: 10, fill: 'cornflowerblue' }, {x: 20, width: 10, y: 0, height: 10}])),
circle([{cx: 20, cy: 50, r: 10, fill: 'firebrick' }]),
])
Insert cell
Insert cell
Insert cell
linearDomain = [0, d3.max(alphabet.map(d => +d.frequency))]
Insert cell
linearRange = [100, 0]
Insert cell
linearScale = d3.scaleLinear(linearDomain, linearRange)
Insert cell
({
abstractValue: +alphabet[0].frequency,
screenValue: linearScale(alphabet[0].frequency),
})
Insert cell
Insert cell
ordinalDomain = alphabet.map(d => d.letter)
Insert cell
ordinalRange = [0, width]
Insert cell
ordinalScale = d3.scaleBand(ordinalDomain, ordinalRange)
Insert cell
({
abstractValue: alphabet[0].letter,
screenValue: ordinalScale(alphabet[0].letter),
})
Insert cell
Insert cell
ordinalScale(alphabet[1].letter) - ordinalScale(alphabet[0].letter)
Insert cell
ordinalScale(alphabet[2].letter) - ordinalScale(alphabet[1].letter)
Insert cell
Insert cell
Insert cell
abstractSpace = ({
X: alphabet.map(d => d.letter),
Y: alphabet.map(d => d.frequency),
})
Insert cell
svg({ width, height: 100 }, [
rect(d3.range(alphabet.length).map(i => ({
x: ordinalScale(abstractSpace.X[i]),
y: linearScale(abstractSpace.Y[i]),
width: ordinalScale.bandwidth(), // bar width as determined by ordinal scale
height: linearScale(0) - linearScale(abstractSpace.Y[i]), // by default, SVG starts in the upper-left corner, but we want to render from the bottom-left
fill: 'cornflowerblue'
}))),
])
Insert cell
Insert cell
barY = (data, { x, y, color }) => ({
data,
encodings: { x, y, color },
scale: (channels, scales, dimensions) => {
const { X, Y, COLOR } = channels;
const { xScale, yScale, colorScale } = scales;
const indices = d3.range(X.length);
return indices.map(i => ({
x: xScale(X[i]),
// y: dimensions.height - yScale(Y[i]),
y: yScale(Y[i]),
width: xScale.bandwidth(),
// height: yScale(Y[i]),
height: yScale(0) - yScale(Y[i]),
fill: colorScale(COLOR[i])
}))
},
render: rect,
})
Insert cell
dot = (data, { x, y, color }) => ({
data,
encodings: { x, y, color },
scale: (channels, scales, dimensions) => {
const { X, Y, COLOR } = channels;
const { xScale, yScale, colorScale } = scales;
const indices = d3.range(X.length);
return indices.map(i => ({
cx: xScale(X[i]),
cy: yScale(Y[i]),
r: 3,
fill: colorScale ? colorScale(COLOR[i]) : 'black'
}))
},
render: circle,
})
Insert cell
Insert cell
plotMark = (mark, scales, dimensions) => {
const {data, encodings, scale, render} = mark;
// 1. Use the encoding functions to extract channels from the data
const channels = extractChannels(data, encodings);
// 2. Reify the scales by feeding them the dimensions of the container.
const reifiedScales = reifyScales(scales, dimensions);
// 3. Call the mark's scale function get the screen space coordinates.
const scaledData = scale(channels, reifiedScales, dimensions);
// 4. Call the mark's render function on the scaled data.
return render(scaledData);
}
Insert cell
extractChannels = (data, encodings) => {
let channels = {};
for (const [c, encoding] of Object.entries(encodings)) {
channels[c.toUpperCase()] = data.map(d => d[encoding]);
}
return channels;
}
Insert cell
extractChannels(alphabet, {x: "letter", y: "frequency"})
Insert cell
reifyScales = (scales, dimensions) => {
let reifiedScales = {};
for (const [name, scale] of Object.entries(scales)) {
reifiedScales[name] = scale(dimensions);
}
return reifiedScales;
}
Insert cell
Insert cell
svg({width, height: 300}, [
plotMark(
barY(alphabet, {x: "letter", y: "frequency", color: "frequency" }),
{
xScale: ({width}) => d3.scaleBand(alphabet.map(d => d.letter), [0, width]).padding(0.1),
yScale: ({height}) => d3.scaleLinear([0, d3.max(alphabet.map(d => d.frequency))], [height, 0]),
// colorScale: () => () => 'black',
colorScale: () => d3.scaleSequential(d3.interpolateBlues).domain(d3.extent(alphabet. map(d => d.frequency))),
},
{width: width, height: 300}),
])
Insert cell
Insert cell
xAxis = (xScale) => d3.create("svg:g")
.call(d3.axisBottom(xScale))
.node()
Insert cell
svg({width, height: 50}, xAxis(d3.scaleBand(alphabet.map(d => d.letter), [0, width]).padding(0.1)))
Insert cell
yAxis = (yScale) => d3.create("svg:g")
.call(d3.axisLeft(yScale))
.node()
Insert cell
svg({width, height: 300},
g({x: 20},
yAxis(
d3.scaleLinear(d3.extent(alphabet.map(d => d.frequency)), [300, 0])
)
)
)
Insert cell
import {Legend, Swatches} from "@d3/color-legend"
Insert cell
Legend(d3.scaleSequential(d3.interpolateBlues).domain(d3.extent(alphabet.map(d => d.frequency))))
Insert cell
Insert cell
plot = (options) => {
let { width, height, margin, marks, ...scales } = options;
// compute dimensions from outer width, height, and margins
const dimensions = { width: width-margin.left-margin.right, height: height-margin.bottom-margin.top };
// reify scales
scales = reifyScales(scales, dimensions);
// append "Scale" to scale names
scales = renameScales(scales);
const { xScale, yScale, colorScale } = scales;
return svg({ width, height },
g({x: margin.left, y: margin.top}, [
// here are our axes and legends!
g({y: dimensions.height}, xAxis(xScale)),
g({}, yAxis(yScale)),
...colorScale ? [g({x: dimensions.width-350}, Legend(colorScale))] : [],
// and here are our marks! (we use a modified version of `plotMark` that assumes the scales have already been reified)
...marks.map(m =>
/* TODO: not sure why this is off by half a pixel... */
g({x: 0, y: -0.5}, plotMarkReified(m, scales, dimensions)))
]));
}
Insert cell
plotMarkReified = (mark, scales, dimensions) => {
const {data, encodings, scale, render} = mark;
const channels = extractChannels(data, encodings);
const scaledData = scale(channels, scales, dimensions);
return render(scaledData);
}
Insert cell
renameScales = (scales) => {
let renamedScales = {};
for (const [name, scale] of Object.entries(scales)) {
renamedScales[name + "Scale"] = scale
}
return renamedScales;
}
Insert cell
Insert cell
plot({
width,
height: 300,
margin: {
top: 10,
bottom: 30,
left: 40,
right: 20,
},
x: ({width}) => d3.scaleBand(alphabet.map(d => d.letter), [0, width]).padding(0.1),
y: ({height}) => d3.scaleLinear([0, d3.max(alphabet.map(d => d.frequency))], [height, 0]),
color: () => d3.scaleSequential(d3.interpolateBlues).domain(d3.extent(alphabet.map(d => d.frequency))),
marks: [
barY(alphabet, {x: "letter", y: "frequency", color: "frequency" }),
],
})
Insert cell
Insert cell
plot({
width,
height: 300,
margin: {
top: 10,
bottom: 30,
left: 40,
right: 20,
},
x: ({width}) => d3.scaleBand(alphabet.map(d => d.letter), [0, width]).padding(0.1),
y: ({height}) => d3.scaleLinear([0, d3.max(alphabet.map(d => d.frequency))], [height, 0]),
color: () => d3.scaleSequential(d3.interpolateBlues).domain(d3.extent(alphabet.map(d => d.frequency))),
marks: [
dot(alphabet, {x: "letter", y: "frequency", color: "frequency" }),
],
})
Insert cell
Insert cell
// uh oh! the points aren't centered...
plot({
width,
height: 300,
margin: {
top: 10,
bottom: 30,
left: 40,
right: 20,
},
x: ({width}) => d3.scaleBand(alphabet.map(d => d.letter), [0, width]).padding(0.1),
y: ({height}) => d3.scaleLinear([0, d3.max(alphabet.map(d => d.frequency))], [height, 0]),
color: () => d3.scaleSequential(d3.interpolateBlues).domain(d3.extent(alphabet.map(d => d.frequency))),
marks: [
barY(alphabet, {x: "letter", y: "frequency", color: "frequency" }),
dot(alphabet, {x: "letter", y: "frequency" }),
],
})
Insert cell
Insert cell
plot({
width,
height: 300,
margin: {
top: 10,
bottom: 30,
left: 40,
right: 20,
},
x: ({width}) => d3.scaleLinear([0, d3.max(cars.map(d => d["economy (mpg)"]))], [0, width]),
y: ({height}) => d3.scaleLinear([0, d3.max(cars.map(d => d["power (hp)"]))], [height, 0]),
marks: [
dot(cars, {x: "economy (mpg)", y: "power (hp)"}),
],
})
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more