Public
Edited
May 31, 2023
Insert cell
Insert cell
schema1 = FileAttachment("schema@1.json").json()
Insert cell
actions1 = FileAttachment("actions@1.json").json()
Insert cell
chart = createGraph()
Insert cell
function createGraph(nodes, links) {
const actionsTree = {};
const vespace = { nodes: [], links: [] };

schema1.schema.forEach((schemata) => {
if (schemata.category === "progress" || schemata.category === "conversation") {
schemata.types.forEach((type) => {
actionsTree[type] = {};
});
}
});

Object.keys(actionsTree).forEach((progressType, i) => {
actions1.actions.forEach((action) => {
const hasProgressTypeAsCondition = _.some(action.conditions, (condition) => { return ["progress", "conversation"].includes(condition.category) && condition.type === progressType; });
const hasProgressTypeAsEffect = _.some(action.effects, (effect) => { return ["progress", "conversation"].includes(effect.category) && effect.type === progressType; });
if (hasProgressTypeAsCondition || hasProgressTypeAsEffect) {
if (!_.find(vespace.nodes, (node => node.id === progressType))) {
vespace.nodes.push({
id: progressType,
group: i + 1
})
}
if (!_.find(vespace.nodes, (node => node.id === action.name))) {
vespace.nodes.push({
id: action.name,
group: i + 2
})
}
}
if (hasProgressTypeAsCondition) {
vespace.links.push({
source: progressType,
target: action.name
})
actionsTree[progressType][action.name] = {};
}
if (hasProgressTypeAsEffect) {
vespace.links.push({
source: action.name,
target: progressType
})
actionsTree[progressType][action.name] = {};
}
});
});
const width = 2000,
height = 1200;
const 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);
}
const simulation = d3.forceSimulation(vespace.nodes)
.force("link", d3.forceLink(vespace.links).id(d => d.id))
.force("charge", d3.forceManyBody().strength(-400))
.force("x", d3.forceX())
.force("y", d3.forceY());
const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height])
.style("font", "12px sans-serif");
const link = svg.append("g")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.selectAll("path")
.data(vespace.links)
.join("path")
.attr("stroke", "black")
.attr("marker-end", d => `url(${new URL(`#arrow-${d.type}`, location)})`);
const node = svg.append("g")
.attr("fill", "currentColor")
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.selectAll("g")
.data(vespace.nodes)
.join("g")
.call(drag(simulation));
node.append("circle")
.attr("stroke", "white")
.attr("stroke-width", 1.5)
.attr("r", 4);
node.append("text")
.attr("x", 8)
.attr("y", "0.31em")
.text(d => d.id)
.clone(true).lower()
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 3);
simulation.on("tick", () => {
link.attr("d", 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}
`;
});
node.attr("transform", d => `translate(${d.x},${d.y})`);
});
return svg.node();
}
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