viewof chart = {
let selectedNode = null;
const nodeDeselectOpacity = 0.2;
const linkDeselectOpacity = 0.1;
const nodeHighlightStroke = "#ffd700";
const width = 928;
const height = 800;
const colorScale = d3.scaleOrdinal(d3.schemeCategory10);
const finalProcessedNodes = [];
const finalProcessedLinks = [];
const nodeIdsPresent = new Set();
const NodeType = { FILM: "filme", DIRECTOR: "diretor", ACTOR: "ator" };
const NodeGroup = {
[NodeType.FILM]: 1,
[NodeType.DIRECTOR]: 2,
[NodeType.ACTOR]: 3
};
data.nodes.forEach(movie => {
const movieId = movie.id;
if (!nodeIdsPresent.has(movieId)) {
finalProcessedNodes.push({
id: movieId, originalData: movie, nodeType: NodeType.FILM,
group: NodeGroup[NodeType.FILM], movieCount: 0, filmList: []
});
nodeIdsPresent.add(movieId);
}
if (movie.director) {
const directorName = movie.director;
if (!nodeIdsPresent.has(directorName)) {
finalProcessedNodes.push({
id: directorName, nodeType: NodeType.DIRECTOR,
group: NodeGroup[NodeType.DIRECTOR], movieCount: 0, filmList: []
});
nodeIdsPresent.add(directorName);
}
finalProcessedLinks.push({ source: movieId, target: directorName, value: 1.5 });
}
const actorFields = ["actor1", "actor2"];
actorFields.forEach(actorField => {
if (movie[actorField]) {
const actorName = movie[actorField];
if (!nodeIdsPresent.has(actorName)) {
finalProcessedNodes.push({
id: actorName, nodeType: NodeType.ACTOR,
group: NodeGroup[NodeType.ACTOR], movieCount: 0, filmList: []
});
nodeIdsPresent.add(actorName);
}
finalProcessedLinks.push({ source: movieId, target: actorName, value: 1 });
}
});
});
const nodeMapForCounting = new Map(finalProcessedNodes.map(n => [n.id, n]));
finalProcessedLinks.forEach(link => {
const sourceNode = nodeMapForCounting.get(link.source);
const targetNode = nodeMapForCounting.get(link.target);
if (sourceNode && targetNode && sourceNode.nodeType === NodeType.FILM) {
if (targetNode.nodeType === NodeType.DIRECTOR || targetNode.nodeType === NodeType.ACTOR) {
targetNode.movieCount++;
targetNode.filmList.push(sourceNode.id);
}
}
});
const nodesForSimulation = finalProcessedNodes;
const linksForSimulation = finalProcessedLinks.map(d => ({...d}));
const topDirector = nodesForSimulation
.filter(n => n.nodeType === NodeType.DIRECTOR && n.movieCount > 0)
.sort((a, b) => b.movieCount - a.movieCount)[0];
const topDirectorId = topDirector ? topDirector.id : null;
const topActor = nodesForSimulation
.filter(n => n.nodeType === NodeType.ACTOR && n.movieCount > 0)
.sort((a, b) => b.movieCount - a.movieCount)[0];
const topActorId = topActor ? topActor.id : null;
const simulation = d3.forceSimulation(nodesForSimulation)
.force("link", d3.forceLink(linksForSimulation)
.id(d => d.id)
.distance(10)
.strength(0.15)
)
.force("charge", d3.forceManyBody().strength(-60))
.force("x", d3.forceX().strength(0.15))
.force("y", d3.forceY().strength(0.15))
.tick(300);
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("style", "max-width: 100%; height: auto; font-family: sans-serif; font-size: 10px;")
.on('click', deselected);
const linkElements = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(linksForSimulation)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value || 1));
const nodeElements = svg.append("g")
.selectAll("circle")
.data(nodesForSimulation)
.join("circle")
.attr("r", d => {
if (d.id === topDirectorId || d.id === topActorId) return 16;
if (d.nodeType === NodeType.DIRECTOR || d.nodeType === NodeType.ACTOR) return 6;
return 6;
})
.attr("fill", d => colorScale(d.group))
.attr("stroke", d => (d.id === topDirectorId || d.id === topActorId) ? "#000000" : "#FFFFFF")
.attr("stroke-width", d => (d.id === topDirectorId || d.id === topActorId) ? 2 : 1)
.on('click', clicked);
nodeElements.append("title")
.text(d => {
if (d.nodeType === NodeType.DIRECTOR) return `${d.id}\nTipo: Diretor\nDirigiu: ${d.movieCount} filme(s)\nFilmes: ${d.filmList.length > 0 ? d.filmList.join("; ") : "N/A"}`;
if (d.nodeType === NodeType.ACTOR) return `${d.id}\nTipo: Ator\nAtuou em: ${d.movieCount} filme(s)\nFilmes: ${d.filmList.length > 0 ? d.filmList.join("; ") : "N/A"}`;
if (d.nodeType === NodeType.FILM) {
const movieData = d.originalData;
return `Filme: ${d.id}\nAno: ${movieData.year}\nScore: ${movieData.score}\nDiretor: ${movieData.director}\nAtores: ${movieData.actor1}${movieData.actor2 ? ', ' + movieData.actor2 : ''}`;
}
return d.id;
});
nodeElements.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
simulation.on("tick", () => {
linkElements
.attr("x1", d => d.source.x).attr("y1", d => d.source.y)
.attr("x2", d => d.target.x).attr("y2", d => d.target.y);
nodeElements
.attr("cx", d => d.x).attr("cy", d => d.y);
});
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;
}
function clicked(event, d) {
event.stopPropagation();
nodeElements.attr('fill-opacity', nodeDeselectOpacity).attr('stroke-opacity', nodeDeselectOpacity);
linkElements.attr('stroke-opacity', linkDeselectOpacity);
d3.select(this)
.attr('fill-opacity', 1)
.attr('stroke-opacity', 1)
.attr('stroke', nodeHighlightStroke)
.attr('stroke-width', 2.5);
selectedNode = d;
svg.property("value", selectedNode).dispatch("input");
}
function deselected(event) {
nodeElements.attr('fill-opacity', 1)
.attr('stroke-opacity', 1)
.attr("stroke", d => (d.id === topDirectorId || d.id === topActorId) ? "#000000" : "#FFFFFF")
.attr("stroke-width", d => (d.id === topDirectorId || d.id === topActorId) ? 2 : 1);
linkElements.attr('stroke-opacity', 0.6);
selectedNode = null;
svg.property("value", selectedNode).dispatch("input");
}
if (typeof invalidation !== 'undefined') {
invalidation.then(() => simulation.stop());
}
return svg.node();
}