Published
Edited
Oct 27, 2019
1 fork
Importers
Insert cell
Insert cell
Insert cell
Insert cell
mutable z = 42
Insert cell
{
// Configure buttons
const buttons = d3.select(viewof buttons);
buttons.select('input[name=addNode]').on('click', addNode)
buttons.select('input[name=removeNode]').on('click', removeNode)
buttons.select('input[name=addSource]').on('click', addSource)
buttons.select('input[name=addTarget]').on('click', addTarget)
buttons.select('input[name=connectNodes]').on('click', connectNodes)
return Graph.graphData()
}
Insert cell
Graph = {
mutable z = "Graph"
const Graph = ForceGraph3D()(container)
.width(width)
.height(500)
.nodeRelSize(10)
.nodeLabel(d => 'This is node #' + d.id)
.linkDirectionalParticles(2)
.linkWidth(2)
.linkDirectionalParticleWidth(5)
.nodeOpacity(1)
.nodeColor(nodeColor)
.onNodeHover(node => {
container.style.cursor = node ? 'pointer' : null;
})
.onNodeClick(node => {
// node.selected = !node.selected;
// trigger update of highlighted objects in scene
Graph.nodeColor(Graph.nodeColor())
})
.onBackgroundClick(() => {
// Functionally the same as resetButtons, except without the circular reference
const buttons = d3.select(viewof buttons)
buttonData.forEach(d => {
buttons.select("input[name=" + d.name + "]").classed("selected", false).attr("value", d.value);
})

Graph.onNodeClick(null)
})
// Set the distance between nodes
const linkForce = Graph
.d3Force('link')
.distance(link => 100);
return Graph
}
Insert cell
{
for (var i = 0; i < 3; i++) addNode();
return "Initialize things"
}
Insert cell
nodeColor = node => node.selected ? 'rgb(255,0,0,1)' : 'rgba(0,255,255,0.6)'
Insert cell
connectNodes = function() {
resetButtons();
const button = d3.select(this).classed('selected', true);
const value = button.attr('value');
button.attr('value', 'Select source');
Graph.onNodeClick(source => {
button.attr('value', 'Select target');
Graph.onNodeClick(target => {
const { nodes, links } = Graph.graphData();
Graph.graphData({
nodes: nodes,
links: [...links, { source: source.id, target: target.id }]
});
button.classed('selected', null).attr('value', value);
Graph.onNodeClick(null);
});
});
}
Insert cell
addSource = function() {
resetButtons();
const button = d3.select(this).classed('selected', true);
const value = button.attr('value');
button.attr('value', 'Select target');
Graph.onNodeClick((node) => {
addNode(node.id);
button.classed('selected', null).attr('value', value);
Graph.onNodeClick(null);
});
}
Insert cell
addTarget = function() {
resetButtons();
const button = d3.select(this).classed('selected', true);
const value = button.attr('value');
button.attr('value', 'Select source');
Graph.onNodeClick((node) => {
const targetId = addNode();
addLink(node.id, targetId);
button.classed('selected', null).attr('value', value);
Graph.onNodeClick(null);
});
}
Insert cell
removeNode = function() {
resetButtons();
const button = d3.select(this).classed('selected', true);
const value = button.attr('value');
button.attr('value', 'Select node');
Graph.onNodeClick((node) => {
let { nodes, links } = Graph.graphData();
const goodNodes = nodes.filter(chk => { return chk.id != node.id });
const goodLinks = links.filter(link => { return link.source.id != node.id && link.target.id != node.id });
Graph.graphData({ nodes: goodNodes, links: goodLinks });
// TESTING
mutable z += ",\n Removing node: " + node.id;
// END TESTING
button.classed('selected', null).attr('value', value);
Graph.onNodeClick(null);
});
}
Insert cell
// Adds a node with optional target
addNode = function(target_id) {
resetButtons();
const that = d3.select(this).classed('selected', true);
setTimeout(() => that.classed('selected', false), 100)
const { nodes, links } = Graph.graphData();
const id = nodes.length == 0 ? 0 : d3.max(nodes.map(node => node.id)) + 1;

// TESTING
mutable z += ",\n Adding node: " + id + " with target: " + target_id + " and arguments.length: " + arguments.length;
// END TESTING

Graph.graphData({
nodes: [...nodes, { id: id, selected: false }],
links: (arguments.length == 1) ? [...links, { source: id, target: target_id }] : links
});
return id;
}
Insert cell
Insert cell
resetButtons = function() {
const buttons = d3.select(viewof buttons)
buttonData.forEach(d => {
buttons.select("input[name=" + d.name + "]").classed("selected", false).attr("value", d.value);
})
Graph.onNodeClick(null)
}
Insert cell
buttonData = [
{ name: "addNode", value: "Add node", title: "Add a node with no connections" },
{ name: "addSource", value: "Add source", title: "First click on the target node" },
{ name: "addTarget", value: "Add target", title: "First click on the source node" },
{ name: "connectNodes", value: "Connect nodes", title: "First click on the source, then the target" },
{ name: "removeNode", value: "Remove node", title: "Click the node to remove" }
]
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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