Public
Edited
Sep 1, 2024
Insert cell
md`# Revisiting 4.2.2 Marks and Channels`
Insert cell
md `## Sanjiv Narayan; July 22, 2024, Familiarizing with D3`
Insert cell
md `## Position`
Insert cell
//first version
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const iwidth = width - margin.left - margin.right;

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const x = d3
.scaleLinear()
.domain(d3.extent(data, (d) => d[quantAttrib]))
.range([0, iwidth])
.nice();

g.append("g").call(d3.axisTop(x));
g.selectAll(".row")
.data(data)
.join("ellipse")
.attr("rx", 5)
.attr("ry", 10)
.attr("cx", (d) => x(d[quantAttrib]))
.attr("cy", 10)
.attr("fill", "steelblue");

return svg.node();
}
Insert cell
md`## Position on unaligned scale`
Insert cell
//second version - sample of data only
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const iwidth = width - margin.left - margin.right;

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const sampleData = data.slice(0, 5);

const x = d3
.scaleLinear()
.domain(d3.extent(sampleData, (d) => d[quantAttrib]))
.range([0, iwidth / 6])
.nice();

g.selectAll(".row")
.data(sampleData)
.join("g")
.attr(
"transform",
(d, i) => `translate(${Math.random() * width * 0.7}, ${i * 20})`
)
.call(d3.axisTop(x).ticks(3))
.append("circle")
.attr("r", 5)
.attr("cx", (d) => x(d[quantAttrib]))
.attr("cy", 5)
.attr("fill", "steelblue");

return svg.node();
}
Insert cell
md `# Length`
Insert cell
//first version
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const iwidth = width - margin.left - margin.right;

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const x = d3
.scaleLinear()
.domain([0, d3.max(data, (d) => d[quantAttrib])])
.range([0, iwidth])
.nice();

g.append("g").call(d3.axisTop(x));

g.selectAll(".row")
.data(data)
.join("rect")
.attr("height", 5)
.attr("width", (d) => x(d[quantAttrib]))
.attr("y", (d, i) => i*6)
.attr("fill", "steelblue");

return svg.node();
}
Insert cell
md `# tilt`
Insert cell
//first version
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height*2]);

const iwidth = width - margin.left - margin.right;

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const angle = d3
.scaleLinear()
.domain(d3.extent(data, (d) => d[quantAttrib]))
.range([0, 360]);

g.selectAll(".row")
.data(data)
.join("rect")
.attr("height", 1)
.attr("width", 50)
.attr("y", (height * 2) / 3)
.attr("x", width / 2)
.attr(
"transform",
(d) =>
`rotate (
${angle(d[quantAttrib])},
${width / 2},
${(height * 2) / 3})`
)
.attr("fill", "red");

return svg.node();
}
Insert cell
md `# Area`
Insert cell
//first version
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const iwidth = width - margin.left - margin.right;

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const x = d3
.scaleLinear()
.domain(d3.extent(data, (d) => d[quantAttrib]))
.range([0, iwidth])
.nice();

const rad = d3
.scaleSqrt()
.domain([0, d3.max(data, (d) => d[quantAttrib])])
.range([0, 20])
.nice();

g.append("g").call(d3.axisTop(x));

g.selectAll(".row")
.data(data)
.join("circle")
.attr("r", (d) => rad(d[quantAttrib]))
.attr("cx", (d) => x(d[quantAttrib]))
.attr("cy", height / 2)
.attr("fill", "steelblue");

return svg.node();
}
Insert cell
md `# Colors`
Insert cell
//first version
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const iwidth = width - margin.left - margin.right;

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const x = d3
.scaleLinear()
.domain(d3.extent(data, (d) => d[quantAttrib]))
.range([0, iwidth])
.nice();

const col = d3
.scaleSequential(d3.interpolateGreens)
.domain(d3.extent(data, (d) => d[quantAttrib]));

g.append("g").call(d3.axisTop(x));

g.selectAll(".row")
.data(data)
.join("circle")
.attr("r", 15)
.attr("cx", (d) => x(d[quantAttrib]))
.attr("cy", height / 2)
.style("fill", d => col(d[quantAttrib]));

return svg.node();
}
Insert cell
md `# Curvature`
Insert cell
//first version
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height*2]);

const iwidth = width - margin.left - margin.right;

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const x = d3
.scaleLinear()
.domain(d3.extent(data, (d) => d[quantAttrib]))
.range([0, iwidth])
.nice();

g.append("g").call(d3.axisTop(x));

g.selectAll(".row")
.data(data)
.join("path")
.attr("class", "row")
.attr("d", (d) => {
const x0 = x(d[quantAttrib]),
y0 = 20,
y1 = y0 + 50;
return `M ${x0} ${y0} L ${x0} ${y1}`;
});

return svg.node();
}
Insert cell
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
const iwidth = width - margin.right - margin.left;

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const x = d3
.scaleLinear()
.domain(d3.extent(data, d => d[quantAttrib]))
.range([0, iwidth])
.nice();

const curv = d3
.scaleLinear()
.domain([0, d3.max(data, d => d[quantAttrib])])
.range([0, 50])
.nice();

g.append("g").call(d3.axisTop(x));

g.selectAll(".row")
.data(data)
.join("path")
.attr("class", "row")
.attr("d", d => {
const x0 = x(d[quantAttrib]),
y0 = 20,
y1 = y0 + 30;
return `M ${x0} ${y0} Q ${x0 + curv(d[quantAttrib])} ${y0 +
(y1 - y0) / 2} ${x0} ${y1}`;
});

return svg.node();
}
Insert cell
md `# Identity Channels`
Insert cell
catAttrib = "Species"
Insert cell
data.map(d => d[catAttrib])
Insert cell
new Set(data.map(d => d[catAttrib]))
Insert cell
new Set(data.map(d => d[catAttrib])).values()
Insert cell
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
const iwidth = width - margin.right - margin.left;

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const x = d3
.scaleBand()
.domain(new Set(data.map(d => d[catAttrib])).values())
.range([0, iwidth]);

g.append("g").call(d3.axisTop(x));

g.selectAll(".row")
.data(data)
.join("circle")
.attr("r", 5)
.attr("cx", d => x(d[catAttrib]) + x.bandwidth() / 2)
.attr("cy", () => (Math.random() * height) / 2);

return svg.node();
}
Insert cell
md `# Color Hue`
Insert cell
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
const iwidth = width - margin.right - margin.left;

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const x = d3
.scaleLinear()
.domain(d3.extent(data, (d) => d[quantAttrib]))
.range([0, iwidth])
.nice();

const color = d3
.scaleOrdinal(d3.schemePastel2)
.domain(new Set(data.map((d) => d[catAttrib])).values());

g.append("g").call(d3.axisTop(x));

g.selectAll(".row")
.data(data)
.join("circle")
.attr("r", 5)
.attr("cx", (d) => x(d[quantAttrib]))
.attr("cy", height / 2)
.style("fill", (d) => color(d[catAttrib]));

return svg.node();
}
Insert cell
data = (await vegaDatasets["penguins.json"]()).filter(
(d) => d[quantAttrib] !== null
)
Insert cell
quantAttrib = 'Body Mass (g)'
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
vegaDatasets = require("vega-datasets")
Insert cell
d3 = require("d3@6")
Insert cell
height = 100;
Insert cell
margin = ({ left: 20, right: 20, top: 20, bottom: 20 })
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