Published
Edited
May 8, 2021
2 forks
9 stars
Insert cell
Insert cell
Insert cell
Insert cell
population = [
bonneOfLuxembourg,
catherineOfFrance,
catherineOfFranceCountessOfMontpensier,
charlesFifthOfFrance,
charlesSixthOfFrance,
charlesSeventhOfFrance,
henryFifthOfEngland,
henrySixthOfEngland,
isabeauOfBavaria,
isabellaOfFrance,
joanFirstOfAuvergne,
joannaOfBourbon,
joanOfFrance,
johnOfBerry,
johnOfTouraine,
johnSecondOfFrance,
louisFirstOfAnjou,
louisFirstOfOrleans,
louisOfGuyenne,
marieOfAnjou,
michelleOfFrance,
philipOfBurgundy,
philipSecondTheBold
]
Insert cell
bonneOfLuxembourg = ({
id: "bonne-of-luxembourg",
gender: "https://schema.org/Female",
givenName: ["Bonne", "Jutta"],
additionalName: "of Luxembourg",
familyName: "Luxembourg",
honorificSuffix: "Duchess of Normandy",
birthDate: "1315-05-20",
deathDate: "1349-09-11",
url: "https://en.wikipedia.org/wiki/Bonne_of_Luxembourg"
})
Insert cell
catherineOfFrance = ({
id: "catherine-of-france",
gender: "https://schema.org/Female",
givenName: "Catherine",
additionalName: "of France",
familyName: "Valois",
honorificSuffix: "Queen of England",
birthDate: "1401-10-27",
deathDate: "1437-01-03",
url: "https://en.wikipedia.org/wiki/Catherine_of_Valois"
})
Insert cell
catherineOfFranceCountessOfMontpensier = ({
id: "catherine-of-france-countess-of-montpensier",
gender: "https://schema.org/Female",
givenName: "Catherine",
additionalName: "of France",
familyName: "Valois",
honorificSuffix: "Countess of Montpensier",
birthDate: "1378-02-04",
deathDate: "1388-11",
url: "https://en.wikipedia.org/wiki/Catherine_of_France,_Countess_of_Montpensier"
})
Insert cell
charlesFifthOfFrance = ({
id: "charles-fifth-of-france",
gender: "https://schema.org/Male",
alternateName: "Charles V of France",
givenName: "Charles V",
additionalName: "the Wise",
familyName: "Valois",
honorificSuffix: "King of France",
birthDate: "1338-01-21",
deathDate: "1380-09-16",
url: "https://en.wikipedia.org/wiki/Charles_V_of_France"
})
Insert cell
charlesSixthOfFrance = ({
id: "charles-sixth-of-france",
gender: "https://schema.org/Male",
alternateName: "Charles VI of France",
givenName: "Charles VI",
additionalName: ["the Beloved", "the Mad"],
familyName: "Valois",
honorificSuffix: "King of France",
birthDate: "1369-12-03",
deathDate: "1422-10-21",
url: "https://en.wikipedia.org/wiki/Charles_VI_of_France"
})
Insert cell
charlesSeventhOfFrance = ({
id: "charles-seventh-of-france",
gender: "https://schema.org/Male",
alternateName: "Charles VII of France",
givenName: "Charles VII",
additionalName: ["the Well-Served", "the Victorious"],
familyName: "Valois",
honorificSuffix: "Dauphin / King of France",
birthDate: "1403-02-22",
deathDate: "1461-06-22",
url: "https://en.wikipedia.org/wiki/Charles_VII_of_France"
})
Insert cell
henryFifthOfEngland = ({
id: "henry-fifth-of-england",
gender: "https://schema.org/Male",
alternateName: "Henry V of England",
givenName: "Henry V",
additionalName: "of Monmouth",
familyName: ["Lancaster", "Plantagenet"],
honorificSuffix: "King of England",
birthDate: "1386-09-16",
deathDate: "1422-08-31",
url: "https://en.wikipedia.org/wiki/Henry_V_of_England"
})
Insert cell
henrySixthOfEngland = ({
id: "henry-sixth-of-england",
gender: "https://schema.org/Male",
givenName: "Henry VI",
familyName: ["Lancaster", "Plantagenet"],
honorificSuffix: ["King of England", "disputed King of France"],
birthDate: "1421-12-06",
deathDate: "1471-05-21",
url: "https://en.wikipedia.org/wiki/Henry_VI_of_England"
})
Insert cell
isabeauOfBavaria = ({
id: "isabeau-of-bavaria",
gender: "https://schema.org/Female",
givenName: ["Isabeau", "Isabelle", "Elisabeth"],
additionalName: ["of Bavaria-Ingolstadt", "of Bavaria"],
familyName: "Wittelsbach",
honorificSuffix: "Queen of France",
birthDate: "1370",
birthPlace: "Munich",
deathDate: "1435",
deathPlace: "Paris",
url: "https://en.wikipedia.org/wiki/Isabeau_of_Bavaria"
})
Insert cell
isabellaOfFrance = ({
id: "isabella-of-france",
gender: "https://schema.org/Female",
givenName: "Isabella",
additionalName: "of France",
familyName: "Valois",
honorificSuffix: "Queen of England / Duchess of Orléans",
birthDate: "1389-10-09",
deathDate: "1409-09-13",
url: "https://en.wikipedia.org/wiki/Isabella_of_Valois"
})
Insert cell
joanFirstOfAuvergne = ({
id: "joan-first-of-auvergne",
gender: "https://schema.org/Female",
alternateName: "Joan I of Auvergne",
givenName: "Joan I",
familyName: "Auvergne",
honorificSuffix: "Countess of Auvergne and Boulogne / Queen of France",
birthDate: "1326-05-08",
deathDate: "1360-09-29",
url: "https://en.wikipedia.org/wiki/Joan_I,_Countess_of_Auvergne"
})
Insert cell
joannaOfBourbon = ({
id: "joanna-of-bourbon",
gender: "https://schema.org/Female",
givenName: "Joanna",
additionalName: "of Bourbon",
familyName: "Bourbon",
honorificSuffix: "Queen of France",
birthDate: "1338-02-03",
deathDate: "1378-02-16",
url: "https://en.wikipedia.org/wiki/Joanna_of_Bourbon"
})
Insert cell
joanOfFrance = ({
id: "joan-of-france",
gender: "https://schema.org/Female",
givenName: "Joan",
additionalName: "of France",
familyName: "Valois",
honorificSuffix: "Duchess of Brittany",
birthDate: "1391-01-24",
deathDate: "1433-09-27",
url: "https://en.wikipedia.org/wiki/Joan_of_France,_Duchess_of_Brittany"
})
Insert cell
johnOfBerry = ({
id: "john-of-berry",
gender: "https://schema.org/Male",
givenName: "John",
additionalName: "the Magnificent",
familyName: "Valois",
honorificSuffix: ["Duke of Berry and Auvergne", "Count of Poitiers and Montpensier"],
birthDate: "1340-11-30",
deathDate: "1416-06-15",
url: "https://en.wikipedia.org/wiki/John,_Duke_of_Berry"
})
Insert cell
johnOfTouraine = ({
id: "john-of-touraine",
gender: "https://schema.org/Male",
givenName: "John",
familyName: "Valois",
honorificSuffix: ["Dauphin of France", "Duke of Touraine"],
birthDate: "1398",
deathDate: "1417",
})
Insert cell
johnSecondOfFrance = ({
id: "john-second-of-france",
gender: "https://schema.org/Male",
alternateName: "John II of France",
givenName: "John II",
additionalName: "the Good",
familyName: "Valois",
honorificSuffix: "King of France",
birthDate: "1319-04-26",
deathDate: "1364-04-08",
url: "https://en.wikipedia.org/wiki/John_II_of_France"
})
Insert cell
louisFirstOfAnjou = ({
id: "louis-first-of-anjou",
gender: "https://schema.org/Male",
alternateName: "Louis I of Anjou",
givenName: "Louis I",
familyName: "Valois-Anjou",
honorificSuffix: "Duke of Anjou",
birthDate: "1339-06-23",
deathDate: "1384-09-20",
url: "https://en.wikipedia.org/wiki/Louis_I_of_Anjou"
})
Insert cell
louisFirstOfOrleans = ({
id: "louis-first-of-orleans",
gender: "https://schema.org/Male",
alternateName: "Louis I of Orléans",
givenName: "Louis I",
familyName: "Valois-Orléans",
honorificSuffix: "Duke of Orléans",
birthDate: "1372-03-13",
deathDate: "1407-11-23",
url: "https://en.wikipedia.org/wiki/Louis_I,_Duke_of_Orl%C3%A9ans"
})
Insert cell
louisOfGuyenne = ({
id: "louis-of-guyenne",
gender: "https://schema.org/Male",
givenName: "Louis",
familyName: "Valois",
honorificSuffix: ["Dauphin of France", "Duke of Guyenne"],
birthDate: "1397",
deathDate: "1415",
url: "https://en.wikipedia.org/wiki/Louis,_Duke_of_Guyenne"
})
Insert cell
marieOfAnjou = ({
id: "marie-of-anjou",
gender: "https://schema.org/Female",
givenName: "Marie",
additionalName: "of Anjou",
familyName: "Valois-Anjou",
honorificSuffix: "Queen of France",
birthDate: "1404-10-14",
deathDate: "1463-11-29",
url: "https://en.wikipedia.org/wiki/Marie_of_Anjou"
})
Insert cell
michelleOfFrance = ({
id: "michelle-of-france",
gender: "https://schema.org/Female",
givenName: "Michelle",
additionalName: "of France",
familyName: "Valois",
honorificSuffix: "Duchess of Burgundy",
birthDate: "1395-02-11",
deathDate: "1422-06-08",
url: "https://en.wikipedia.org/wiki/Michelle_of_Valois"
})
Insert cell
philipOfBurgundy = ({
id: "philip-of-burgundy",
gender: "https://schema.org/Male",
alternateName: "Philip of Burgundy",
givenName: "Philip I",
familyName: "Burgundy",
honorificSuffix: "Count of Auvergne and Boulogne",
birthDate: "1323-11-10",
deathDate: "1346-08-10",
url: "https://en.wikipedia.org/wiki/Philip_I,_Count_of_Auvergne"
})
Insert cell
philipSecondTheBold = ({
id: "philip-second-the-bold",
gender: "https://schema.org/Male",
alternateName: [
"Philip II, Duke of Burgundy and Count of Flanders",
"Philip IV, Count of Artois and Burgundy",
],
givenName: "Philip II",
additionalName: "the Bold",
familyName: "Valois-Burgundy",
honorificSuffix: ["Duke of Touraine / Burgundy", "Count of Nevers and Rethel / Count Palatine of Burgundy, Count of Artois and Flanders"],
birthDate: "1342-01-17",
deathDate: "1404-04-27",
url: "https://en.wikipedia.org/wiki/Philip_the_Bold"
})
Insert cell
areUnique = {
const identifiers = population.map(d => d.id);
return identifiers.length === new Set(identifiers).size;
}
Insert cell
marriages = [
{
weddingDate: "1332-06-28",
husband: johnSecondOfFrance,
wife: bonneOfLuxembourg,
children: [
charlesFifthOfFrance,
louisFirstOfAnjou,
johnOfBerry,
philipSecondTheBold
]
},
{
weddingDate: "1338",
husband: philipOfBurgundy,
wife: joanFirstOfAuvergne
},
{
weddingDate: "1350-02-13",
husband: johnSecondOfFrance,
wife: joanFirstOfAuvergne
},
{
weddingDate: "1350-04-08",
husband: charlesFifthOfFrance,
wife: joannaOfBourbon,
children: [
charlesSixthOfFrance,
louisFirstOfOrleans,
catherineOfFranceCountessOfMontpensier
]
},
{
weddingDate: "1385-07-13",
husband: charlesSixthOfFrance,
wife: isabeauOfBavaria,
children: [
isabellaOfFrance,
joanOfFrance,
michelleOfFrance,
louisOfGuyenne,
johnOfTouraine,
catherineOfFrance,
charlesSeventhOfFrance
]
},
{
weddingDate: "1420-06-02",
husband: henryFifthOfEngland,
wife: catherineOfFrance,
children: [
henrySixthOfEngland
]
},
{
weddingDate: "1422-12-12",
husband: charlesSeventhOfFrance,
wife: marieOfAnjou
}
]
Insert cell
findAncestorMarriages = (marriages, childId) => {
let ancestorMarriages = [];
const findAncestorMarriages = (marriages, childId) => {
const marriage = findMarriage(marriages, childId);
if (marriage) {
ancestorMarriages.push(marriage);
findAncestorMarriages(marriages, marriage.husband.id);
findAncestorMarriages(marriages, marriage.wife.id);
}
}
findAncestorMarriages(marriages, childId);
return ancestorMarriages;
}
Insert cell
findMarriage = (marriages, childId) => {
for (const marriage of marriages) {
if (marriage.children) {
for (const child of marriage.children) {
if (child.id === childId) {
return marriage;
}
}
}
}
return undefined;
}
Insert cell
Insert cell
genealogyChart = (population, marriages) => {
const svg = createCanvas();
appendTopAxis(svg);
const plane = createPlane(svg);
marriages.forEach(marriage => appendMarriage(plane, population, marriage));
population.forEach((person, personIndex) => appendPerson(plane, person, personIndex));
return svg;
}
Insert cell
createCanvas = () =>
d3.create("svg")
.attr("class", "genealogy")
.attr("width", width)
.attr("height", height);
Insert cell
d3 = require("d3@6")
Insert cell
height = margin.top + (population.length * personHeight) + 10;
Insert cell
Insert cell
Insert cell
appendTopAxis = (container) =>
container.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${margin.top})`)
.call(d3.axisTop(xScale))
.call(cleanAxis);
Insert cell
xScale = d3.scaleTime().range(range).domain(domain);
Insert cell
range = [margin.left, width - margin.right];
Insert cell
domain = [
d3.min(population, d => d3.isoParse(d.birthDate)),
d3.max(population, d => d3.isoParse(d.deathDate))
]
Insert cell
cleanAxis = (group) => {
const attributes = ["fill", "font-family", "font-size", "text-anchor"];
attributes.forEach(d => group.attr(d, null));
group.select(".domain").remove();
group.selectAll(".tick").attr("opacity", null);
group.selectAll(".tick line").attr("stroke", null);
group.selectAll(".tick text").attr("fill", null);
}
Insert cell
createPlane = (container) =>
container.append("g")
.attr("class", "plane")
.attr("transform", `translate(0,${margin.top + 15})`);
Insert cell
appendMarriage = (container, population, marriage) => {
const marriageContainer = container.append("g")
.attr("class", "marriage")
.attr("data-husband-id", marriage.husband.id)
.attr("data-wife-id", marriage.wife.id);

const husbandIndex = population.findIndex(d => d.id === marriage.husband.id);
const wifeIndex = population.findIndex(d => d.id === marriage.wife.id);
const weddingDate = d3.isoParse(marriage.weddingDate);
const weddingBar = {
x: nice(xScale(weddingDate)),
y: personHeight * Math.min(husbandIndex, wifeIndex),
height: personHeight * Math.abs(husbandIndex - wifeIndex)
};

marriageContainer.append("rect")
.attr("class", "wedding-bar")
.attr("x", weddingBar.x)
.attr("y", weddingBar.y)
.attr("width", weddingBarWidth)
.attr("height", weddingBar.height);

if (marriage.children)
{
marriage.children.map(child => {
const personIndex = population.findIndex(d => d.id === child.id);
const childBar = {
x: weddingBar.x,
y: personHeight * personIndex,
width: nice(xScale(d3.isoParse(child.birthDate)) - weddingBar.x)
};

marriageContainer.append("rect")
.attr("class", "child-bar")
.attr("x", childBar.x)
.attr("y", childBar.y)
.attr("width", childBar.width)
.attr("height", lifetimeBarHeight);
});
}
}
Insert cell
nice = (value) => {
return Math.round(value);
const floor = Math.floor(value);
const ceiling = Math.ceil(value);
const decimalPart = value - floor;
const lowerBound = decimalPart < 0.5 ? floor - 0.5 : ceiling - 0.5;
const upperBound = decimalPart < 0.5 ? floor + 0.5 : ceiling + 0.5;
return (value - lowerBound) < 0.5 ? lowerBound : upperBound;
}
Insert cell
Insert cell
Insert cell
appendPerson = (container, person, personIndex) => {
let personContainer = container.append("g")
.attr("class", "person")
.attr("data-id", person.id);

const lifetimeStartPoint = {
x: xScale(d3.isoParse(person.birthDate)),
y: personHeight * personIndex
};

const lifetimeEndPoint = {
x: xScale(d3.isoParse(person.deathDate)),
y: lifetimeStartPoint.y
};

const lifetimeWidth = lifetimeEndPoint.x - lifetimeStartPoint.x;

if (person.url) {
personContainer = personContainer.append("a")
.attr("href", person.url)
.attr("target", "_blank"); // hangs in the notebook's iframe otherwise

personContainer.append("rect")
.attr("class", "hoverable-area")
.attr("x", lifetimeStartPoint.x)
.attr("y", lifetimeStartPoint.y)
.attr("width", lifetimeWidth)
.attr("height", personHeight);
}

personContainer.append("rect")
.attr("class", "lifetime-bar")
.attr("x", lifetimeStartPoint.x)
.attr("y", lifetimeStartPoint.y)
.attr("width", lifetimeWidth)
.attr("height", lifetimeBarHeight);

const text = getText(person);
const textWidth = measureWidth(text);
const remainingPlaneWidth = range[1] - lifetimeStartPoint.x;
if (textWidth <= remainingPlaneWidth) {
personContainer.append("text")
.attr("x", lifetimeStartPoint.x)
.attr("y", lifetimeStartPoint.y + 12)
.attr("text-anchor", "start")
.text(text);
} else {
personContainer.append("text")
.attr("x", lifetimeEndPoint.x)
.attr("y", lifetimeEndPoint.y + 12)
.attr("text-anchor", "end")
.text(text);
}
}
Insert cell
getText = (person) => {
let text = first(person.givenName);
if (person.additionalName) {
text += ` ${last(person.additionalName)}`;
}
if (person.honorificSuffix) {
text += `, ${all(person.honorificSuffix)}`;
}
return text;
}
Insert cell
first = (value) => Array.isArray(value) ? value[0] : value;
Insert cell
last = (value) => Array.isArray(value) ? value[value.length - 1] : value;
Insert cell
all = (value) => Array.isArray(value) ? value.join(", ") : value;
Insert cell
measureWidth = (text) => {
const context = document.createElement("canvas")
.getContext("2d");
context.font = "10px sans-serif" // unnecessary as the default font
return context.measureText(text).width;
}
Insert cell
html`<style>
.tick line {
stroke: currentColor;
}

.tick text {
fill: currentColor;
text-anchor: middle;
}

.tick text,
.person text {
font-family: sans-serif;
font-size: 10px;
}

.person text,
.person a[href] text {
fill: #808080;
}

.person a[href] .hoverable-area {
fill: #ffffff; /* has to be set, not 'none' */
fill-opacity: 0;
}

.person a[href]:hover {
text-decoration: none;
}

.person.hovered a[href] text,
.person a[href]:hover text {
fill: #3182bd;
}

.person.hovered .lifetime-bar,
.person a[href]:hover .lifetime-bar {
fill: #3182bd;
}

.marriage {
fill: #efefef;
}

.marriage.hovered {
fill: #f3f8fb;
}
</style>`
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more