Public
Edited
Feb 22, 2021
5 forks
26 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
svg = {
// create plot
const plot = d3.create('svg')
.attr('width', width)
.attr('height', height)
.style('background-color', background);
// group for links
const plotLinks = plot
.append('g')
.attr('class', 'links')
.attr('transform',`translate(${margin.left}, ${margin.top}`);
// group for nodes
const plotNodes = plot
.append('g')
.attr('class', 'nodes')
.attr('transform',`translate(${margin.left}, ${margin.top}`);
// family names
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();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
simulation = d3.forceSimulation(drawNodes)
.force('collide', forceCollide)
.force('xAxis', d3.forceX(d => scaleX(d.family)).strength(1))
.force('yAxis', d3.forceY(d => scaleY(d.level)).strength(1))
// .force('cluster', forceCluster)
// .force("center", forceCenter)
.force("charge", foceManyBody)
.force("link", forceLink)
.alpha(1);
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
forceCluster = d3_cluster.forceCluster()
.centers(d => gen_levels[d.cluster])
.strength(0.5);
Insert cell
Insert cell
Insert cell
forceCenter = d3.forceCenter();
Insert cell
Insert cell
Insert cell
forceCollide = d3.forceCollide()
// .strength(1)
.radius(d => radius + padding)
// .iterations(0.5);
Insert cell
Insert cell
foceManyBody = d3.forceManyBody()
.distanceMin([radius + padding])
.distanceMax([padding * 4])
.strength(1)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more