chart = {
const links = data.links.map(d => Object.create(d));
const nodes = data.nodes.map(d => Object.create(d));
var connected = [];
const simulation = d3
.forceSimulation(nodes)
.force(
"link",
d3
.forceLink(links)
.id(d => d.id)
.distance(100)
)
.force(
"collide",
d3
.forceCollide()
.strength(0.5)
.radius(50)
.iterations(90)
)
.force(
"center",
d3
.forceCenter()
.x(0.5)
.y(0.5)
);
const svg = d3
.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height]);
// GRAPH
////////////////////////////////////////////////////////////////////////////////
// Rendering functions
var reset_node_opacity = function(d) {
return 1;
};
var reset_link_opacity = function(d) {
return 0.4;
};
const link = svg
.append("g")
.selectAll("line")
.data(links)
.join("line")
.attr("value", d => d.value)
.attr("target", d => d.target.id)
.attr("source", d => d.source.id)
.attr("stroke", '#000')
.attr("stroke-width", d => link_width(d.value))
.attr("stroke-opacity", d => reset_link_opacity(d));
const node = svg
.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.join("circle")
.style("cursor", "pointer")
.attr("id", d => d.id)
.attr("r", d => d.radius)
.attr("fill", color)
.call(drag(simulation));
const texts_widgets = svg
.selectAll(".id")
.data(nodes)
.enter()
.filter(d => d.group == 'widget')
.append("text")
.attr("class", "labels")
.attr("font-family", "bebas neue")
.attr("font-size", 14)
.attr("dx", 15)
.attr("dy", "0.35em")
.style('fill', '#1f77b4')
.style("cursor", "pointer")
.attr("id", d => d.id)
.text(d => d.id)
.call(drag(simulation));
const texts_insights = svg
.selectAll(".id")
.data(nodes)
.enter()
.filter(d => d.group == 'insight')
.append("text")
.attr("class", "labels")
.attr("font-family", "bebas neue")
.attr("font-size", 18)
.attr("dx", 23)
.attr("dy", "0.35em")
.style('fill', '#ff7f0e')
.style("cursor", "pointer")
.attr("id", d => d.id)
.text(d => d.id)
.call(drag(simulation));
/////////////////////////////////////////////////////////////////////////////////////
///// UX FUNCTIONS
/////////////////////////////////////////////////////////////////////////////////////
const reset_all_opacity = function() {
d3.selectAll('circle').attr('opacity', d => reset_node_opacity(d));
d3.selectAll('line').attr('stroke-opacity', d => reset_link_opacity(d));
d3.selectAll('.labels').style('opacity', 1);
};
const hide_all_light = function() {
d3.selectAll('circle').attr('opacity', 0.08);
d3.selectAll('line').attr('stroke-opacity', 0.03);
d3.selectAll('.labels').style('opacity', 0.03);
};
var restore_default_display = function() {
d3.selectAll('.legend_session').attr('active', 'no');
d3.selectAll('.legend_channel').attr('active', 'no');
reset_all_opacity();
};
/////////////////////////////////////////////////////////////////////////////////////
///// INTERACTIONS
/////////////////////////////////////////////////////////////////////////////////////
var selection = null;
const selected = d => selection === d;
var neighbors_nodes_id = [];
texts_insights.on('click', (e, d) => {
onClickHandler(e, "#de700d");
});
texts_widgets.on('click', (e, d) => {
onClickHandler(e, "#185e8f");
});
node.on('click', (e, d) => {
var nodecolor;
if (e.group === "insight") nodecolor = "#de700d";
else nodecolor = "#185e8f";
onClickHandler(e, nodecolor);
});
function onClickHandler(e, nodecolor) {
if (selection == null) {
selection = e.id;
d3.selectAll('circle[id="' + e.id + '"]')
.attr("stroke", nodecolor)
.attr("stroke-width", 5);
onMouseOver(e);
} else if (selection == e.id) {
d3.selectAll('circle[id="' + selection + '"]').attr("stroke-width", 0);
selection = null;
onMouseOut(e);
} else if (neighbors_nodes_id.includes(e.id)) {
d3.selectAll('circle[id="' + selection + '"]').attr("stroke-width", 0);
selection = e.id;
d3.selectAll('circle[id="' + e.id + '"]')
.attr("stroke", nodecolor)
.attr("stroke-width", 5);
onMouseOver(e);
}
}
node.on('mouseover', d => {
if (!selection) {
onMouseOver(d);
} else if (neighbors_nodes_id.includes(d.id)) {
onMouseOverNeighbors(d);
}
});
var last_targeted_node;
function onMouseOver(d) {
last_targeted_node = d;
neighbors_nodes_id = [];
// the hover interaction is not active if a node is selected
hide_all_light();
// Higlight basic links
d3.selectAll('circle[id="' + d.id + '"]').attr('opacity', 1);
var links_src = d3.selectAll('line[source="' + d.id + '"]');
var links_tar = d3.selectAll('line[target="' + d.id + '"]');
links_src.attr('stroke-opacity', dd => reset_link_opacity(dd));
links_tar.attr('stroke-opacity', dd => reset_link_opacity(dd));
links_src.each(e => {
neighbors_nodes_id.push(e.target.id);
d3.selectAll('circle[id="' + e.target.id + '"]').attr(
'opacity',
reset_node_opacity(e.target)
);
});
links_tar.each(e => {
neighbors_nodes_id.push(e.source.id);
d3.selectAll('circle[id="' + e.source.id + '"]').attr(
'opacity',
reset_node_opacity(e.source)
);
});
// Highlight selected node label
texts_widgets.filter(w => w.id === d.id).style('opacity', 1);
texts_insights.filter(i => i.id === d.id).style('opacity', 1);
// Highlight neighbors labels
neighbors_nodes_id.forEach(e =>
texts_widgets.filter(w => w.id === e).style('opacity', 1)
);
neighbors_nodes_id.forEach(e =>
texts_insights.filter(w => w.id === e).style('opacity', 1)
);
}
function onMouseOverNeighbors(d) {
var temp_neighbors = [];
// the hover interaction is not active if a node is selected
// Higlight basic links
d3.selectAll('circle[id="' + d.id + '"]').attr('opacity', 1);
var links_src = d3.selectAll('line[source="' + d.id + '"]');
var links_tar = d3.selectAll('line[target="' + d.id + '"]');
links_src.attr('stroke-opacity', dd => reset_link_opacity(dd));
links_tar.attr('stroke-opacity', dd => reset_link_opacity(dd));
links_src.each(e => {
temp_neighbors.push(e.target.id);
d3.selectAll('circle[id="' + e.target.id + '"]').attr(
'opacity',
reset_node_opacity(e.target)
);
});
links_tar.each(e => {
temp_neighbors.push(e.source.id);
d3.selectAll('circle[id="' + e.source.id + '"]').attr(
'opacity',
reset_node_opacity(e.source)
);
});
// Highlight selected node label
texts_widgets.filter(w => w.id === d.id).style('opacity', 1);
texts_insights.filter(i => i.id === d.id).style('opacity', 1);
// Highlight neighbors labels
temp_neighbors.forEach(e =>
texts_widgets.filter(w => w.id === e).style('opacity', 1)
);
temp_neighbors.forEach(e =>
texts_insights.filter(w => w.id === e).style('opacity', 1)
);
}
node.on('mouseout', d => {
onMouseOut(d);
});
function onMouseOut(d) {
if (!selection) {
restore_default_display();
} else if (neighbors_nodes_id.includes(d.id)) {
hide_all_light();
onMouseOver(last_targeted_node);
}
}
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);
texts_widgets.attr("x", d => d.x).attr("y", d => d.y);
texts_insights.attr("x", d => d.x).attr("y", d => d.y);
});
invalidation.then(() => simulation.stop());
return svg.node();
}