Published
Edited
Oct 15, 2020
Insert cell
Insert cell
chart = {
const links = data.links.map(d => Object.create(d));
const nodes = data.nodes.map(d => Object.create(d));
function isCurrentBlock(element) {
if(element.id === currentID) {
return true;
}
}
const currentBlock = nodes.find(isCurrentBlock);
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(120))
.force("charge", d3.forceManyBody().strength(-1300))
.force("x", d3.forceX())
.force("y", d3.forceY());

const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height]);
svg.append("defs").selectAll("marker")
.data(links)
.join("marker")
.attr("id", d => `arrow-${d.relation_type}`)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -0.5)
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient", "auto")
.append("path")
.attr("fill", linkColor)
.attr("d", "M0,-5L10,0L0,5");
const link = svg.append("g")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.selectAll("path")
.data(links)
.join("path")
.attr("stroke", linkColor)
.attr("marker-end", d => `url(${new URL(`#arrow-${d.relation_type}`, location)})`);

const node = svg.append("g")
.attr("fill", "currentColor")
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.selectAll("g")
.data(nodes)
.join("g")
.call(drag(simulation));


node.append("circle")
.attr("class",function(d){if(d.id===currentID)return "currentNode"
else return"node"})
.attr("stroke", function(d){if(d.id===currentID)return "yellow"
else return"white"})
.attr("stroke-width", 3)
.attr("fill", nodeColor)
.attr("r", 9);
const tooltip = svg.append("g");
const currentBlockBox = svg.append("g");
svg
.selectAll(".node")
.on("mouseover", function(event, d) {
tooltip.call(
callout,
`Block ${d.id}
${d.contents}`
);
tooltip.attr("transform", "translate(200,250)");
})
.on("mouseout", function() {
tooltip.call(callout, null);
});

svg
.on("touchmove mousemove", function(event, d) {
currentBlockBox.call(
callout,
`Block ${currentBlock.id}
${currentBlock.contents}`
);
currentBlockBox.attr("transform", "translate(-200,-250)");
});
node.append("text")
.attr("x", 0)
.attr("y", "-10")
.text(d => d.block_type)
.attr("font-weight", "600")
.attr("font-family", "sans-serif")
.attr("font-size", "12px")
.attr("fill",nodeColor)
.attr("text-anchor", "middle")
.clone(true).lower()
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 2);


node.append("title")
.text(d => d.contents);

const linkText = svg.selectAll("linkText")
.data(links)
.enter()
.append("text")
.text(function(d) {
return d.relation_type;
})
.attr("font-family", "sans-serif")
.attr("font-size", "8px")
.attr("fill", linkColor)
.attr("text-anchor", "middle");
simulation.on("tick", () => {
link.attr("d", linkArc);
node.attr("transform", d => `translate(${d.x},${d.y})`);
linkText
.attr("x", d => (d.source.x*2+d.target.x)/3)
.attr("y", d => (d.source.y*2+d.target.y)/3);
});

invalidation.then(() => simulation.stop());

return svg.node();
}
Insert cell
currentID = 1739;
Insert cell
data = FileAttachment("new_data@2.json").json()
Insert cell
callout = (g, value) => {
if (!value) return g.style("display", "none");

g
.style("display", null)
.style("pointer-events", "none")
.style("font", "12px sans-serif");
const path = g.selectAll("path")
.data([null])
.join("path")
.attr("fill", "white")
.attr("stroke", "black");

const text = g.selectAll("text")
.data([null])
.join("text")
.call(text => text
.selectAll("tspan")
.data((value + "").split(/\n/))
.join("tspan")
.attr("x", 0)
.attr("y", (d, i) => `${i * 1.1}em`)
.style("font-weight", (_, i) => i ? null : "bold")
.style("line-hegith", 1.8)
.text(d => d));

const {x, y, width: w, height: h} = text.node().getBBox();
text.attr("transform", `translate(${-w / 2},${15 - y})`);
path.attr("d", `M${- w / 2 - 10},5H${w / 2 + 10}v${h + 20}h-${w + 20}z`);
}
Insert cell
height = 680
Insert cell
nodeColor = {
const typeColor = {
none: "#808e9b",
event: "#575fcf",
opinion: "#ff791f",
description: "#00d8d6",
definition: "#05c46b",
question: "#ffa801",
prediction: "#ef5777",
}
return d => typeColor[d.block_type.toLowerCase()];
}
Insert cell
linkColor = {
const typeColor = {
support: "#409EFF",
oppose: "#F56C6C",
andthen: "#E6A23C",
react: "#67C23A",
}
return d => typeColor[d.relation_type.toLowerCase()];
}
Insert cell
function linkArc(d) {
const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y);
return `
M${d.source.x},${d.source.y}
A${r},${r} 0 0,1 ${d.target.x},${d.target.y}
`;
}
Insert cell
drag = simulation => {
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event,d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event,d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
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