Module:FamilyTree: Difference between revisions
From KB Lexicon
No edit summary |
No edit summary |
||
| Line 262: | Line 262: | ||
-- ========================================= | -- ========================================= | ||
local function | local function relationshipBadge(relType) | ||
local | if not isRealValue(relType) then | ||
return nil | |||
end | |||
local t = mw.ustring.lower(relType) | |||
if t:find('adopt') then | |||
return 'adopted' | |||
end | |||
if t:find('step') then | |||
return 'step' | |||
end | |||
if t:find('bio') then | |||
return nil | |||
end | |||
return relType | |||
end | |||
local function findUnionBetween(people, name1, name2) | |||
if not isRealValue(name1) or not isRealValue(name2) then | |||
return nil | |||
end | |||
local person = people[name1] | |||
if not person or not person.unions then | |||
return nil | |||
end | |||
for _, union in ipairs(person.unions) do | |||
if union.partner == name2 then | |||
return union | |||
end | |||
end | |||
return nil | |||
end | |||
local function getMarriageYear(union) | |||
if not union then | |||
return nil | |||
end | |||
local raw = union.marriageDate or union.engagementDate or union.startDate | |||
if not isRealValue(raw) then | |||
return nil | |||
end | |||
local year = tostring(raw):match('^(%d%d%d%d)') | |||
return year or tostring(raw) | |||
end | |||
local function getParents(people, root) | |||
local person = people[root] | local person = people[root] | ||
if not person then | if not person then | ||
return | return {} | ||
end | end | ||
local parents = uniq(person.parents) | |||
sortNames(people, parents) | |||
return parents | |||
end | |||
local function getGrandparents(people, root) | |||
local out = {} | |||
local parents = getParents(people, root) | |||
for _, parentName in ipairs( | for _, parentName in ipairs(parents) do | ||
local parent = people[parentName] | local parent = people[parentName] | ||
if parent then | if parent then | ||
| Line 278: | Line 338: | ||
end | end | ||
out = uniq(out) | |||
sortNames(people, out) | |||
return out | |||
end | end | ||
| Line 302: | Line 364: | ||
end | end | ||
out = uniq(out) | |||
sortNames(people, out) | |||
return out | |||
end | end | ||
| Line 322: | Line 386: | ||
for _, v in ipairs(grandparents) do addUnique(out, v) end | for _, v in ipairs(grandparents) do addUnique(out, v) end | ||
out = uniq(out) | |||
sortNames(people, out) | |||
return out | |||
end | end | ||
local function getRootSiblingSequence(people, root) | local function getRootSiblingSequence(people, root) | ||
local siblings = getSiblings(people, root) | local siblings = getSiblings(people, root) | ||
local seq = {} | local seq = {} | ||
local inserted = false | local inserted = false | ||
| Line 411: | Line 422: | ||
for _, link in ipairs(person.childLinks) do | for _, link in ipairs(person.childLinks) do | ||
local key | local key | ||
if isRealValue(link.unionID) then | if isRealValue(link.unionID) then | ||
key = 'union::' .. link.unionID | key = 'union::' .. link.unionID | ||
| Line 465: | Line 475: | ||
-- ========================================= | -- ========================================= | ||
local function renderCard(people, name, badgeText) | local function renderCard(people, name, badgeText, extraClass) | ||
if not isRealValue(name) then | if not isRealValue(name) then | ||
return nil | return nil | ||
| Line 473: | Line 483: | ||
local card = html.create('div') | local card = html.create('div') | ||
card:addClass(' | card:addClass('kbftv2-card') | ||
if isRealValue(extraClass) then | |||
card:addClass(extraClass) | |||
end | |||
card:wikitext(makeLink(person.name, person.displayName)) | card:wikitext(makeLink(person.name, person.displayName)) | ||
if isRealValue(badgeText) then | if isRealValue(badgeText) then | ||
card:tag('div') | card:tag('div') | ||
:addClass(' | :addClass('kbftv2-badge') | ||
:wikitext(badgeText) | :wikitext(badgeText) | ||
end | end | ||
| Line 485: | Line 499: | ||
end | end | ||
local function | local function renderSingleCard(people, name, extraClass) | ||
local wrap = html.create('div') | local wrap = html.create('div') | ||
wrap:addClass(' | wrap:addClass('kbftv2-single') | ||
wrap:node(renderCard(people, name)) | wrap:node(renderCard(people, name, nil, extraClass)) | ||
return wrap | return wrap | ||
end | end | ||
local function renderCouple(people, leftName, rightName, marriageYear) | local function renderCouple(people, leftName, rightName, marriageYear, leftClass, rightClass) | ||
if isRealValue(leftName) and isRealValue(rightName) then | if isRealValue(leftName) and isRealValue(rightName) then | ||
local wrap = html.create('div') | local wrap = html.create('div') | ||
wrap:addClass(' | wrap:addClass('kbftv2-couple') | ||
wrap:node(renderCard(people, leftName)) | wrap:node(renderCard(people, leftName, nil, leftClass)) | ||
local marriage = wrap:tag('div') | local marriage = wrap:tag('div') | ||
marriage:addClass(' | marriage:addClass('kbftv2-marriage') | ||
if isRealValue(marriageYear) then | if isRealValue(marriageYear) then | ||
marriage:tag('div') | marriage:tag('div') | ||
:addClass(' | :addClass('kbftv2-marriage-year') | ||
:wikitext(marriageYear) | :wikitext(marriageYear) | ||
end | end | ||
marriage:tag('div') | marriage:tag('div') | ||
:addClass(' | :addClass('kbftv2-marriage-line') | ||
wrap:node(renderCard(people, rightName)) | wrap:node(renderCard(people, rightName, nil, rightClass)) | ||
return wrap | return wrap | ||
end | end | ||
if isRealValue(leftName) then | if isRealValue(leftName) then | ||
return | return renderSingleCard(people, leftName, leftClass) | ||
end | end | ||
if isRealValue(rightName) then | if isRealValue(rightName) then | ||
return | return renderSingleCard(people, rightName, rightClass) | ||
end | end | ||
| Line 526: | Line 540: | ||
end | end | ||
local function | local function renderGenerationRow(units) | ||
local row = html.create('div') | local row = html.create('div') | ||
row:addClass(' | row:addClass('kbftv2-row') | ||
for _, | for _, unit in ipairs(units) do | ||
row:node( | if unit then | ||
row:node(unit) | |||
end | |||
end | end | ||
| Line 537: | Line 553: | ||
end | end | ||
local function | local function renderChildCards(people, children) | ||
local childrenWrap = html.create('div') | |||
childrenWrap:addClass('kbftv2-unit-children') | |||
for _, child in ipairs(children) do | |||
childrenWrap:node( | |||
renderCard( | |||
people, | |||
child.name, | |||
relationshipBadge(child.relationshipType) | |||
) | |||
) | |||
end | end | ||
local | return childrenWrap | ||
end | |||
local function renderFamilyUnit(people, root, group) | |||
local unit = html.create('div') | |||
unit:addClass('kbftv2-unit') | |||
local | local top = unit:tag('div') | ||
top:addClass('kbftv2-unit-top') | |||
if isRealValue(group.partner) then | |||
local | local union = findUnionBetween(people, root, group.partner) | ||
local | local marriageYear = getMarriageYear(union) | ||
local | top:node(renderCouple(people, root, group.partner, marriageYear, 'kbftv2-root-echo', nil)) | ||
else | |||
local soloLabel = top:tag('div') | |||
soloLabel:addClass('kbftv2-solo-label') | |||
soloLabel:wikitext((people[root] and people[root].displayName) or root) | |||
end | |||
if #group.children > 0 then | |||
unit:tag('div') | |||
:addClass('kbftv2-unit-drop') | |||
unit:node(renderChildCards(people, group.children)) | |||
end | end | ||
return | return unit | ||
end | end | ||
local function | local function renderUpperCoupleGeneration(people, couples) | ||
if #couples == 0 then | |||
return nil | return nil | ||
end | end | ||
local | local gen = html.create('div') | ||
gen:addClass('kbftv2-generation') | |||
local units = {} | |||
for _, pair in ipairs(couples) do | |||
table.insert(units, renderCouple(people, pair[1], pair[2], nil)) | |||
end | end | ||
gen:node(renderGenerationRow(units)) | |||
gen:node( | |||
return gen | return gen | ||
end | end | ||
local function | local function buildGrandparentCouples(people, root) | ||
local | local parents = getParents(people, root) | ||
local couples = {} | |||
for _, parentName in ipairs(parents) do | |||
local parent = people[parentName] | |||
if parent then | |||
local gp = uniq(parent.parents) | |||
sortNames(people, gp) | |||
if #gp > 0 then | |||
table.insert(couples, { gp[1], gp[2] }) | |||
end | |||
end | |||
end | |||
return couples | |||
end | |||
local | local function buildParentCouples(people, root) | ||
local parents = getParents(people, root) | |||
if #parents == 0 then | |||
return {} | |||
end | |||
return { { parents[1], parents[2] } } | |||
end | |||
local function renderFocalGeneration(people, root) | |||
local sequence = getRootSiblingSequence(people, root) | |||
local | local gen = html.create('div') | ||
gen:addClass('kbftv2-generation') | |||
local units = {} | |||
for _, name in ipairs(sequence) do | for _, name in ipairs(sequence) do | ||
if name == root then | |||
table.insert(units, renderSingleCard(people, name, 'kbftv2-root-focus')) | |||
else | |||
table.insert(units, renderSingleCard(people, name)) | |||
end | |||
end | |||
local groupWrap = gen:tag('div') | |||
groupWrap:addClass('kbftv2-sibling-group') | |||
groupWrap:tag('div') | |||
:addClass('kbftv2-sibling-spine') | |||
groupWrap:node(renderGenerationRow(units)) | |||
return | return gen | ||
end | end | ||
| Line 618: | Line 673: | ||
local gen = html.create('div') | local gen = html.create('div') | ||
gen:addClass(' | gen:addClass('kbftv2-generation') | ||
local units = {} | |||
for _, group in ipairs(groups) do | for _, group in ipairs(groups) do | ||
table.insert(units, renderFamilyUnit(people, root, group)) | |||
end | end | ||
gen:node(renderGenerationRow(units)) | |||
return gen | return gen | ||
end | end | ||
| Line 671: | Line 695: | ||
local connected = getConnectedPeople(people, root) | local connected = getConnectedPeople(people, root) | ||
local node = html.create('div') | local node = html.create('div') | ||
node:addClass(' | node:addClass('kbftv2-tree') | ||
node:tag('div') | node:tag('div') | ||
:addClass(' | :addClass('kbftv2-title') | ||
:wikitext('Connected to ' .. makeLink(person.name, person.displayName)) | :wikitext('Connected to ' .. makeLink(person.name, person.displayName)) | ||
local | local units = {} | ||
for _, name in ipairs(connected) do | for _, name in ipairs(connected) do | ||
table.insert(units, renderSingleCard(people, name)) | |||
end | end | ||
local gen = node:tag('div') | |||
gen:addClass('kbftv2-generation') | |||
gen:node(renderGenerationRow(units)) | |||
return tostring(node) | return tostring(node) | ||
| Line 697: | Line 722: | ||
local node = html.create('div') | local node = html.create('div') | ||
node:addClass(' | node:addClass('kbftv2-tree') | ||
node:tag('div') | node:tag('div') | ||
:addClass(' | :addClass('kbftv2-title') | ||
:wikitext(makeLink(person.name, person.displayName)) | :wikitext(makeLink(person.name, person.displayName)) | ||
local function addSection(label, | local function addSection(label, names) | ||
names = uniq(names) | |||
if # | if #names == 0 then | ||
return | return | ||
end | end | ||
sortNames(people, names) | |||
node:tag('div') | node:tag('div') | ||
:addClass(' | :addClass('kbftv2-section-title') | ||
:wikitext(label) | :wikitext(label) | ||
node:tag('div') | local units = {} | ||
for _, name in ipairs(names) do | |||
table.insert(units, renderSingleCard(people, name)) | |||
end | |||
local gen = node:tag('div') | |||
gen:addClass('kbftv2-generation') | |||
gen:node(renderGenerationRow(units)) | |||
end | end | ||
| Line 733: | Line 764: | ||
local node = html.create('div') | local node = html.create('div') | ||
node:addClass(' | node:addClass('kbftv2-tree') | ||
node:tag('div') | node:tag('div') | ||
:addClass(' | :addClass('kbftv2-title') | ||
:wikitext('Family Tree: ' .. makeLink(person.name, person.displayName)) | :wikitext('Family Tree: ' .. makeLink(person.name, person.displayName)) | ||
local | local gpGen = renderUpperCoupleGeneration(people, buildGrandparentCouples(people, root)) | ||
if gpGen then | |||
node:node(gpGen) | |||
node:tag('div'):addClass('kbftv2-connector') | |||
if | |||
node:node( | |||
node:tag('div'):addClass(' | |||
end | end | ||
local parentGen = | local parentGen = renderUpperCoupleGeneration(people, buildParentCouples(people, root)) | ||
if parentGen then | if parentGen then | ||
node:node(parentGen) | node:node(parentGen) | ||
node:tag('div'):addClass(' | node:tag('div'):addClass('kbftv2-connector') | ||
end | end | ||
node:node( | node:node(renderFocalGeneration(people, root)) | ||
local | local familyGen = renderFamilyGroupsGeneration(people, root) | ||
if | if familyGen then | ||
node:tag('div'):addClass(' | node:tag('div'):addClass('kbftv2-connector') | ||
node:node( | node:node(familyGen) | ||
end | end | ||
Revision as of 07:39, 30 March 2026
Documentation for this module may be created at Module:FamilyTree/doc
local p = {}
local cargo = mw.ext.cargo
local html = mw.html
-- =========================================
-- Helpers
-- =========================================
local function trim(s)
if s == nil then
return nil
end
s = tostring(s)
s = mw.text.trim(s)
if s == '' then
return nil
end
return s
end
local function isRealValue(v)
v = trim(v)
if not v then
return false
end
local lowered = mw.ustring.lower(v)
return lowered ~= 'unknown' and lowered ~= 'none' and lowered ~= 'n/a'
end
local function addUnique(list, value)
if not isRealValue(value) then
return
end
for _, existing in ipairs(list) do
if existing == value then
return
end
end
table.insert(list, value)
end
local function uniq(list)
local out = {}
local seen = {}
for _, v in ipairs(list or {}) do
if isRealValue(v) and not seen[v] then
seen[v] = true
table.insert(out, v)
end
end
return out
end
local function getArg(frame, key)
local v = frame.args[key]
if isRealValue(v) then
return trim(v)
end
local parent = frame:getParent()
if parent then
v = parent.args[key]
if isRealValue(v) then
return trim(v)
end
end
return nil
end
local function getRoot(frame)
return getArg(frame, 'root') or getArg(frame, 'person') or mw.title.getCurrentTitle().text
end
local function makeLink(name, displayName)
if not isRealValue(name) then
return ''
end
displayName = trim(displayName) or name
return string.format('[[%s|%s]]', name, displayName)
end
local function ensurePerson(people, name)
name = trim(name)
if not isRealValue(name) then
return nil
end
if not people[name] then
people[name] = {
name = name,
displayName = name,
parents = {},
children = {},
partners = {},
unions = {},
childLinks = {}
}
end
return people[name]
end
local function sortNames(people, names)
table.sort(names, function(a, b)
local ad = (people[a] and people[a].displayName) or a
local bd = (people[b] and people[b].displayName) or b
return mw.ustring.lower(ad) < mw.ustring.lower(bd)
end)
end
-- =========================================
-- Data loading
-- =========================================
local function loadCharacters()
local results = cargo.query(
'Characters',
'Page,DisplayName',
{ limit = 5000 }
)
local people = {}
for _, row in ipairs(results) do
local page = trim(row.Page)
local displayName = trim(row.DisplayName)
if isRealValue(page) then
people[page] = {
name = page,
displayName = displayName or page,
parents = {},
children = {},
partners = {},
unions = {},
childLinks = {}
}
end
end
return people
end
local function loadParentChild(people)
local results = cargo.query(
'ParentChild',
'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder',
{ limit = 5000 }
)
for _, row in ipairs(results) do
local child = trim(row.Child)
local p1 = trim(row.Parent1)
local p2 = trim(row.Parent2)
local unionID = trim(row.UnionID)
local relationshipType = trim(row.RelationshipType)
local birthOrder = tonumber(trim(row.BirthOrder)) or 999
if isRealValue(child) then
ensurePerson(people, child)
if isRealValue(p1) then
ensurePerson(people, p1)
addUnique(people[child].parents, p1)
addUnique(people[p1].children, child)
table.insert(people[p1].childLinks, {
child = child,
otherParent = p2,
unionID = unionID,
relationshipType = relationshipType,
birthOrder = birthOrder
})
end
if isRealValue(p2) then
ensurePerson(people, p2)
addUnique(people[child].parents, p2)
addUnique(people[p2].children, child)
table.insert(people[p2].childLinks, {
child = child,
otherParent = p1,
unionID = unionID,
relationshipType = relationshipType,
birthOrder = birthOrder
})
end
end
end
end
local function loadUnions(people)
local results = cargo.query(
'Unions',
'UnionID,Partner1,Partner2,UnionType,Status,StartDate,EndDate,MarriageDate,DivorceDate,EngagementDate',
{ limit = 5000 }
)
for _, row in ipairs(results) do
local p1 = trim(row.Partner1)
local p2 = trim(row.Partner2)
if isRealValue(p1) then
ensurePerson(people, p1)
end
if isRealValue(p2) then
ensurePerson(people, p2)
end
if isRealValue(p1) and isRealValue(p2) then
addUnique(people[p1].partners, p2)
addUnique(people[p2].partners, p1)
table.insert(people[p1].unions, {
unionID = trim(row.UnionID),
partner = p2,
unionType = trim(row.UnionType),
status = trim(row.Status),
startDate = trim(row.StartDate),
endDate = trim(row.EndDate),
marriageDate = trim(row.MarriageDate),
divorceDate = trim(row.DivorceDate),
engagementDate = trim(row.EngagementDate)
})
table.insert(people[p2].unions, {
unionID = trim(row.UnionID),
partner = p1,
unionType = trim(row.UnionType),
status = trim(row.Status),
startDate = trim(row.StartDate),
endDate = trim(row.EndDate),
marriageDate = trim(row.MarriageDate),
divorceDate = trim(row.DivorceDate),
engagementDate = trim(row.EngagementDate)
})
end
end
end
local function finalizePeople(people)
for _, person in pairs(people) do
person.parents = uniq(person.parents)
person.children = uniq(person.children)
person.partners = uniq(person.partners)
end
end
local function loadData()
local people = loadCharacters()
loadParentChild(people)
loadUnions(people)
finalizePeople(people)
return people
end
-- =========================================
-- Relationship helpers
-- =========================================
local function relationshipBadge(relType)
if not isRealValue(relType) then
return nil
end
local t = mw.ustring.lower(relType)
if t:find('adopt') then
return 'adopted'
end
if t:find('step') then
return 'step'
end
if t:find('bio') then
return nil
end
return relType
end
local function findUnionBetween(people, name1, name2)
if not isRealValue(name1) or not isRealValue(name2) then
return nil
end
local person = people[name1]
if not person or not person.unions then
return nil
end
for _, union in ipairs(person.unions) do
if union.partner == name2 then
return union
end
end
return nil
end
local function getMarriageYear(union)
if not union then
return nil
end
local raw = union.marriageDate or union.engagementDate or union.startDate
if not isRealValue(raw) then
return nil
end
local year = tostring(raw):match('^(%d%d%d%d)')
return year or tostring(raw)
end
local function getParents(people, root)
local person = people[root]
if not person then
return {}
end
local parents = uniq(person.parents)
sortNames(people, parents)
return parents
end
local function getGrandparents(people, root)
local out = {}
local parents = getParents(people, root)
for _, parentName in ipairs(parents) do
local parent = people[parentName]
if parent then
for _, gp in ipairs(parent.parents) do
addUnique(out, gp)
end
end
end
out = uniq(out)
sortNames(people, out)
return out
end
local function getSiblings(people, root)
local out = {}
local seen = {}
local person = people[root]
if not person then
return out
end
for _, parentName in ipairs(person.parents) do
local parent = people[parentName]
if parent then
for _, childName in ipairs(parent.children) do
if childName ~= root and not seen[childName] then
seen[childName] = true
table.insert(out, childName)
end
end
end
end
out = uniq(out)
sortNames(people, out)
return out
end
local function getConnectedPeople(people, root)
local out = {}
local person = people[root]
if not person then
return out
end
for _, v in ipairs(person.parents) do addUnique(out, v) end
for _, v in ipairs(person.children) do addUnique(out, v) end
for _, v in ipairs(person.partners) do addUnique(out, v) end
local siblings = getSiblings(people, root)
for _, v in ipairs(siblings) do addUnique(out, v) end
local grandparents = getGrandparents(people, root)
for _, v in ipairs(grandparents) do addUnique(out, v) end
out = uniq(out)
sortNames(people, out)
return out
end
local function getRootSiblingSequence(people, root)
local siblings = getSiblings(people, root)
local seq = {}
local inserted = false
local midpoint = math.floor(#siblings / 2) + 1
for i, sib in ipairs(siblings) do
if i == midpoint then
table.insert(seq, root)
inserted = true
end
table.insert(seq, sib)
end
if not inserted then
table.insert(seq, root)
end
return seq
end
local function getFamilyGroupsForRoot(people, root)
local person = people[root]
if not person or not person.childLinks then
return {}
end
local groups = {}
for _, link in ipairs(person.childLinks) do
local key
if isRealValue(link.unionID) then
key = 'union::' .. link.unionID
elseif isRealValue(link.otherParent) then
key = 'partner::' .. link.otherParent
else
key = 'single::' .. root
end
if not groups[key] then
groups[key] = {
unionID = link.unionID,
partner = link.otherParent,
children = {}
}
end
table.insert(groups[key].children, {
name = link.child,
relationshipType = link.relationshipType,
birthOrder = tonumber(link.birthOrder) or 999
})
end
local out = {}
for _, group in pairs(groups) do
table.sort(group.children, function(a, b)
return a.birthOrder < b.birthOrder
end)
table.insert(out, group)
end
table.sort(out, function(a, b)
local aSingle = not isRealValue(a.partner)
local bSingle = not isRealValue(b.partner)
if aSingle ~= bSingle then
return aSingle
end
local ap = a.partner or ''
local bp = b.partner or ''
local ad = (people[ap] and people[ap].displayName) or ap
local bd = (people[bp] and people[bp].displayName) or bp
return mw.ustring.lower(ad) < mw.ustring.lower(bd)
end)
return out
end
-- =========================================
-- Rendering helpers
-- =========================================
local function renderCard(people, name, badgeText, extraClass)
if not isRealValue(name) then
return nil
end
local person = people[name] or { name = name, displayName = name }
local card = html.create('div')
card:addClass('kbftv2-card')
if isRealValue(extraClass) then
card:addClass(extraClass)
end
card:wikitext(makeLink(person.name, person.displayName))
if isRealValue(badgeText) then
card:tag('div')
:addClass('kbftv2-badge')
:wikitext(badgeText)
end
return card
end
local function renderSingleCard(people, name, extraClass)
local wrap = html.create('div')
wrap:addClass('kbftv2-single')
wrap:node(renderCard(people, name, nil, extraClass))
return wrap
end
local function renderCouple(people, leftName, rightName, marriageYear, leftClass, rightClass)
if isRealValue(leftName) and isRealValue(rightName) then
local wrap = html.create('div')
wrap:addClass('kbftv2-couple')
wrap:node(renderCard(people, leftName, nil, leftClass))
local marriage = wrap:tag('div')
marriage:addClass('kbftv2-marriage')
if isRealValue(marriageYear) then
marriage:tag('div')
:addClass('kbftv2-marriage-year')
:wikitext(marriageYear)
end
marriage:tag('div')
:addClass('kbftv2-marriage-line')
wrap:node(renderCard(people, rightName, nil, rightClass))
return wrap
end
if isRealValue(leftName) then
return renderSingleCard(people, leftName, leftClass)
end
if isRealValue(rightName) then
return renderSingleCard(people, rightName, rightClass)
end
return nil
end
local function renderGenerationRow(units)
local row = html.create('div')
row:addClass('kbftv2-row')
for _, unit in ipairs(units) do
if unit then
row:node(unit)
end
end
return row
end
local function renderChildCards(people, children)
local childrenWrap = html.create('div')
childrenWrap:addClass('kbftv2-unit-children')
for _, child in ipairs(children) do
childrenWrap:node(
renderCard(
people,
child.name,
relationshipBadge(child.relationshipType)
)
)
end
return childrenWrap
end
local function renderFamilyUnit(people, root, group)
local unit = html.create('div')
unit:addClass('kbftv2-unit')
local top = unit:tag('div')
top:addClass('kbftv2-unit-top')
if isRealValue(group.partner) then
local union = findUnionBetween(people, root, group.partner)
local marriageYear = getMarriageYear(union)
top:node(renderCouple(people, root, group.partner, marriageYear, 'kbftv2-root-echo', nil))
else
local soloLabel = top:tag('div')
soloLabel:addClass('kbftv2-solo-label')
soloLabel:wikitext((people[root] and people[root].displayName) or root)
end
if #group.children > 0 then
unit:tag('div')
:addClass('kbftv2-unit-drop')
unit:node(renderChildCards(people, group.children))
end
return unit
end
local function renderUpperCoupleGeneration(people, couples)
if #couples == 0 then
return nil
end
local gen = html.create('div')
gen:addClass('kbftv2-generation')
local units = {}
for _, pair in ipairs(couples) do
table.insert(units, renderCouple(people, pair[1], pair[2], nil))
end
gen:node(renderGenerationRow(units))
return gen
end
local function buildGrandparentCouples(people, root)
local parents = getParents(people, root)
local couples = {}
for _, parentName in ipairs(parents) do
local parent = people[parentName]
if parent then
local gp = uniq(parent.parents)
sortNames(people, gp)
if #gp > 0 then
table.insert(couples, { gp[1], gp[2] })
end
end
end
return couples
end
local function buildParentCouples(people, root)
local parents = getParents(people, root)
if #parents == 0 then
return {}
end
return { { parents[1], parents[2] } }
end
local function renderFocalGeneration(people, root)
local sequence = getRootSiblingSequence(people, root)
local gen = html.create('div')
gen:addClass('kbftv2-generation')
local units = {}
for _, name in ipairs(sequence) do
if name == root then
table.insert(units, renderSingleCard(people, name, 'kbftv2-root-focus'))
else
table.insert(units, renderSingleCard(people, name))
end
end
local groupWrap = gen:tag('div')
groupWrap:addClass('kbftv2-sibling-group')
groupWrap:tag('div')
:addClass('kbftv2-sibling-spine')
groupWrap:node(renderGenerationRow(units))
return gen
end
local function renderFamilyGroupsGeneration(people, root)
local groups = getFamilyGroupsForRoot(people, root)
if #groups == 0 then
return nil
end
local gen = html.create('div')
gen:addClass('kbftv2-generation')
local units = {}
for _, group in ipairs(groups) do
table.insert(units, renderFamilyUnit(people, root, group))
end
gen:node(renderGenerationRow(units))
return gen
end
-- =========================================
-- Public renderers
-- =========================================
local function renderConnectedForRoot(people, root)
local person = people[root]
if not person then
return '<strong>FamilyTree error:</strong> No character found for "' .. tostring(root) .. '".'
end
local connected = getConnectedPeople(people, root)
local node = html.create('div')
node:addClass('kbftv2-tree')
node:tag('div')
:addClass('kbftv2-title')
:wikitext('Connected to ' .. makeLink(person.name, person.displayName))
local units = {}
for _, name in ipairs(connected) do
table.insert(units, renderSingleCard(people, name))
end
local gen = node:tag('div')
gen:addClass('kbftv2-generation')
gen:node(renderGenerationRow(units))
return tostring(node)
end
local function renderProfileForRoot(people, root)
local person = people[root]
if not person then
return '<strong>FamilyTree error:</strong> No character found for "' .. tostring(root) .. '".'
end
local node = html.create('div')
node:addClass('kbftv2-tree')
node:tag('div')
:addClass('kbftv2-title')
:wikitext(makeLink(person.name, person.displayName))
local function addSection(label, names)
names = uniq(names)
if #names == 0 then
return
end
sortNames(people, names)
node:tag('div')
:addClass('kbftv2-section-title')
:wikitext(label)
local units = {}
for _, name in ipairs(names) do
table.insert(units, renderSingleCard(people, name))
end
local gen = node:tag('div')
gen:addClass('kbftv2-generation')
gen:node(renderGenerationRow(units))
end
addSection('Parents', person.parents)
addSection('Partners', person.partners)
addSection('Children', person.children)
return tostring(node)
end
local function renderTreeForRoot(people, root)
local person = people[root]
if not person then
return '<strong>FamilyTree error:</strong> No character found for "' .. tostring(root) .. '".'
end
local node = html.create('div')
node:addClass('kbftv2-tree')
node:tag('div')
:addClass('kbftv2-title')
:wikitext('Family Tree: ' .. makeLink(person.name, person.displayName))
local gpGen = renderUpperCoupleGeneration(people, buildGrandparentCouples(people, root))
if gpGen then
node:node(gpGen)
node:tag('div'):addClass('kbftv2-connector')
end
local parentGen = renderUpperCoupleGeneration(people, buildParentCouples(people, root))
if parentGen then
node:node(parentGen)
node:tag('div'):addClass('kbftv2-connector')
end
node:node(renderFocalGeneration(people, root))
local familyGen = renderFamilyGroupsGeneration(people, root)
if familyGen then
node:tag('div'):addClass('kbftv2-connector')
node:node(familyGen)
end
return tostring(node)
end
-- =========================================
-- Public functions
-- =========================================
function p.tree(frame)
local root = getRoot(frame)
local people = loadData()
return renderTreeForRoot(people, root)
end
function p.profile(frame)
local root = getRoot(frame)
local people = loadData()
return renderProfileForRoot(people, root)
end
function p.connected(frame)
local root = getRoot(frame)
local people = loadData()
return renderConnectedForRoot(people, root)
end
return p