Published
Edited
Jun 23, 2020
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`<figure>
<img src="${await FileAttachment("dataSet.jpg").url()}">
</figure>`
Insert cell
Insert cell
category_array=["Sex", "Survived", "Age", "Class", "value"]
Insert cell
//Draw the tree here using the graphTree function. It could look simlar to that.
//graphTree(hierarchyData, {label: d => d.data.name})
graphTree(hierachyData, {label: d => d.data.name})
Insert cell
//Add the depth and height information to the nested data
/*
hierachyData = d3.nest()
.key(d => d.Sex)
.entries(data);
*/
// TEST to understand how hierarchy and nest work, source from d3 documentation about nest and hierarchy
/*
hierachyData = d3.hierarchy({
name:"Data Set", children: [
{name: "Female", children: [{name: "Adult"},
{name: "Child"}]},
{name: "Male", children: [{name: "Adult"},
{name: "Child"}]}
]
})
*/

hierachyData = d3.hierarchy({
name: "root",
children: childNode
})
Insert cell
childNode = d3.nest()
.key(d => d[category_array[0]])
.key(d => d[category_array[1]])
.key(d => d[category_array[2]])
.key(d => d[category_array[3]])
.key(d => d[category_array[4]])
.entries(data)
.map(function(first_level) { // 5 level of the nested data
return {name: first_level.key, children: first_level.values.map(function(second_level) {
return {name: second_level.key, children: second_level.values.map(function(third_level) {
return {name: third_level.key, children: third_level.values.map(function(fourth_level) {
return {name: fourth_level.key, children: fourth_level.values.map(function(fifth_level) {
return {name: fifth_level.key}})}})}})}})}})

// category_array.forEach(element => childNode.map(showChildren(element)));
Insert cell
// try to recursively do the above code, not working

function showChildren(d) {
return {name: d.key}
}
Insert cell
md` The tree graph needs nested data with specific information of the nodes, this includes height and depth. Have a look at the nest and hierarchy functionalities in D3. Remember to include the functionality to alter the order of the level in the tree according to the order in category_array.`
Insert cell
function graphTree(root, {
label = d => d.data.id,
highlight = () => false,
marginLeft = 40
} = {}) {
root = tree(root);

let x0 = Infinity;
let x1 = -x0;
root.each(d => {
if (d.x > x1) x1 = d.x;
if (d.x < x0) x0 = d.x;
});

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, x1 - x0 + dx * 2])
.style("overflow", "visible");
const g = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("transform", `translate(${marginLeft},${dx - x0})`);
const link = g.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5)
.selectAll("path")
.data(root.links())
.join("path")
.attr("stroke-opacity", 0.5)
.attr("d", treeLink);
const node = g.append("g")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3)
.selectAll("g")
.data(root.descendants())
.join("g")
.attr("transform", d => `translate(${d.y},${d.x})`);

node.append("circle")
.attr("fill", d => highlight(d) ? "red" : d.children ? "#555" : "#999")
.attr("r", 2.5);

node.append("text")
.attr("fill", d => highlight(d) ? "red" : null)
.attr("dy", "0.31em")
.attr("x", d => d.children ? -6 : 6)
.attr("text-anchor", d => d.children ? "end" : "start")
.text(label)
.clone(true).lower()
.attr("stroke", "white");
return svg.node();
}
Insert cell
Insert cell
parallelSet={
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
// your code
// REFERENCE: https://observablehq.com/@d3/parallel-sets?collection=@d3/d3-sankey
// use sankey-diagram to create a nodes and links structure
const {nodes, links} = sankey({
nodes: graph.nodes.map(d => Object.assign({}, d)),
links: graph.links.map(d => Object.assign({}, d))
});
// draw the axis for each level (represents the attributes) as nodes
svg.append("g")
.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);
// draw all links
svg.append("g")
.attr("fill", "none")
.selectAll("g")
.data(links)
.join("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke", d => color(d.names[0]))
.attr("stroke-width", d => d.width)
.style("mix-blend-mode", "multiply");
// name and alignment of each value in each level
svg.append("g")
.style("font", "14px sans-serif")
.selectAll("text")
.data(nodes)
.join("text")
.attr("x", d => d.x0 < width / 2 ? d.x1 + 10 : d.x0 - 10)
.attr("y", d => (d.y1 + d.y0) / 2)
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
.attr("font-weight", "bold")
.text(d => d.name)
// add style for text for a better appearance
.clone(true)
.lower()
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-opacity", 0.4);
return svg.node();
}
Insert cell
// REFERENCE: https://observablehq.com/@d3/parallel-sets?collection=@d3/d3-sankey
sankey = d3.sankey()
.nodeSort(null)
.linkSort(null)
.nodeWidth(2)
.nodePadding(30)
.extent([[0, 5], [width, height - 5]])
Insert cell
// REFERENCE: https://observablehq.com/@d3/parallel-sets?collection=@d3/d3-sankey
graph = {
let index = -1;
const nodes = [];
const nodeByKey = new Map;
const indexByKey = new Map;
const links = [];

for (const k of category_array) {
for (const d of data) {
if(k !== "value") { // ignore the "value" in category_array to remove the "value" level (node)
const key = JSON.stringify([k, d[k]]);
if (nodeByKey.has(key)) continue;
const node = {name: d[k]};
nodes.push(node);
nodeByKey.set(key, node);
indexByKey.set(key, ++index);
}
}
}

for (let i = 1; i < category_array.length; ++i) {
if(category_array[i] !== "value") { // ignore the "value" in category_array to remove the "value" level (node)
const a = category_array[i - 1];
const b = category_array[i];
const prefix = category_array.slice(0, i + 1);
const linkByKey = new Map;
for (const d of data) {
const names = prefix.map(k => d[k]);
const key = JSON.stringify(names);
const value = d.value || 1;
let link = linkByKey.get(key);
if (link) { link.value += value; continue; }
link = {
source: indexByKey.get(JSON.stringify([a, d[a]])),
target: indexByKey.get(JSON.stringify([b, d[b]])),
names,
value
};
links.push(link);
linkByKey.set(key, link);
}
}
}

return {nodes, links};
}
Insert cell
md`## Apendix`
Insert cell
data = d3.csvParse(await FileAttachment("titanic.csv").text(), d3.autoType)
Insert cell
import { table } from "@tmcw/tables/2"
Insert cell
dx = 12
Insert cell
dy = 90
Insert cell
Insert cell
tree = d3.tree().nodeSize([dx, dy])
Insert cell
shape2path = require("shape2path@3")
Insert cell
d3 = require("d3@5", "d3-sankey@0.12")
Insert cell
height = 600
Insert cell
color = d3.scaleOrdinal(["#4e79a7","#f28e2c","#e15759","#76b7b2","#59a14f","#edc949","#af7aa1","#ff9da7","#9c755f","#bab0ab"])
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