Published
Edited
Jun 5, 2019
1 star
Insert cell
md`# 100 Days of Code Scatterplot Day 20 - 30

## Creating a connected scatterplot`
Insert cell
Insert cell
testdata = [
{day: 1, level: 3, skill:"Set up Observable"},
{day: 2, level: 7, skill:"Load modules"},
{day: 3, level: 7, skill:"Code snippets"},
{day: 4, level: 8, skill:"Assignment"},
{day: 5, level: 9, skill:"Data types"}]
Insert cell
{
const height =200
const width =200;
const svg = d3.select(DOM.svg(height,width))
const xscale=d3.scaleLinear()
.domain([0, 10])
.range([0,width])
const yscale= d3.scaleLinear()
.domain([0,10])
.range([ height, 0]);
// Add X axis
svg.append("g")
.call(d3.axisBottom(xscale))

// Add Y axis
svg.append("g")
.call(d3.axisRight(yscale))
const circlesSelection = svg.selectAll("circle")
circlesSelection.append("circle")
.data(testdata)
.join("circle")
.attr("cx", (d) => d.day)
.attr("r", 5)
.attr("cy", (d) => d.level)
.attr("fill", "blue")
.attr("stroke","black");
return svg.node();

}
Insert cell
Insert cell
{
const height =200
const width =200;
const svg = d3.select(DOM.svg(height,width))
const xscale = d3.scaleLinear()
.domain([0, 10])
.range([20,180])
const yscale = d3.scaleLinear()
.domain([0,10])
.range([ 180, 20]);
// Add X axis
svg.append("g")
.attr("transform", "translate(0,180)")
.call(d3.axisBottom(xscale));

// Add Y axis
svg.append("g")
.attr("transform", "translate(20,0)")
.call(d3.axisLeft(yscale));
const circlesSelection = svg.selectAll("circle")
circlesSelection.append("circle")
.data(testdata)
.join("circle")
.attr("cx", (d) => xscale(d.day))
.attr("r", 5)
.attr("cy", (d) => yscale(d.level))
.attr("fill", "white")
.attr("stroke","blue");
return svg.node();

}
Insert cell
md`First scatterplot!`
Insert cell
d3 = require("d3@5")
Insert cell
Insert cell
Insert cell
height= 400
Insert cell
width= 400
Insert cell
margin = ({top: 30, right: 30, bottom: 30, left: 30})
Insert cell
Insert cell
xscale=d3.scaleLinear()
//.domain([0, 10]) //input values set manually
//.range([0,width]) manual range of the output based on pixel values
.domain([0,d3.extent(testdata, d => d.day)[1]]) // input values derived from the extent othe testdata x values
.range([margin.left, width - margin.right]) // range of the output based on pixel values and margins
Insert cell
xscale(5)
Insert cell
yscale = d3.scaleLinear()
.domain([0,10]) //input values set manually
//.range([ 180, 20]); manual range of the output based on pixels
.domain([0,d3.extent(testdata, d => d.level)[1]]) // input values derived from the extent othe testdata y values
.range([height - margin.bottom, margin.top]) // range of the output based on pixel values and margins
Insert cell
yscale(5)
Insert cell
scatter = {
const svg = d3.select(DOM.svg(height,width))
// Add X axis
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xscale));

// Add Y axis
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yscale));
const circlesSelection = svg.selectAll("circle")
circlesSelection.append("circle")
.data(testdata)
.join("circle")
.attr("cx", (d) => xscale(d.day))
.attr("r", 5)
.attr("cy", (d) => yscale(d.level))
.attr("fill", "white")
.attr("stroke","blue");

return svg.node();

}
Insert cell
Insert cell
Insert cell
linefunction = d3.line()
//.curve(d3.curveLinear)
.x(d => xscale(d.day))
.y(d => yscale(d.level))
Insert cell
scatterandline = {
const svg = d3.select(DOM.svg(height,width))
// Add X axis
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xscale));

// Add Y axis
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yscale));
const circlesSelection = svg.selectAll("circle")
circlesSelection.append("circle")
.data(testdata)
.join("circle")
.attr("cx", (d) => xscale(d.day))
.attr("r", 5)
.attr("cy", (d) => yscale(d.level))
.attr("fill", "white")
.attr("stroke","blue");
const label = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("fill","black");
label.selectAll("g")
.data(testdata)
.join("g")
.attr("transform", d => `translate(${xscale(d.day)-25},${yscale(d.level)+20})`)
.append("text")
.text(d => d.skill);
svg.append("path")
.datum(testdata) // attach one path datum to the DOM so no selection is needed
.attr("d", linefunction)
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
return svg.node();
}
Insert cell
Insert cell
Insert cell
dot`digraph "in(d3)" {
require -> d3
}`
Insert cell
Insert cell
dot`digraph "d3-bar-chart" {
${edges.map(([i, o]) => `${i} -> ${o}`).join("\n")}
}`
Insert cell
dot = require("@observablehq/graphviz@0.2")
Insert cell
edges = [
["d3", "chart"],
["DOM", "chart"],
["width", "chart"],
["height", "chart"],
["data", "chart"],
["x", "chart"],
["y", "chart"],
["xAxis", "chart"],
["yAxis", "chart"],
["require", "data"],
["d3", "x"],
["data", "x"],
["margin", "x"],
["width", "x"],
["d3", "y"],
["data", "y"],
["height", "y"],
["margin", "y"],
["d3", "xAxis"],
["height", "xAxis"],
["margin", "xAxis"],
["x", "xAxis"],
["d3", "yAxis"],
["margin", "yAxis"],
["y", "yAxis"],
["require", "d3"]
]
Insert cell
Insert cell
scatterandlinelabel = {
const svg = d3.select(DOM.svg(height,width))
// Add X axis
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xscale));

// Add Y axis
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yscale));
//Add Y label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", margin.left-20)
.attr("x", 0 - (height / 2))
.style("text-anchor", "middle")
.attr("font-size", 10)
.text("Arbitrary Skill Level");
//Add X label
svg.append("text")
.attr("y", height + margin.top - 33)
.attr("x", (width / 2))
.style("text-anchor", "middle")
.attr("font-size", 10)
.text("Day");
svg.append("text")
.attr("transform", `translate(${(width + margin.left + margin.right)/2},20)`)
.style("text-anchor", "middle")
.style("font-weight", 700)
.text("Learning D3");
const circlesSelection = svg.selectAll("circle")
circlesSelection.append("circle")
.data(testdata)
.join("circle")
.attr("cx", (d) => xscale(d.day))
.attr("r", 5)
.attr("cy", (d) => yscale(d.level))
.attr("fill", "white")
.attr("stroke","blue");
const label = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("fill","black");
label.selectAll("g")
.data(testdata)
.join("g")
.attr("transform", d => `translate(${xscale(d.day)-25},${yscale(d.level)+20})`)
.append("text")
.text(d => d.skill);
svg.append("path")
.datum(testdata) // attach one path datum to the DOM so no selection is needed
.attr("d", linefunction)
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
return svg.node();
}
Insert cell
md`**Day 26**

Now that we have a scatterplot template using test data we can start building the wobbly line connected scatterplot like this one https://observablehq.com/@d3/connected-scatterplot. It will show wobbles ie the comfort level in learning D3 using a completely arbitrary measure, level, against the cumulative learning time in hours.

Now to create new data set.`
Insert cell
skilldata = [
{day: 1, daytext: "Day 1 ", level: 2, time: 1, skill:"Set up Observable"},
{day: 2, daytext: "Day 2 ", level: 8, time: 2, skill:"Load modules"},
{day: 3, daytext: "Day 3 ", level: 14, time: 3, skill:"Code snippets"},
{day: 4, daytext: "Day 4 ", level: 20, time: 4, skill:"Assignment"},
{day: 5, daytext: "Day 5 ",level: 24, time: 6, skill:"Data types"},
{day: 6, daytext: "Day 6 ",level: 28, time: 8, skill:"Classes"},
{day: 7, daytext: "Day 7 ",level: 30, time: 10, skill:"Strings and template literals"},
{day: 8, daytext: "Day 8 ",level: 34, time: 12, skill:"Functions"},
{day: 9, daytext: "Day 9 ",level: 35, time: 14, skill:"Arrays"},
{day: 10, daytext: "Day 10 ",level: 30, time: 18, skill:"Data Join"},
{day: 11, daytext: "Day 11 ",level: 28, time: 22, skill:"Coordinates"},
{day: 12, daytext: "Day 12 ",level: 32, time: 24, skill:"HTML basics"},
{day: 13, daytext: "Day 13 ",level: 35, time: 26, skill:"SVG in HTML"},
{day: 14, daytext: "Day 14 ",level: 37, time: 29, skill:"SVG elements"},
{day: 15, daytext: "Day 15 ",level: 38, time: 32, skill:"Generators"},
{day: 16, daytext: "Day 16 ",level: 40, time: 37, skill:"SVG Static map"},
{day: 17, daytext: "Day 17 ",level: 44, time: 40, skill:"HTML Static map"},
{day: 18, daytext: "Day 18 ",level: 45, time: 42, skill:"Views"},
{day: 19, daytext: "Day 19 ",level: 40, time: 48, skill:"Mutable"},
{day: 20, daytext: "Day 20 ",level: 42, time: 50, skill:"SVG Axes"},
{day: 21, daytext: "Day 21 ",level: 42, time: 52, skill:"SVG DOM"},
{day: 22, daytext: "Day 22 ",level: 44, time: 55, skill:"SVG scale transform"}]
Insert cell
Insert cell
margin2 = ({top: 30, right: 30, bottom: 30, left: 30})
Insert cell
xscale2=d3.scaleLinear()
.domain([0,d3.extent(skilldata, d => d.level)[1]+12]) // input values derived from the extent othe testdata x values. Allow buffer for circle labels
.range([margin2.left, width - margin2.right]) // range of the output based on pixel values and margins
Insert cell
yscale2 = d3.scaleLinear()
.domain([0,d3.extent(skilldata, d => d.time)[1]+5]) // input values derived from the extent othe testdata y values
.range([height - margin2.bottom, margin2.top]) // range of the output based on pixel values and margins
Insert cell
linefunction2 = d3.line()
//.curve(d3.curveLinear)
.x(d => xscale2(d.level))
.y(d => yscale2(d.time))
Insert cell
height2 = 400
Insert cell
width2= 400
Insert cell
scatterandlinelabel2 = {
const svg = d3.select(DOM.svg(height2,width2))
// Add X axis
svg.append("g")
.attr("transform", `translate(0,${height2 - margin2.bottom})`)
.call(d3.axisBottom(xscale2));

// Add Y axis
svg.append("g")
.attr("transform", `translate(${margin2.left}, 0)`)
.call(d3.axisLeft(yscale2));
//Add Y label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", margin2.left -22)
.attr("x", 0 - (height2 / 2))
.style("text-anchor", "middle")
.attr("font-size", 10)
.text("Cumulative Time (hours)");
//Add X label
svg.append("text")
.attr("y", height2 + margin2.top -35)
.attr("x", (width2 / 2))
.style("text-anchor", "middle")
.attr("font-size", 10)
.text("Arbitrary Comfort Level");
//Add Title
svg.append("text")
.attr("transform", `translate(${(width2 + margin2.left + margin2.right)/2},20)`)
.style("text-anchor", "middle")
.style("font-weight", 700)
.text("Learning D3 - Daily Progress");
//Add Circles
const circlesSelection = svg.selectAll("circle")
circlesSelection.append("circle")
.data(skilldata)
.join("circle")
.attr("cx", (d) => xscale2(d.level))
.attr("r", 5)
.attr("cy", (d) => yscale2(d.time))
.attr("fill", "white")
.attr("stroke","blue");
const label = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("fill","black");
label.selectAll("g")
.data(skilldata)
.join("g")
.attr("transform", d => `translate(${xscale2(d.level)+10},${yscale2(d.time)+5})`)
.append("text")
.text(d => d.skill);
svg.append("path")
.datum(skilldata) // attach one path datum to the DOM so no selection is needed
.attr("d", linefunction2)
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
return svg.node();
}
Insert cell
md`**Day 28**

Formatting plot colours to look a bit more like this https://observablehq.com/@d3/connected-scatterplot`
Insert cell
function length(path) {
return d3.create("svg:path").attr("d", path).node().getTotalLength();
}
Insert cell
scatterandlinelabel3 = {
const svg = d3.select(DOM.svg(height2,width2))
const l = length(linefunction(skilldata))
// Add X axis
svg.append("g")
.attr("transform", `translate(0,${height2 - margin2.bottom})`)
.call(d3.axisBottom(xscale2));

// Add Y axis
svg.append("g")
.attr("transform", `translate(${margin2.left}, 0)`)
.call(d3.axisLeft(yscale2));
//Add Y label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", margin2.left -22)
.attr("x", 0 - (height2 / 2))
.style("text-anchor", "middle")
.attr("font-size", 10)
.text("Cumulative Time (hours)");
//Add X label
svg.append("text")
.attr("y", height2 + margin2.top -35)
.attr("x", (width2 / 2))
.style("text-anchor", "middle")
.attr("font-size", 10)
.text("Arbitrary Comfort Level");
//Add Title
svg.append("text")
.attr("transform", `translate(${(width2 + margin2.left + margin2.right)/2},20)`)
.style("text-anchor", "middle")
.style("font-weight", 700)
.text("Learning D3 - Daily Progress");
//Add Circles
const circlesSelection = svg.selectAll("circle")
circlesSelection.append("circle")
.data(skilldata)
.join("circle")
.attr("cx", (d) => xscale2(d.level))
.attr("r", 5)
.attr("cy", (d) => yscale2(d.time))
.attr("fill", "black")
.attr("stroke", "black")
.attr("stroke-width", 2);
const label = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("fill","black");
label.selectAll("g")
.data(skilldata)
.join("g")
.attr("transform", d => `translate(${xscale2(d.level)+10},${yscale2(d.time)+5})`)
.append("text")
.text(d => d.skill);
svg.append("path")
.datum(skilldata) // attach one path datum to the DOM so no selection is needed
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 2.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", linefunction2);
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
scatterandlinelabel4 = {
replay;
const svg = d3.select(DOM.svg(height2,width2))
const l = length(linefunction2(skilldata))
// Add X axis
svg.append("g")
.attr("transform", `translate(0,${height2 - margin2.bottom})`)
.call(d3.axisBottom(xscale2));

// Add Y axis
svg.append("g")
.attr("transform", `translate(${margin2.left}, 0)`)
.call(d3.axisLeft(yscale2));
//Add Y label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", margin2.left -22)
.attr("x", 0 - (height2 / 2))
.style("text-anchor", "middle")
.attr("font-size", 10)
.text("Cumulative Time (hours)");
//Add X label
svg.append("text")
.attr("y", height2 + margin2.top -35)
.attr("x", (width2 / 2))
.style("text-anchor", "middle")
.attr("font-size", 10)
.text("Arbitrary Comfort Level");
//Add Title
svg.append("text")
.attr("transform", `translate(${(width2 + margin2.left + margin2.right)/2},20)`)
.style("text-anchor", "middle")
.style("font-weight", 700)
.text("Learning D3 - Daily Progress");
//Add Circles
const circlesSelection = svg.selectAll("circle")
circlesSelection.append("circle")
.data(skilldata)
.join("circle")
.attr("cx", (d) => xscale2(d.level))
.attr("r", 5)
.attr("cy", (d) => yscale2(d.time))
.attr("fill", "black")
.attr("stroke", "black")
.attr("stroke-width", 2);
const label = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("fill","black");
label.selectAll("g")
.data(skilldata)
.join("g")
.attr("transform", d => `translate(${xscale2(d.level)+10},${yscale2(d.time)+5})`)
.append("text")
.text(d => d.skill);
svg.append("path")
.datum(skilldata) // attach one path datum to the DOM so no selection is needed
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 2.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-dasharray", `0,${l}`)
.attr("d", linefunction2)
.transition()
.duration(5000)
.ease(d3.easeLinear)
.attr("stroke-dasharray", `${l},${l}`);
label.transition()
.delay((d, i) => length(linefunction2(skilldata.slice(0, i + 1))) / l * (5000 - 125))
.attr("opacity", 1);
return svg.node();
}
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