svg = {
const plot = d3.create('svg')
.attr('width', width)
.attr('height', height)
.style('background-color', background);
const plotLinks = plot
.append('g')
.attr('class', 'links')
.attr('transform',`translate(${margin.left}, ${margin.top}`);
const plotNodes = plot
.append('g')
.attr('class', 'nodes')
.attr('transform',`translate(${margin.left}, ${margin.top}`);
const plotFamilies = plot
.append('g')
.attr('class', 'family-names')
.selectAll('text')
.data(families.filter(d => d.key !== 'Universe 1 * Kohler'))
.join('text')
.text(d => d.key.split(' * ')[1])
.attr('class', d => {
const universe = d.key.split(' ')[1];
const family = d.key.split(' * ')[1];
return `${family}_${universe}`;
})
.attr('x', d => scaleX(d.value))
.attr('y', d => scaleFamilyNames(d.key.split(' * ')[0]))
.style('font-weight', 'bold')
.style('font-size', '14px')
.style('fill', d => {
const colours = d.key.split(' * ');
return scaleColourUniverses(colours[0]);
});
// group for tooltip
const plotTooltip = plot
.append('g')
.attr('class', 'tooltip')
.attr('transform',`translate(${margin.left}, ${margin.top}`);
// links
const link = plotLinks
.selectAll("line")
.data(drawLinks.filter(d => d.name !== 'Unknown 1' && d.name !== 'Unknown 2'))
.join("path")
.attr('class', d => {
const universe_source = d.source.universe.split(' ')[1];
const universe_target = d.target.universe.split(' ')[1];
return `${d.source.Family_name}_${universe_source}_${d.target.Family_name}_{universe_target}`
})
.style('display', d => d.hide)
.style('fill', 'none')
.style("stroke-width", d => scaleThickness(d.type))
.style("stroke-dasharray", d => scaleDash(d.type))
.style('stroke', d => checkLinksColours(d));
// nodes
const node = plotNodes
.selectAll("circle")
.data(drawNodes.filter(d => d.Family_name !== 'Unknown'))
.join("circle")
.attr('class', d => {
const universe = d.universe.split(' ')[1];
return `${d.Family_name}_${universe}`;
})
.style("stroke", d => checkNodeColours(d))
.style("stroke-width", 3)
.attr("r", radius)
.style("fill", background)
.style('display', d => {
if (d.season > +season) return 'none';
return 'inherit'
})
.on('mouseover', d => mouseOver(d))
.on('mouseleave', mouseLeave);
simulation.on("tick", () => {
link
.attr('d', (d, i) => {
let sourceX = d.source.x;
let sourceY = d.source.y;
let targetX = d.target.x;
let targetY = d.target.y;
const parents = (d.parents.toString()).split(" - ");
if (d.type === 'Child' && parents.length > 1) {
if ((d.target.name === 'Regina Tiedemann' || d.target.name === 'Katharina Nielsen') && season < 3) {
} else {
const pt = findParents(parents, drawNodes);
const xyPt = pt.sort((a,b) => a.x - b.x);
const xPt1 = xyPt[0].x;
const xPt2 = xyPt[1].x;
sourceX = xPt1 + (xPt2 - xPt1) / 2;
const yPt1 = xyPt[0].y;
const yPt2 = xyPt[1].y;
sourceY = yPt1 + (yPt2 - yPt1) / 2;
}
}
return `M${sourceX},${sourceY} C ${sourceX},${(sourceY + targetY) / 2} ${targetX},${(sourceY + targetY) / 2} ${targetX},${targetY}`
})
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});
function mouseOver(d) {
const universe = d.universe.split(' ')[1];
const nameFamily = `${d.Family_name}_${universe}`;
// highlight nodes
plotNodes
.selectAll('circle')
.style('fill', e => {
if (e.name === d.name) return white;
return background;
})
.style('stroke', e => {
const thisNameFamily = `${e.Family_name}_${e.universe.split(' ')[1]}`;
if (e.name === 'Mikkel Nielsen / Michael Kahnwald' && e.universe === 'Universe 1' && d.name === 'Jonas Kahnwald / The Stranger') return white;
else if (e.name === 'Martha Nielsen' && e.universe === 'Universe 2' && d.name === 'Jonas Kahnwald / The Stranger') return white;
else if (e.name === 'Infinite' && d.name === 'Jonas Kahnwald / The Stranger') return white;
else if (d.name === 'Martha Nielsen' && d.universe === 'Universe 2' && e.name === 'Jonas Kahnwald / The Stranger') return white;
else if (d.name === 'Martha Nielsen' && d.universe === 'Universe 2' && e.name === 'Infinite') return white;
else if (d.name === 'Hannah Waller' && e.name === 'Jonas Waller') return white;
else if (e.name === 'Hannah Waller' && d.name === 'Jonas Waller') return white;
else if (thisNameFamily === nameFamily) return white;
else if (d.name === 'Infinite' && thisNameFamily === 'Nielsen_2') return white;
else if (d.name === 'Infinite' && thisNameFamily === 'Kahnwald_1') return white;
return checkNodeColours(e);
});
// highlight links
plotLinks
.selectAll('path')
.style('stroke', e => {
const source = `${e.source.Family_name}_${e.source.universe.split(' ')[1]}`;
const target = `${e.target.Family_name}_${e.target.universe.split(' ')[1]}`;
if (nameFamily === source || nameFamily === target) return white;
else if (e.target.name === 'Infinite' && d.name === 'Jonas Kahnwald / The Stranger') return white;
else if (e.target.name === 'Infinite' && d.name === 'Martha Nielsen' && d.universe === 'Universe 2') return white;
else if (d.name === 'Infinite' && source === 'Nielsen_2') return white;
else if (d.name === 'Infinite' && source === 'Kahnwald_1') return white;
else if (d.name === 'Infinite' && target === 'Nielsen_2') return white;
else if (d.name === 'Infinite' && target === 'Kahnwald_1') return white;
else return checkLinksColours(e);
});
// highlight family name
plot.select('.family-names')
.select(`.${nameFamily}`)
.style('fill', white);
// add information about selected person
let pX = 20;
let anchor = 'start';
if (d.x > width / 2) {
pX = -20;
anchor = 'end';
}
const plotRect = plotTooltip
.append('rect')
.attr('class', 'information')
.style('fill', background)
.style('opacity', 0.8)
.style('stroke', 'none');
plotTooltip
.append('text')
.attr('class', 'information')
.text(d.name)
.style('fill', white)
.style('text-anchor', anchor)
.attr('x', d.x + pX)
.attr('y', d.y + 5);
const rectWidth = Math.round(plotTooltip.select('text').node().getBoundingClientRect().width) + 20;
const rectHeight = Math.round(plotTooltip.select('text').node().getBoundingClientRect().height) + 20;
plotRect
.attr('width', rectWidth)
.attr('height', rectHeight)
.attr('x', () => {
if (d.x > width / 2) {
return (d.x + pX / 2 - rectWidth)
}
return (d.x + pX / 2)
})
.attr('y', (d.y - rectHeight / 2));
}
function mouseLeave() {
plotNodes
.selectAll('circle')
.style('fill', background)
.style("stroke", d => checkNodeColours(d));
plotLinks
.selectAll('path')
.style('stroke', d => checkLinksColours(d));
plot.select('.family-names')
.selectAll('text')
.style('fill', d => {
const colours = d.key.split(' * ');
return scaleColourUniverses(colours[0]);
});
plotTooltip
.selectAll('.information')
.remove();
}
invalidation.then(() => simulation.stop());
return plot.node();
}