Published
Edited
Apr 11, 2021
Insert cell
Insert cell
chart = {
const root = tree(bilink(d3.hierarchy(data)
.sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.name, b.data.name))));

const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -width / 2, width, width]);

const node = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 6)
.selectAll("g")
.data(root.leaves())
.join("g")
.attr("transform", d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`)
.append("text")
.attr("dy", "0.31em")
.attr("x", d => d.x < Math.PI ? 6 : -6)
.attr("text-anchor", d => d.x < Math.PI ? "start" : "end")
.attr("transform", d => d.x >= Math.PI ? "rotate(180)" : null)
.text(d => d.data.name)
.each(function(d) { d.text = this; })
.on("mouseover", overed)
.on("mouseout", outed)
.on("click", clicked)
.call(text => text.append("title").text(d => `${id(d)}
${d.outgoing.length} influenced by
${d.incoming.length} has affected`));

const link = svg.append("g")
.attr("stroke", colornone)
.attr("fill", "none")
.selectAll("path")
.data(root.leaves().flatMap(leaf => leaf.outgoing))
.join("path")
.style("mix-blend-mode", "multiply")
.attr("d", ([i, o]) => line(i.path(o)))
.each(function(d) { d.path = this; });

function overed(event, d) {
link.style("mix-blend-mode", null);
d3.select(this).attr("font-weight", "bold");
d3.selectAll(d.incoming.map(d => d.path)).attr("stroke", colorin).raise();
d3.selectAll(d.incoming.map(([d]) => d.text)).attr("fill", colorin).attr("font-weight", "bold");
d3.selectAll(d.outgoing.map(d => d.path)).attr("stroke", colorout).raise();
d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr("fill", colorout).attr("font-weight", "bold");
}

function outed(event, d) {
link.style("mix-blend-mode", "multiply");
d3.select(this).attr("font-weight", null);
d3.selectAll(d.incoming.map(d => d.path)).attr("stroke", null);
d3.selectAll(d.incoming.map(([d]) => d.text)).attr("fill", null).attr("font-weight", null);
d3.selectAll(d.outgoing.map(d => d.path)).attr("stroke", null);
d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr("fill", null).attr("font-weight", null);
}
function clicked(event, d) {
mutable clickedData = d;
}

return svg.node();
}
Insert cell
mutable clickedData = undefined;
Insert cell
viewof selectedSubject = {
const arIncoming = clickedData.incoming;
const incomingList = [];
arIncoming.forEach(function(obj) {
incomingList.push(html`<li><a target="_blank" href="https://walkingtogether.life/books/?filter=subject&amp;id=${getSubjectID(obj[0].data.name)}">${obj[0].data.name}</a></li>`);
});
const element = html`<h2><a target="_blank" href="https://walkingtogether.life/books/?filter=subject&amp;id=${getSubjectID(clickedData.data.name)}">${clickedData.data.shortname}</a></h2>
<p style="margin-bottom:0px;">People to whom <b>${clickedData.data.shortname}</b> referred:</p>
<ul style="margin-top:0px;">
${clickedData.data.imports.map(name => html`
<li><a target="_blank" href="https://walkingtogether.life/books/?filter=subject&amp;id=${getSubjectID(getShortName(name))}">${getShortName(name)}</a></li>
`)}
</ul>
<p style="margin-bottom:0px;">People who referred to <b>${clickedData.data.shortname}</b>:</p>
<ul style="margin-top:0px;">
${incomingList}
</ul>`
return element
};

Insert cell
function getShortName(strName) {
let strShortName = strName.replace('graph.Biography.', '');
strShortName = strShortName.replace('graph.EvangelistBiography.', '');
strShortName = strShortName.replace('graph.MissionaryBiography.', '');
return strShortName;
}
Insert cell
function getSubjectID(strName) {
let nSubjectID = 0;
let objFound = data.children[0].children.find(obj => obj.name === strName);
if (objFound === undefined)
{
objFound = data.children[1].children.find(obj => obj.name === strName);
if (objFound === undefined)
{
objFound = data.children[2].children.find(obj => obj.name === strName);
if (objFound === undefined)
nSubjectID = 0;
else
nSubjectID = objFound.personID;
}
else
nSubjectID = objFound.personID;
}
else
nSubjectID = objFound.personID;
return nSubjectID;
}
Insert cell
data = hierarchy(await FileAttachment("graph@2.json").json())
Insert cell
function hierarchy(data, delimiter = ".") {
let root;
const map = new Map;
data.forEach(function find(data) {
const {name} = data;
if (map.has(name)) return map.get(name);
const i = name.lastIndexOf(delimiter);
map.set(name, data);
if (i >= 0) {
find({name: name.substring(0, i), children: []}).children.push(data);
data.name = name.substring(i + 1);
} else {
root = data;
}
return data;
});
return root;
}
Insert cell
Insert cell
function id(node) {
return `${node.parent ? id(node.parent) + "." : ""}${node.data.name}`;
}
Insert cell
colorin = "#00f"
Insert cell
colorout = "#f00"
Insert cell
colornone = "#eee"
Insert cell
width = 954
Insert cell
radius = width / 2
Insert cell
line = d3.lineRadial()
.curve(d3.curveBundle.beta(0.85))
.radius(d => d.y)
.angle(d => d.x)
Insert cell
tree = d3.cluster()
.size([2 * Math.PI, radius - 100])
Insert cell
d3 = require("d3@6")
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