chart = {
const mainGraphNodes = data.nodes.map((d) => Object.create(d));
const mainGraphLinks = data.links.map((d) => Object.create(d));
const mainGraphCategories = data.categories.map(d => Object.create(d));
const simulation = d3
.forceSimulation()
.velocityDecay(0.1)
.nodes(mainGraphNodes)
.force('x', d3.forceX(width).strength(0.05))
.force('y', d3.forceY(height).strength(0.05))
.force(
'link',
d3.forceLink(mainGraphLinks).id((d) => d.id),
)
.force('charge', d3.forceManyBody().strength(-240))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collision', d3.forceCollide().radius(7));
const zoom = d3.zoom().scaleExtent([0.2, 5]);
const svg = d3
.create('svg')
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('preserveAspectRatio', 'xMinYMid')
.call(zoom)
.call(zoom.transform, d3.zoomIdentity);
const container = svg.append('g').attr('id', 'graphContainer');
zoom.on('zoom', (event) => {
container.attr('transform', event.transform);
});
svg.append('style').text(`
text.hover {
font-weight: bold;
}
line:hover{
stroke: #000;
stroke-opacity: 1;
}
line.pactive {
stroke: #000;
stroke-opacity: 1;
}
circle.pactive{
stroke: #000;
}
line.gactive {
stroke-opacity: 1;
}
text.gactive{
font-weight: bold;
font-size: 25;
}
.inactive {
fill: #808080;
opacity: 0.4;
}
line.inactive {
stroke-opacity: 0.4;
}
`);
const graphLinks = container
.append('g')
.attr('class', 'links')
.selectAll('line')
.data(mainGraphLinks)
.enter()
.append('line')
.attr('pointer-events', 'visible')
.attr('class', (linkData) => {
return (
'id' +
linkData.source.id +
' id' +
linkData.target.id +
' category' +
linkData.source.categories.map((category) => category.id).join(' category') +
' category' +
linkData.target.categories.map((category) => category.id).join(' category')
);
})
.attr('stroke', '#999')
.attr('stroke-opacity', 0.8)
.attr('stroke-width', '1')
.on('mouseover', (event, linkData) => {
d3.select(event.target).style('stroke', '#4576d1').style('stroke-width', '5');
})
.on('mouseout', (event) => {
d3.select(event.target)
.style('stroke', '#999')
.style('stroke-width', '1');
});
graphLinks
.append('title')
.text((linkData) => linkData.tags.map((tag) => tag.text).join(' '));
const graphNodes = container
.append('g')
.selectAll('.node')
.data(mainGraphNodes)
.join('g')
.attr('class', 'node')
.attr('pointer-events', 'visible');
graphNodes
.on('mouseover', (event, nodeData) => {
d3.select(event.target).style('stroke', '#3f51b5').style('stroke-width', '5');
d3.selectAll(`line.id${nodeData.id}`).style('stroke', '#4576d1').style('stroke-width', '5');
})
.on('mouseout', (event, nodeData) => {
d3.select(event.target).style('stroke', '#666').style('stroke-width', '1');
d3.selectAll(`line.id${nodeData.id}`)
.style('stroke', '#999')
.style('stroke-width', '1');
})
graphNodes.call(drag(simulation));
const categoryArc = d3.arc().innerRadius(5).outerRadius(7);
graphNodes
.append('circle')
.attr('r', 5)
.attr('class', (nodeData) => {
return (
'id' +
nodeData.id +
' category' +
nodeData.categories.map((category) => category.id).join(' category')
);
})
.attr('fill', '#3b3939')
.each((nodeData, index) => {
const pieData = [];
nodeData.categories.forEach((element) => {
pieData.push({
category: element.id,
categoryName: element.text,
color: element.color,
value: 1 / nodeData.categories.length,
});
});
const GlobalVariables = Object.freeze({
pie: d3.pie().value(data => data.value),
});
const arcData = GlobalVariables.pie(pieData);
const selectedNode = graphNodes.filter((element, id) => {
return id === index;
});
selectedNode
.selectAll()
.data(arcData)
.enter()
.append('path')
.attr('fill', (category) => category.data.color)
.attr('d', categoryArc)
.attr('class', (category) => `category${category.data.category} arcs`)
.append('title')
.text((category) => category.data.categoryName);
});
graphNodes.append('title').text((nodeData) => nodeData.data_entity_text);
simulation.on('tick', () => {
graphLinks
.attr('x1', (linkData) => linkData.source.x)
.attr('y1', (linkData) => linkData.source.y)
.attr('x2', (linkData) => linkData.target.x)
.attr('y2', (linkData) => linkData.target.y);
graphNodes.attr('transform', (nodeData) => `translate(${nodeData.x}, ${nodeData.y})`);
});
let legendState = new Map();
mainGraphCategories.forEach((category) => {
legendState.set(category.id, false);
});
svg
.append('g')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.attr('font-family', 'sans-serif')
.attr('font-size', 15)
.attr('text-anchor', 'start')
.attr('class', 'categoryLegend')
.attr('opacity', 1)
.selectAll('g')
.data(mainGraphCategories)
.join('g')
.attr(
'transform',
(category, index) =>
`translate(${15},${index * (15 + 5) + 10})`,
)
.call((g) =>
g
.append('text')
.attr('x', 6)
.attr('y', '0.35em')
.attr('class', (category) => 'category' + category.id)
.attr('fill', (category) => (category.color ? category.color : '#c3c3c3'))
.text((category) => category.text),
)
.call((g) =>
g
.append('circle')
.attr('r', 5)
.attr('class', (category) => 'category' + category.id + ' legendNodes')
.attr('fill', (category) => (category.color ? category.color : '#c3c3c3')),
)
.on('mouseover', (event, category) => {
d3.selectAll(`text.${'category' + category.id}`).classed('hover', true);
})
.on('mouseout', (event, category) => {
d3.selectAll(`text.${'category' + category.id}`).classed('hover', false);
})
.on('click', (event, category) => {
selectGroup(category.id, 'category', legendState);
});
invalidation.then(() => simulation.stop());
return svg.node();
}