Published
Edited
Jun 27, 2019
1 star
Insert cell
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
skilldata = [
{day: 1, daytext: "Day 1 ", level: 2, time: 1, tool:"Notebook", skill:"Set up Observable"},
{day: 2, daytext: "Day 2 ", level: 8, time: 2, tool:"Notebook",skill:"Load modules"},
{day: 3, daytext: "Day 3 ", level: 14, time: 3, tool:"JS",skill:"Code snippets"},
{day: 4, daytext: "Day 4 ", level: 20, time: 4, tool:"JS",skill:"Assignment"},
{day: 5, daytext: "Day 5 ",level: 24, time: 6, tool:"JS",skill:"Data types"},
{day: 6, daytext: "Day 6 ",level: 28, time: 8, tool:"JS",skill:"Classes"},
{day: 7, daytext: "Day 7 ",level: 30, time: 10, tool:"JS",skill:"Strings and template literals"},
{day: 8, daytext: "Day 8 ",level: 34, time: 12, tool:"JS",skill:"Functions"},
{day: 9, daytext: "Day 9 ",level: 35, time: 14, tool:"JS",skill:"Arrays"},
{day: 10, daytext: "Day 10 ",level: 30, time: 18, tool:"JS", skill:"Data Join"},
{day: 11, daytext: "Day 11 ",level: 28, time: 22, tool:"SVG",skill:"Coordinates"},
{day: 12, daytext: "Day 12 ",level: 32, time: 24, tool:"HTML",skill:"HTML basics"},
{day: 13, daytext: "Day 13 ",level: 35, time: 26, tool:"SVG & HTML", skill:"SVG in HTML"},
{day: 14, daytext: "Day 14 ",level: 37, time: 29, tool:"SVG",skill:"SVG elements"},
{day: 15, daytext: "Day 15 ",level: 38, time: 32, tool:"JS",skill:"Generators"},
{day: 16, daytext: "Day 16 ",level: 40, time: 37, tool:"SVG",skill:"SVG Static map"},
{day: 17, daytext: "Day 17 ",level: 44, time: 40, tool:"HTML",skill:"HTML Static map"},
{day: 18, daytext: "Day 18 ",level: 45, time: 42, tool:"JS",skill:"Views"},
{day: 19, daytext: "Day 19 ",level: 40, time: 48, tool:"JS",skill:"Mutable"},
{day: 20, daytext: "Day 20 ",level: 42, time: 50, tool:"SVG",skill:"SVG Axes"},
{day: 21, daytext: "Day 21 ",level: 42, time: 52, tool:"SVG",skill:"SVG DOM"},
{day: 22, daytext: "Day 22 ",level: 44, time: 55, tool:"SVG",skill:"SVG scale transform"},
{day: 23, daytext: "Day 23 ",level: 46, time: 57, tool:"SVG",skill:"SVG annotation"},
{day: 24, daytext: "Day 24 ",level: 48, time: 60, tool:"JS",skill:"Graphviz library"},
{day: 25, daytext: "Day 25 ",level: 50, time: 61, tool:"SVG",skill:"SVG labels"},
{day: 26, daytext: "Day 26 ",level: 51, time: 62, tool:"JS",skill:"Data"},
{day: 27, daytext: "Day 27 ",level: 55, time: 63, tool:"SVG",skill:"SVG scatterplot"},
{day: 28, daytext: "Day 28 ",level: 55, time: 63, tool:"CSS",skill:"CSS colours"},
{day: 29, daytext: "Day 29 ",level: 57, time: 65, tool:"JS",skill:"Transitions"},
{day: 30, daytext: "Day 30 ",level: 58, time: 66, tool:"SVG",skill:"SVG annotation"},
{day: 31, daytext: "Day 31 ",level: 60, time: 68, tool:"JS & HTML",skill:"Mapbox JS library"},
{day: 32, daytext: "Day 32 ",level: 62, time: 70, tool:"JS & HTML",skill:"Mapbox JS library"},
{day: 33, daytext: "Day 33 ",level: 64, time: 72, tool:"JS & HTML",skill:"Mapbox JS library"},
{day: 34, daytext: "Day 34 ",level: 66, time: 73, tool:"JS & HTML",skill:"Mapbox JS library"},
{day: 35, daytext: "Day 35 ",level: 70, time: 75, tool:"JS & HTML",skill:"Mapbox JS library"},
{day: 36, daytext: "Day 36 ",level: 72, time: 77, tool:"JS & HTML",skill:"Leaflet JS library"},
{day: 37, daytext: "Day 37 ",level: 74, time: 79, tool:"JS & HTML",skill:"Leaflet JS library"},
{day: 38, daytext: "Day 38 ",level: 78, time: 81, tool:"JS & HTML",skill:"Leaflet JS library"},
{day: 39, daytext: "Day 39 ",level: 80, time: 83, tool:"JS & HTML",skill:"Leaflet JS library"}]
Insert cell
groupskilldata = [
{group:"Notebook",count:2},
{group:"JS",count:14},
{group:"SVG",count:10},
{group:"CSS",count:1},
{group:"HTML",count:2},
{group:"SVG & HTML",count:1},
{group:"JS & HTML",count:9}]
Insert cell
Insert cell
// Create a new array map with the keys as the tool then create a new array with just the keys
groups = d3.map(skilldata, function(d){return(d.tool)}).keys()
Insert cell
xscale= d3.scaleBand()
.domain(groups)
.range([0, width-20])
.padding([0.5])
Insert cell
yscale = d3.scaleLinear()
.domain([0,d3.extent(groupskilldata, d => d.count)[1]+2]) // input values derived from the extent on the data y values
.range([height - margin.bottom, margin.top]); // range of the output based on pixel values and margins
Insert cell
subgroups =
["count"]
Insert cell
Insert cell
barchart = {
const svg = d3.select(DOM.svg(height,width))
// Add X axis
svg.append("g")
.attr("transform", `translate(30,${height - margin.bottom})`)
.call(d3.axisBottom(xscale).tickSizeOuter(1));


// Bars
svg.selectAll("mybar")
.data(groupskilldata)
.enter()
.append("rect")
.attr("x", function(d) { return xscale(d.group)+30; })
.attr("y", function(d) { return yscale(d.count)-30; })
.attr("width", xscale.bandwidth())
.attr("height", function(d) { return height- yscale(d.count); })
.attr("fill", "#69b3a2");
//Add Title
svg.append("text")
.attr("transform", `translate(${(width + margin.left + margin.right)/2},20)`)
.style("text-anchor", "middle")
.style("font-weight", 700)
.text("Learning D3 Tools - First 39 Days");
return svg.node();

}
Insert cell
Insert cell
width2 = 460 - margin.left - margin.right
Insert cell
height2 = 400 - margin.top - margin.bottom
Insert cell
xscale2= d3.scaleBand()
.domain(groups)
.range([0, width2])
.padding([0.5])
Insert cell
yscale2 = d3.scaleLinear()
.domain([0,d3.extent(groupskilldata, d => d.count)[1]+1]) // input values derived from the extent on the data y values
.range([height2 ,0]); // range of the output based on pixel values and margins

Insert cell
barchart2 = {
const svg = d3.select(DOM.svg(height2 + margin.top + margin.bottom,
width2 + margin.left + margin.right))
// Add X axis
svg.append("g")
.attr("transform", `translate(${margin.left},${height2})`)
.call(d3.axisBottom(xscale2).tickSizeOuter(1))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");

// Add Y axis
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yscale2));

// Bars
svg.selectAll("mybar")
.data(groupskilldata)
.enter()
.append("rect")
.attr("x", function(d) { return xscale2(d.group)+margin.left; })
.attr("y", function(d) { return yscale2(d.count); })
.attr("width", xscale2.bandwidth())
.attr("height", function(d) { return height2- yscale2(d.count); })
.attr("fill", "#69b3a2");
//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("Days count");
//Add Title
svg.append("text")
.attr("transform", `translate(${(width2 + margin.left + margin.right)/2},15)`)
.style("text-anchor", "middle")
.style("font-weight", 700)
.text("First 39 Days Learning D3 Tools Count");
return svg.node();

}
Insert cell
Insert cell
viewof newcolour = html`<input type="color" value="#69b3a2">`
Insert cell
newcolour
Insert cell
barchart3 = {
const svg = d3.select(DOM.svg(height2 + margin.top + margin.bottom,
width2 + margin.left + margin.right))

// Add X axis
svg.append("g")
.attr("transform", `translate(${margin.left},${height2})`)
.call(d3.axisBottom(xscale2).tickSizeOuter(1))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");

// Add Y axis
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yscale2));

// Bars
svg.selectAll("mybar")
.data(groupskilldata)
.enter()
.append("rect")
.attr("x", function(d) { return xscale2(d.group)+margin.left; })
.attr("y", function(d) { return yscale2(d.count); })
.attr("width", xscale2.bandwidth())
.attr("height", function(d) { return height2- yscale2(d.count); })
.attr("fill", newcolour);
//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("Days count");
//Add Title
svg.append("text")
.attr("transform", `translate(${(width2 + margin.left + margin.right)/2},15)`)
.style("text-anchor", "middle")
.style("font-weight", 700)
.text("First 39 Days Learning D3 Tools Count");
return svg.node();

}
Insert cell
Insert cell
// Take a look at the groupskilldata again
groupskilldata
Insert cell
// Create a first circle packing template
firstcirclepack = {
const svg = d3.select(DOM.svg(height2 + margin.top + margin.bottom,
width2 + margin.left + margin.right))
// Initialize the circle: all located at the center of the svg area.
const node= svg.append("g")
.selectAll("circle")
.data(groupskilldata)
.enter()
.append("circle")
.attr("r", 25)
.attr("cx", width / 2)
.attr("cy", height / 2)
.style("fill", "#69b3a2")
.style("fill-opacity", 0.3)
.attr("stroke", "#69a2b2")
.style("stroke-width", 4)
// Features of the forces applied to the nodes:
const simulation = d3.forceSimulation()
.force("center", d3.forceCenter().x(width / 2).y(height / 2)) // Attraction to the center of the svg area
.force("charge", d3.forceManyBody().strength(0.1)) // Nodes are attracted one each other of value is > 0
.force("collide", d3.forceCollide().strength(.4).radius(30).iterations(1)); // Force that avoids circle overlapping

// Apply these forces to the nodes and update their positions.
simulation.nodes(groupskilldata)
.on("tick", function(d){
node
.attr("cx", function(d){ return d.x; })
.attr("cy", function(d){ return d.y; })
});
return svg.node();

}
Insert cell
// Add colour and size based on the data to a version version of circle packing with dragging
circlepack = {
const svg = d3.select(DOM.svg(height2 + margin.top + margin.bottom,
width2 + margin.left + margin.right))
//Now add a colour palette
const palette = d3.scaleOrdinal(d3.schemeSet1);
// Initialize the circle: all located at the center of the svg area. Then add dragging functions.
const node= svg.append("g")
.selectAll("circle")
.data(groupskilldata)
.enter()
.append("circle")
.attr("cx", width / 2)
.attr("cy", height / 2)
.style("fill", d =>palette(d.group))
.attr("stroke", "#69a2b2")
.style("stroke-width", 1)
// Use SVG circle attribute r to define the radius of the circle
.attr("r",d => d.count*5)
.call(d3.drag() // call specific function when circle is dragged
.on("start", function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(.03).restart();
d.fx = d.x;
d.fy = d.y;
})
.on("drag", function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
})
.on("end", function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(.03);
d.fx = null;
d.fy = null;
}));
// Features of the forces applied to the nodes:
const simulation = d3.forceSimulation()
.force("center", d3.forceCenter().x(width / 2).y(height / 2)) // Attraction to the center of the svg area
.force("charge", d3.forceManyBody().strength(500)) // Nodes are attracted one each other of value is > 0
.force("collide", d3.forceCollide().strength(1).radius(0.1).iterations(1)) // Force that avoids circle overlapping

// Apply these forces to the nodes and update their positions.
simulation.nodes(groupskilldata)
.on("tick", function(d){
node
.attr("cx", function(d){ return d.x; })
.attr("cy", function(d){ return d.y; })
});
return svg.node();

}
Insert cell
Insert cell
// Create circle packing without the dragging
circlepacklabels = {
const svg = d3.select(DOM.svg(height2 + margin.top + margin.bottom,
width2 + margin.left + margin.right))
const g =svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);
const palette = d3.scaleOrdinal(d3.schemeSet1);
svg.selectAll("circle")
.data(groupskilldata)
.enter()
.append("circle")
// The dataset is updated with x and y values based on the 'end' as dragged above so we can use these for the circle coordinate placement
.attr("cx", d => d.x)
.attr("cy",d => d.y)
.style("fill", d =>palette(d.group))
.attr("stroke", "grey")
.style("stroke-width", 1)
// Use SVG circle attribute r to define the radius of the circle
.attr("r",d => d.count*5)
.append("svg:title")
.text(function(d) { return d.count +" days of "+ d.group; });
// Features of the forces applied to the nodes:
const simulation = d3.forceSimulation()
.force("center", d3.forceCenter().x(width / 2).y(height / 2)) // Attraction to the center of the svg area
.force("charge", d3.forceManyBody().strength(500)) // Nodes are attracted one each other of value is > 0
.force("collide", d3.forceCollide().strength(1).radius(0.1).iterations(1)) // Force that avoids circle overlapping
//Add Title
svg.append("text")
.attr("transform", `translate(${(width + margin.left + margin.right)/2},50)`)
.style("text-anchor", "middle")
.style("font-weight", 700)
.text("Learning D3 Tools - First 39 Days");
return svg.node();

}
Insert cell
Insert cell
rawdata = [{name:"Origin", parent: "", value:""},
{name:"Notebook", parent: "Origin", value:"2"},
{name:"CSS", parent: "Origin",value:"1"},
{name:"JavaScript & HTML", parent: "Origin",value:"9"},
{name:"SVG & HTML", parent: "Origin",value:"1"},
{name:"JavaScript", parent: "Origin",value:"14"},
{name:"HTML", parent: "Origin",value:"2"},
{name:"SVG", parent: "Origin",value:"10"}]
Insert cell
treeskilldata = d3.csvParse(d3.csvFormat(rawdata,["name","parent","value"] ))
Insert cell
Insert cell
root = d3.stratify()
.id(function(d) { return d.name; }) // Name of the entity (column name is name in csv)
.parentId(function(d) { return d.parent; }) // Name of the parent (column name is parent in csv)
(treeskilldata);
Insert cell
root.sum(function(d) { return +d.value })
Insert cell
treemap = {
const svg = d3.select(DOM.svg(height + margin.top*4,
width ))
//Now add a colour palette
const palette = d3.scaleOrdinal(d3.schemeSet1);
// Then d3.treemap computes the position of each element of the hierarchy
// The coordinates are added to the root object above
d3.treemap()
.size([width, height])
.padding(4)
(root)
svg.selectAll("rect")
.data(root.leaves())
.enter()
.append("rect")
.attr('x', function (d) { return d.x0; })
.attr('y', function (d) { return d.y0; })
.attr('width', function (d) { return d.x1 - d.x0; })
.attr('height', function (d) { return d.y1 - d.y0; })
.style("stroke", "black")
.style("fill", function (d) { return palette(d.data.name)}); //Note the name variable is in the root.data object
root.leaves()
// and to add the text labels
svg
.selectAll("text")
.data(root.leaves())
.enter()
.append("text")
.attr("x", d => d.x0+5) // adjust position (more left)
.attr("y", d => d.y1-5) // adjust position (higher)
.text(function(d){ return d.data.name +" ("+ d.data.value +")"})
.attr("font-size", "12px")
.attr("fill", "black")
return svg.node();

}
Insert cell
Insert cell
Insert cell
sankeydata=[
{"type":"nodes","node":0,"name":"Notebook"},
{"type":"nodes","node":1,"name":"CSS"},
{"type":"nodes","node":2,"name":"HTML"},
{"type":"nodes","node":3,"name":"JavaScript"},
{"type":"nodes","node":4,"name":"SVG"},
{"type":"nodes","node":5,"name":"SVG & HTML"},
{"type":"nodes","node":6,"name":"JS & HTML"},
{"type":"nodes","node":7,"name":"D3"},
{"type":"links","source":0,"target":7,"value":2},
{"type":"links","source":1,"target":7,"value":1},
{"type":"links","source":2,"target":7,"value":2},
{"type":"links","source":3,"target":7,"value":14},
{"type":"links","source":4,"target":5,"value":0.5},
{"type":"links","source":4,"target":7,"value":10},
{"type":"links","source":5,"target":7,"value":1},
{"type":"links","source":6,"target":7,"value":9},
{"type":"links","source":3,"target":6,"value":4},
{"type":"links","source":2,"target":6,"value":5},
{"type":"links","source":2,"target":5,"value":0.5}
]
Insert cell
md`We need to group the data with d3-collection (https://github.com/d3/d3-collection)'s d3.nest to nest the tabular data into hierarchical format (http://learnjsdata.com/group_data.html). Note: There is a deprecation notice on d3-collection so next time look at d3-array module for grouping.`
Insert cell
sankeynest=d3.nest()
// Set the kay as the type ie nodes and links
.key(function(d) { return d.type; })
// Returns a nested object with the named indexes
.object(sankeydata);
Insert cell
Insert cell
sankeychart = {
const svg = d3.select(DOM.svg(height ,width));
//Now add a colour palette
const palette = d3.scaleOrdinal(d3.schemeSet1);
const {nodes, links} = sankey(sankeynest);

svg.append("g")
.attr("stroke", "#000")
.selectAll("rect")
.data(nodes)
.join("rect")
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("height", d => d.y1 - d.y0)
.attr("width", d => d.x1 - d.x0)
.attr("fill", d => palette(d.name))
.append("title")
.text(d => `${d.name}}`);

svg.append("g")
.style("font", "10px sans-serif")
.selectAll("text")
.data(nodes)
.join("text")
.attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
.attr("y", d => (d.y1 + d.y0) / 2)
.attr("dy", "0.35em")
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
.text(d => d.name+ " ("+d.value+")");
const link = svg.append("g")
.attr("fill", "none")
.attr("stroke-opacity", 0.5)
.selectAll("g")
.data(links)
.join("g")
.style("mix-blend-mode", "multiply");
link.append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke", d => palette(d.source.name) )
.attr("stroke-width", d => Math.max(1, d.width));
return svg.node();
}




Insert cell
sankey = {
const sankey2 = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 5], [width - 1, height - 5]]);
return ({nodes, links}) => sankey2({
nodes: nodes.map(d => Object.assign({}, d)),
links: links.map(d => Object.assign({}, d))
});
}
Insert cell
Insert cell
voronoi = {
const context = DOM.context2d(width, height);
const path = d3.geoPath(projection, context).pointRadius(2.5);

context.beginPath();
path(sphere);
context.strokeStyle = "#000";
context.stroke();

context.beginPath();
path(mesh);
context.stroke();

context.beginPath();
path({type: "MultiPoint", coordinates: points});
context.fillStyle = "blue";
context.fill();

return context.canvas;
}
Insert cell
sphere = ({type: "Sphere"})
Insert cell
projection = d3.geoOrthographic()
.fitExtent([[1, 1], [width - 1, height - 1]], sphere)
.rotate([0, -30])
Insert cell
points = Array.from({length: 200}, (_, i) => [
i / 1.618 * 360 % 360,
Math.acos(2 * i / 200 - 1) / Math.PI * 180 - 90
])
Insert cell
mesh = d3.geoVoronoi(points).cellMesh()
Insert cell
Insert cell
points2 = Array.from({length: 39}, (_, i) => [
i / 1.618 * 360 % 360,
Math.acos(i / 39 -1) / Math.PI * 180 - 90
])
Insert cell
mesh2 = d3.geoVoronoi(points2).cellMesh()
Insert cell
voronoiD3data = {
const context = DOM.context2d(width*3, height*1.5);
const path = d3.geoPath(projection, context).pointRadius(2.5);

//Now add a colour palette
const palette = d3.scaleOrdinal(d3.schemeSet1);
context.beginPath();
path(sphere);
context.strokeStyle = "black";
context.stroke();
context.beginPath();
path(mesh2);
context.stroke();
context.filStyle = "red";
context.fill;


return context.canvas;
}
Insert cell
Insert cell
Insert cell
datalearningd3 = root.children
Insert cell
datapointslearningd3 = datalearningd3.map(d=>[d.x0+(d.x1-d.x0)/2,d.y0+(d.y1-d.y0)/2])
Insert cell
points_voronoi = {
const svg = d3.select(DOM.svg(height, width ))
const cell = svg.append("g")
.attr("fill", "none")
.selectAll("g")
.data(cells)
.enter().append("g");
svg.append("g")
.selectAll("circle")
.data(datapointslearningd3)
.enter()
.append("circle")
.attr("transform", d => `translate(${d})`)
.attr("r", d => 12)
.attr("fill", "black")
return svg.node();

}
Insert cell
treemap_voronoi = {
const svg = d3.select(DOM.svg(height, width ))
const cell = svg.append("g")
.attr("fill", "none")
.selectAll("g")
.data(cells)
.enter().append("g");

const path = svg.append("g").selectAll("path");
path.data(cells)
.enter()
.append("path")
.attr("stroke","white")
.attr("fill", function(d,i) {return colors[i % colors.length]} ) // Use the same colour palette as the treemap
.attr("d", function(d) { return "M" + d.join("L") + "Z" } );


root.leaves()
svg.append("g")
.selectAll("circle")
.data(datapointslearningd3)
.enter()
.append("circle")
.attr("transform", d => `translate(${d})`)
.attr("r", d => 2)
.attr("fill", "black")
// Add the text labels
svg
.selectAll("text")
.data(root.leaves())
.enter()
.append("text")
.attr("x", d => d.x0+(d.x1-d.x0)/2-35) // adjust position (more left)
.attr("y", d => d.y0+(d.y1-d.y0)/2-10) // adjust position (higher)
.text(function(d){ return d.data.name +" ("+ d.data.value +")"})
.attr("font-size", "12px")
.attr("fill", "black")
return svg.node();

}
Insert cell
cells = d3.voronoi()
.extent([[0, 0], [width , height ]])
.polygons(datapointslearningd3)
Insert cell
colors = d3.schemeSet1
Insert cell
md`## Imports`
Insert cell
d3 = require("d3@5","d3-sankey@0.12","d3-array@2","d3-geo-voronoi","d3-delaunay@4")
Insert cell
import {rasterize, serialize} from '@mbostock/saving-svg'
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