Public
Edited
Apr 30, 2023
Insert cell
Insert cell
chart = {
//Tooltip format lifted (which much thanks) from https://observablehq.com/@muqeet/artsed-bubble
const toolTip = d3.select("body").selectAll('div.tool-tip')
.data([1]).join("div")
.attr("class", "tool-tip")
.style("opacity", 0);
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(d => {return forceVariables.nodeDistance}))
.force("charge", d3.forceManyBody().strength(d => forceVariables.charge))
.force("x", d3.forceX())
.force("y", d3.forceY())
.force('collision', d3.forceCollide().radius(getRadius))
// function(d) { return 9 * (1 + d.functions.length) }

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

const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line")
.attr("stroke-width", 4);
const g = svg.append("g")

const defs = svg.append('defs')
.selectAll('clipPath')
.data([{ name: 'group'}])
.join('clipPath')
.attr('id', 'group-clip')
.append('circle')
.attr('r', d => forceVariables.nodeRadius - 2); // forceVariables.nodeRadius
const node = g.append("g")
.selectAll("g")
.data(nodes)
.join("g")
.attr("data-name", d => d.name)
.attr("fill", "none")
.on("mouseover", function(event, d) {
toolTip.style("opacity", 1);
d3.select(this).style("cursor", "pointer");
})
.on("mouseout", function(event, d) {
toolTip.transition().duration(500).style('opacity', 0);
webIt(d, "out", nodes, link) //add css to illustrate all the connections
// d3.select(this)
// .style('stroke', "") //#72798C this is one of the Net Element Colors if we want to use it
// .style("stroke-opacity", "")
// .style("stroke-width", "");
})
.on("touchmove mousemove", function(event, d) {
buildToolTip(event, d, "node", toolTip);
webIt(d, "in", node, link)
})
.on("click", function(event, d){
d.fx = null;
d.fy = null;
window.open(d.url,d.name);
}).call(drag(simulation));
const cir = node
.append('circle')
.attr("id", d => d.id)
.attr('r', d => getRadius(d.group))
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.attr('fill', '#999')
const img = node
.append('image')
.attr('xlink:href', function(d) {
let img = d.icon
return img
})
.attr('clip-path', d => `url(#group-clip)`)
.attr('width', d => getRadius(d.group) * 2)
.attr('height', d => getRadius(d.group) * 2)
.attr('x', d => getRadius(d.group) * -1)
.attr('y', d => getRadius(d.group) * -1)
.on("mouseover", function(event, d) {
let id = "#" + d.id
d3.select(id)
.style('stroke', "#18aef9").style('stroke-width', 6).style("stroke-opacity", 1);
})
.on("mouseout", function(event, d) {
let id = "#" + d.id
d3.select(id)
.style('stroke', "#fff").style('stroke-width', 1.5).style("stroke-opacity", 1)
});

// //add the mouseover functionality for the links: make tooltips and highlight links and nodes
// link.on("mouseover", function(event, d) {
// toolTip.style("opacity", 1);
// })

simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

// node.attr("cx", d => d.x)
// .attr("cy", d => d.y);
node.attr('transform', function(d) {
// d.x = Math.max(getRadius(d), Math.min(width - getRadius(d), d.x));
// d.y = Math.max(getRadius(d), Math.min(height - getRadius(d), d.y));
return `translate(${d.x},${d.y})`
});
});

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

return svg.node();
}
Insert cell
Insert cell
Insert cell
nodes = {
let all_nodes = data.nodes.map(d => Object.create(d));
return all_nodes; //[all_nodes[0], all_nodes[1], all_nodes[2]];
}
Insert cell
height = 680
Insert cell
color = {
// const scale = d3.scaleOrdinal(d3.schemeCategory10);
// return d => scale(d.functions[0]);
return d3.color("steelblue");
}
Insert cell
size = {
return forceVariables.nodeRadius; // d => 5 * (1 + d.functions.length);
}
Insert cell
getRadius = (d) => {
let res = 1;
// let group = d.group;
// switch (group) {
// case 'Firer':
// res = groupData[0].radius;
// break;
// case 'criminal':
// res = groupData[1].radius;
// break;
// case 'business':
// res = groupData[2].radius;
// break;
// }
return res * forceVariables.nodeRadius;
}
Insert cell
forceVariables = ({ nodeRadius: 20,
nodeDistance: 100,
charge: -100,
xDenom: 3,
xStr: 0.0,
yDenom: 0.9,
yStr: 0.0
})
Insert cell
groupData = [{name: 'Firer', shape: "circle", radius: 3},
{name: "criminal", shape: "circle", radius: 2},
{name: "business", shape: "rectangle", radius: 1}]
Insert cell
function buildToolTip(event, d, type, toolTip) {
var rows = [];
if(type == "link") {
rows.push(["Connection", "related" /*d.relationship*/])
toolTip.html(
rows.map(row => `<span>${row[0]}:<mark>${row[1]}</mark></span>`).join('')
)
.transition().duration(200)
.style('opacity', 0.9)
.style("left", (event.pageX + 28) + "px")
.style("top", (event.pageY - 28) + "px");
}
else if(type == "node") {
rows.push([d.name, d.short_description])
toolTip.html(rows.map(row => `<span>${row[0]}:<mark>${row[1]}</mark></span>`).join('')
+
`<img src=${d.top_image} width="200"></img>`
)
.transition().duration(200)
.style('opacity', 0.9)
.style("left", (event.clientX + 28) + "px")
.style("top", (event.clientY - 28) + "px");
}
}
Insert cell
html`
<style>
.tool-tip {
position: absolute;
padding: 8px;
margin-top: -20px;
font-size: 1em;
background: #ddd;
pointer-events: none;
}

.tool-tip span {
display:block;
}

.tool-tip mark {
font-weight:600;
margin-left: 0.2em;
background-color: transparent;
color: inherit !important;
}
</style>
`
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
function webIt(data, inOrOut, node, link) {
let target_links = link.filter(d => d.source.id == data.id || d.target.id == data.id);
const link_nodes = target_links.data() //returns all the data binded to those links
if(inOrOut == "in") { //add css
target_links
.style('stroke', "#18aef9")
.style("stroke-opacity", 0.6)
.style("stroke-width", 6);
for(var i = 0; i < link_nodes.length; i++){
//console.log(link_nodes[i].source.id)
let source_id = link_nodes[i].source.id
d3.select(`#${source_id}`)
.style('stroke', "#18aef9")
.style("stroke-opacity", 0.6)
.style("stroke-width", 6);
let target_id = link_nodes[i].target.id
d3.select(`#${target_id}`)
.style('stroke', "#18aef9")
.style("stroke-opacity", 0.6)
.style("stroke-width", 6);
}
}
else if(inOrOut == "out") { //remove css
target_links
.style('stroke', "#999")
.style("stroke-width", 4)
.style("stroke-opacity", 0.6);
for(var i = 0; i < link_nodes.length; i++){
//console.log(link_nodes[i].source.id)
let source_id = link_nodes[i].source.id
d3.select(`#${source_id}`)
.style('stroke', "") //#72798C this is one of the Net Element Colors if we want to use it
.style("stroke-opacity", 1)
.style("stroke-width", 0);
let target_id = link_nodes[i].target.id
d3.select(`#${target_id}`)
.style('stroke', "") //#72798C this is one of the Net Element Colors if we want to use it
.style("stroke-opacity", 1)
.style("stroke-width", 0);
}
}
}
Insert cell
d3 = require("d3@6")
Insert cell
import { Range } from "@observablehq/inputs"
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