Module:Arbre généalogique Wikidata
Utilisation
[modifier le code]Ce module est conçu pour une utilisation à travers le modèle Modèle:Arbre généalogique Wikidata. Il exploite les données issues de Wikidata pour créer un arbre généalogique.
Son code est basé sur celui de wikidata:Module:Wikidata/FamilyTree. Le modèle fonctionne en coordination avec la feuille de rendu css Modèle:Arbre généalogique Wikidata/styles.css.
La sous–page Module:Arbre généalogique Wikidata/Rendu HTML n'est pas appelée par ce module.
Fonctions exportables
[modifier le code]drawTree( frame )
– Met en forme les arguments reçu dans frame pour un appel dedrawTreeImpl( args )
;drawTreeImpl( args )
– Prend en argument un tableau (décrit dans la section suivante) et retourne le code html d'un arbre généalogique.
Liste des paramètres
[modifier le code]Le tableau suivant liste les différents paramètres et leurs valeurs acceptées. Un appel valide de la fonction drawTreeImpl( args )
est de la forme :
local gen = require("Module:Arbre généalogique Wikidata")
local arbre = gen.drawTreeImpl({entityId="Q7742", ancestors=4, descendants=0, decorate="by-generation"})
Nom du paramètre | Description |
---|---|
entityId |
Identifiant Wikidata pour la base de l'arbre généré. Par défaut, identifiant de la page courante. |
mode |
Mode de rendu de l'arbre généalogique. Les valeurs acceptées sont "horizontal" et "vertical" . Par défaut, "vertical" (nb. Le modèle {{Arbre généalogique Wikidata}} a comme mode de rendu par défaut le mode horizontal).
|
invert |
Booléen indiquant si l'arbre généalogique doit être inversé (les ancêtres en bas et les descendants en haut). Par défaut, false .
|
interleave |
Booléen indiquant si les ancêtres doivent être entrelacés (père et mère affichés sur la même ligne). Par défaut, false .
|
ancestors |
Le nombre de générations d'ancêtres à afficher. Par défaut, 2 pour un mode horizontal et 3 pour un mode vertical. |
descendants |
Le nombre de générations de descendants à afficher. Par défaut, 2 pour un mode horizontal et 3 pour un mode vertical. |
compactParents |
Booléen indiquant si les parents doivent être affichés de manière compacte (sur la même ligne) ou non. Vaut toujours true si mode horizontal et vaut false par défaut si mode vertical.
|
compactChildren |
Booléen indiquant si les enfants doivent être affichés de manière compacte (sur la même ligne) ou non. Dans le code actuel, vaut toujours true .
|
years |
Booléen indiquant si les années de naissance et de décès doivent être affichées. Les valeurs acceptées sont true , "next-line" et "new-line" . Dans les deux derniers cas, la valeur du paramètre définit la classe css utilisée pour l'affichage des années. Par défaut, vaut false .
|
descriptions |
Booléen indiquant si les descriptions des entités doivent être affichées. Les valeurs acceptées sont true , "next-line" et "new-line" . Dans les deux derniers cas, la valeur du paramètre définit la classe css utilisée pour l'affichage de la description. Par défaut, vaut false .
|
decorate |
Chaîne de caractères indiquant les décorations à appliquer à l'arbre généalogique. Les deux valeurs acceptées sont "by-generation" (qui applique une coloration par génération si la feuille de style est appliquée) et "none" . Par défaut, vaut "none" .
|
Modules externes et autres éléments dont ce module a besoin pour fonctionner
[modifier le code]Extension:Client Wikibase
[modifier le code]mw.wikibase.getEntityIdForCurrentPage
– Retourne l'id de l'élément wikidata associé à la page courante ;mw.wikibase.getLabel
– Retourne le label wikidata associé à la page courante ou à celle dont l'id associé est passé en argument ;mw.wikibase.sitelink
– Retourne le nom sur wikipédia en français de la page dont l'id associé est passé en argument ;mw.wikibase.getDescription
– Retourne la description wikidata associée à la page courante ou à celle dont l'id associé est passé en argument ;mw.wikibase.getBestStatements
– Prend en argument un id d'entité wikidata et un id de propriété et retourne une table décrivant la valeur de la propriété pour cet élément. Les éléments de rang normal ne sont pas retournés si un élément de rang préféré est présent et les éléments de rang obsolète ne sont jamais retournés.
Autres modules et fonctions externes
[modifier le code]mw.title.new
– Crée un nouvel objet title ;mw.ustring.match
– Cherche la première correspondance d'un motif dans une chaine de caractères ;mw.getCurrentFrame
– Retourne l'objet frame courant, généralement l'objet frame du dernier appel à#invoke
;frame:getTitle()
– Renvoie le nom du module invoqué par le{{#invoke:}}
;frame:getParent()
– Renvoie l'objet frame de la page qui a appelé le{{#invoke:}}
;
mw.site.namespaces
– Table contenant les informations sur tous les espaces de noms, indexés par leur numéro ;
Module:Romain
– Prend en argument un nombre décimal et retourne son écriture en numération romaine.
Modules lua utilisant ce module
[modifier le code]Aucun
Modèles appelant directement ce module
[modifier le code]Exemples
[modifier le code]Dans Modèle:Arbre généalogique Wikidata, ce module est appelé par le code suivant :
<templatestyles src="Modèle:Arbre généalogique Wikidata/styles.css" />
{{#invoke:Arbre généalogique Wikidata|drawTree|mode={{{mode|horizontal}}}}}
La documentation de ce module est générée par le modèle {{Documentation module}}.
Elle est incluse depuis sa sous-page de documentation. Veuillez placer les catégories sur cette page-là.
Les éditeurs peuvent travailler dans le bac à sable (créer).
Voir les statistiques d'appel depuis le wikicode sur l'outil wstat et les appels depuis d'autres modules.
local p = {}
local ELEMENT_WIDTH = 5;
local ELEMENT_HEIGHT = 5;
local DEFAULT_MODE = "vertical"; -- horizontal
local DEFAULT_DECORATE = "none"; -- by-generation
local BUSY = 'X'
local EMPTY_TABLE = {};
local MISSING_LABEL_LINK = function( entityId )
return '[[:d:' .. entityId .. '|' .. entityId .. ']]<span style="border-bottom: 1px dotted; cursor: help; white-space: nowrap" title="Il n’y a pas de label anglais dans Wikidata pour cet élément. Vous pouvez aider en l’ajoutant.">?</span>';
end
local RED_LINK_F = function( entityId, label )
return '<span class="plainlinks">' .. label .. '</span> <span class="indicateur-langue" title="Voir sur Wikidata">([[:d:' .. entityId .. '|d]])</span>';
end
local function copyTo( obj, target, skipEmpty )
for k, v in pairs( obj ) do
if skipEmpty ~= true or ( v ~= nil and v ~= '' ) then
target[k] = v;
end
end
return target;
end
local function expectString( var, name )
if (type(var) ~= 'string') then
error("Expected string but have " .. type(var) .. " of argument " .. name);
end
end
local function getEntityIdsFromClaims( claims )
if ( claims == nil ) then return EMPTY_TABLE end
local result = {};
for _, claim in pairs( claims ) do
local id = (((claim.mainsnak or EMPTY_TABLE).datavalue or EMPTY_TABLE).value or EMPTY_TABLE).id or nil;
if ( id ~= nil ) then
result[id] = id;
end
end
return result;
end
local function createNode( entityId, generation )
expectString( entityId, "entityId" );
local node = {};
node.entityId = entityId;
node.generation = generation;
return node;
end
local function populateRelation( relationType, propertyId, line, node, newGeneration )
expectString( relationType, "relationType" );
expectString( propertyId, "propertyId" );
local relativesIds = getEntityIdsFromClaims( mw.wikibase.getBestStatements( node.entityId, propertyId ) );
local propertyKey = relationType .. 'Nodes';
for _, relativeId in pairs( relativesIds ) do
if ( node[propertyKey] == nil ) then node[propertyKey] = {} end
local relativeNode = createNode( relativeId, newGeneration );
table.insert( line, relativeNode );
table.insert( node[propertyKey], relativeNode );
end
end
local function copyRendered( src, dst, dstPosX, dstPosY, width, height )
if ( #src < height ) then error( "Need " .. (height) .. " lines in src; have only " .. #src ); end
if ( #dst < dstPosY + height - 1 ) then error( "Need " .. (dstPosY + height - 1) .. " lines in dst; have only " .. #dst ); end
for y=1, height do
local srcLine = src[ y ];
local dstLine = dst[ dstPosY + y - 1 ];
for x=1, width do
local newValue = srcLine[ x ];
if (newValue ~= nil) then
dstLine[ dstPosX + x - 1 ] = newValue;
end
end
end
end
function willOverride( src, dst, dstPosX, dstPosY, width, height )
for y=1, height do
local srcLine = src[ y ];
local dstLine = dst[ dstPosY + y - 1 ];
if ( srcLine == nil ) then error( "Missing line " .. y .. " in src" ); end
if ( dstLine == nil ) then error( "Missing line " .. ( dstPosY + y - 1 ) .. " in dst" ); end
for x=1, width do
local hasInSrc = srcLine[ x ] ~= nil;
local hasInDst = dstLine[ dstPosX + x - 1 ] ~= nil;
if ( hasInSrc and hasInDst ) then
return true;
end
end
end
return false;
end
local APPEND_LINE_UP = {W="NW",S="NS",SW="NSW",E="NE",EW="NEW",ES="NES",ESW="NESW",N="N",NW="NW",NS="NS",NSW="NSW",NE="NE",NEW="NEW",NES="NES",NESW="NESW",};
local APPEND_LINE_DOWN = {W="SW",S="S",SW="SW",E="ES",EW="ESW",ES="ES",ESW="ESW",N="NS",NW="NSW",NS="NS",NSW="NSW",NE="NES",NEW="NESW",NES="NES",NESW="NESW",};
local function parentsAreSame( node1, node2 )
if (not node1.parentNodes) then return false; end
if (not node2.parentNodes) then return false; end
local firstGrandParentCount = 0;
local firstGrandParentIds = {};
for _, grandParent in pairs( node1.parentNodes ) do
firstGrandParentIds[ grandParent.entityId ] = true;
firstGrandParentCount = firstGrandParentCount + 1;
end
if ( firstGrandParentCount ~= #node2.parentNodes ) then
return false
end
for _, grandParent in pairs( node2.parentNodes ) do
if ( not firstGrandParentIds[ grandParent.entityId ] ) then
return false;
end
end
return true;
end
local function putEntity( result, yStart, xStart, node )
for y=yStart, yStart + ELEMENT_HEIGHT - 1 do
for x=xStart, xStart + ELEMENT_WIDTH - 1 do
result[y][x] = BUSY;
end
end
result[yStart][xStart] = {
entityId = node.entityId,
generation = node.generation
};
end
local function renderSingle( node, connectToFirstLine )
node.resultWidth = ELEMENT_WIDTH;
node.resultHeight = ELEMENT_HEIGHT;
node.renderResult = {};
for y=1,ELEMENT_HEIGHT do node.renderResult[y] = {}; end
putEntity( node.renderResult, 1, 1, node );
node.lineConnectionY = connectToFirstLine and 1 or ELEMENT_HEIGHT;
node.lineConnectionX = math.ceil( ELEMENT_WIDTH / 2 );
end
local renderWithParents;
local renderInterleaveWithParents;
local function renderFakeParentsNodeWithoutChild( options, parentNodes, leftParentTailOnly, rightParentTailOnly )
-- fake node
local node = {};
local lineStartConnectFrom = 99999;
local lineEndConnectTo = 0;
local resultWidth = 0; -- self
local resultHeight = 0;
local commonParentsResult = nil;
-- we shall not interleave different incests... in can be bad, in all sences
if ( not options.interleave and leftParentTailOnly == nil and rightParentTailOnly == nil ) then
-- check if right corner of left parent is the same as left corner of right parent
if (#parentNodes == 2) then
local leftRoot = parentNodes[1];
local rightRoot = parentNodes[2];
while (leftRoot ~= nil and rightRoot ~= nil) do
if (parentsAreSame( leftRoot, rightRoot )) then
leftParentTailOnly = leftRoot.entityId;
rightParentTailOnly = rightRoot.entityId;
commonParentsResult = renderFakeParentsNodeWithoutChild( options, leftRoot.parentNodes, nil, nil );
break;
end
if (leftRoot.parentNodes ~= nil and #leftRoot.parentNodes ~= 0) then
leftRoot = leftRoot.parentNodes[ #leftRoot.parentNodes ];
else
leftRoot = nil;
end
if (rightRoot.parentNodes ~= nil and #rightRoot.parentNodes ~= 0) then
rightRoot = rightRoot.parentNodes[1];
else
rightRoot = nil;
end
end
end
end
for _, parentNode in pairs( parentNodes ) do
if ( options.interleave ) then
renderInterleaveWithParents( options, parentNode );
else
if (commonParentsResult == nil) then
local parentLeftParentTailOnly = nil;
local parentRightParentTailOnly = nil;
if ( _ == 1 ) then parentLeftParentTailOnly = leftParentTailOnly; end;
if ( _ == #parentNodes ) then parentRightParentTailOnly = rightParentTailOnly; end;
renderWithParents( options, parentNode, parentLeftParentTailOnly, parentRightParentTailOnly );
else
local parentLeftParentTailOnly = nil;
local parentRightParentTailOnly = nil;
if ( _ == #parentNodes ) then parentLeftParentTailOnly = rightParentTailOnly; end;
if ( _ == 1 ) then parentRightParentTailOnly = leftParentTailOnly; end;
renderWithParents( options, parentNode, parentLeftParentTailOnly, parentRightParentTailOnly );
end
end
resultHeight = math.max( resultHeight, parentNode.resultHeight );
end
resultHeight = resultHeight + 1; -- lines
local result = {};
for y=1,resultHeight do result[y]={} end;
local commonParentsHorPosition = nil;
local commonParentsVerPosition = nil;
local verPositions = {};
local horPositions = {};
for _, parentNode in pairs( parentNodes ) do
local mergeTailsHere = _ == 2 and commonParentsResult ~= nil;
if (mergeTailsHere) then
commonParentsHorPosition = resultWidth + 2;
-- need to find connection points of tails in parents
if ( parentNodes[1].resultTails == nil ) then error("Need parent tails to present in left parent result") end;
if ( parentNodes[2].resultTails == nil ) then error("Need parent tails to present in right parent result") end;
local tailY = parentNodes[1].resultTails[ leftParentTailOnly ].y + ( horPositions[1] - 1 );
commonParentsVerPosition = tailY - commonParentsResult.resultHeight - 1;
while ( commonParentsHorPosition > 4 ) do
if ( willOverride( commonParentsResult.renderResult, result, commonParentsHorPosition - 2, commonParentsVerPosition, commonParentsResult.resultWidth, commonParentsResult.resultHeight ) ) then
break;
end
commonParentsHorPosition = commonParentsHorPosition - 1;
end
copyRendered( commonParentsResult.renderResult, result, commonParentsHorPosition, commonParentsVerPosition, commonParentsResult.resultWidth, commonParentsResult.resultHeight );
resultWidth = math.max( resultWidth, commonParentsHorPosition + commonParentsResult.resultWidth - 1 );
end
-- position by horizontal of current element
local blockPosition = _ ~= 1 and resultWidth + 2 or 1;
local parentStartLine = resultHeight - 1 - parentNode.resultHeight + 1;
-- check if we have empty space in result to shift parent block to left
while ( blockPosition > 2 ) do
if ( not willOverride( parentNode.renderResult, result, blockPosition - 2, parentStartLine, parentNode.resultWidth, parentNode.resultHeight ) ) then
blockPosition = blockPosition - 1;
else
break;
end
end
horPositions[_] = blockPosition;
verPositions[_] = parentStartLine;
local parentWidth = parentNode.resultWidth;
local parentHeight = parentNode.resultHeight;
-- define how parent horizontal line need to be drawn
lineStartConnectFrom = math.min( lineStartConnectFrom, blockPosition + parentNode.lineConnectionX - 1 )
lineEndConnectTo = math.max( lineEndConnectTo, blockPosition + parentNode.lineConnectionX - 1 )
resultWidth = math.max( resultWidth, blockPosition + parentNode.resultWidth - 1 );
copyRendered( parentNode.renderResult, result, blockPosition, parentStartLine, parentNode.resultWidth, parentNode.resultHeight )
if ( parentNode.resultTails ~= nil ) then
if ( node.resultTails == nil) then node.resultTails = {}; end;
for tailId, resultTail in pairs( parentNode.resultTails ) do
node.resultTails[ tailId ] = { x = blockPosition + resultTail.x - 1, y = parentStartLine + resultTail.y - 1 };
end
end
end
node.resultWidth = resultWidth;
node.resultHeight = resultHeight;
-- connect tails of left and right tree with common parents part
if ( commonParentsResult ~= nil ) then
local x1 = parentNodes[1].resultTails[leftParentTailOnly].x + ( horPositions[1] - 1 );
local x2 = parentNodes[2].resultTails[rightParentTailOnly].x + ( horPositions[2] - 1 );
local y1 = parentNodes[1].resultTails[leftParentTailOnly].y + ( verPositions[1] - 1 );
local y2 = parentNodes[2].resultTails[rightParentTailOnly].y + ( verPositions[2] - 1 );
if ( y1 ~= y2 ) then
mw.log( 'different vertical positions of parent tails in tree:' .. y1 .. ' vs ' .. y2 );
else
result[y1][x1]='ES';
for x=(x1 + 1),(x2 - 1) do
result[y1][x] = "EW";
end
result[y1][x2]='SW';
end
local lineConnectionX = commonParentsResult.lineConnectionX + commonParentsHorPosition - 1;
local lineConnectionY = commonParentsResult.lineConnectionY + commonParentsVerPosition - 1;
result[lineConnectionY][lineConnectionX] = 'ESW';
result[lineConnectionY+1][lineConnectionX] = 'NS';
result[lineConnectionY+2][lineConnectionX] = APPEND_LINE_UP[ result[lineConnectionY+2][lineConnectionX] ];
end
-- local center = math.ceil( resultWidth / 2 );
local center = lineStartConnectFrom + math.floor((lineEndConnectTo - lineStartConnectFrom) / 2);
if ( center < 3 ) then error( "center < 3" ) end
lineStartConnectFrom = math.min( lineStartConnectFrom , center );
lineEndConnectTo = math.max( lineEndConnectTo , center );
-- shift child a bit to center of line
if ( center == lineStartConnectFrom and lineEndConnectTo - lineStartConnectFrom >= 2 ) then center = center + 1; end
if ( center == lineEndConnectTo and lineEndConnectTo - lineStartConnectFrom >= 2 ) then center = center - 1; end
if ( center < 3 ) then error( "center < 3" ) end
local parentConnectionLine = resultHeight;
-- draw parent connection line
if ( lineStartConnectFrom == lineEndConnectTo) then
result[parentConnectionLine][lineStartConnectFrom] = "NS";
else
result[parentConnectionLine][lineStartConnectFrom] = "E";
for x=(lineStartConnectFrom + 1),(lineEndConnectTo - 1) do
result[parentConnectionLine][x] = "EW";
end
result[parentConnectionLine][lineEndConnectTo] = "W";
end
-- draw line up to parents
for _, parentNode in pairs( parentNodes ) do
local parentConnectionIndex = horPositions[_] - 1 + parentNode.lineConnectionX;
result[parentConnectionLine][parentConnectionIndex] = APPEND_LINE_UP[ result[parentConnectionLine][parentConnectionIndex] ];
end
node.renderResult = result;
node.lineConnectionY = resultHeight;
node.lineConnectionX = center;
return node;
end
renderWithParents = function( options, node, leftParentTailOnly, rightParentTailOnly )
if ( not node.parentNodes or #node.parentNodes == 0) then
renderSingle( node, true );
return;
end
local fakeParentsNode = renderFakeParentsNodeWithoutChild( options, node.parentNodes, leftParentTailOnly, rightParentTailOnly );
local center = fakeParentsNode.lineConnectionX;
local resultWidth = fakeParentsNode.resultWidth;
local resultHeight = fakeParentsNode.resultHeight + 2 + ELEMENT_HEIGHT;
local result = {};
for y=1,resultHeight do result[y]={} end;
local parentConnectionLine = fakeParentsNode.resultHeight;
if ( node.entityId ~= leftParentTailOnly and node.entityId ~= rightParentTailOnly ) then
copyRendered( fakeParentsNode.renderResult, result, 1, 1, fakeParentsNode.resultWidth, fakeParentsNode.resultHeight )
node.resultTails = fakeParentsNode.resultTails;
-- draw line down to child
result[parentConnectionLine][center] = APPEND_LINE_DOWN[ result[parentConnectionLine][center] ];
result[parentConnectionLine + 1][center] = "NS";
result[parentConnectionLine + 2][center] = "N";
else
if ( node.resultTails == nil ) then node.resultTails = {}; end
node.resultTails[ node.entityId ] = { x = center, y = parentConnectionLine + 2};
end
node.resultWidth = resultWidth;
node.resultHeight = resultHeight;
-- line to child
result[parentConnectionLine + 2][center] = APPEND_LINE_DOWN[ result[parentConnectionLine + 2][center] ] or 'S';
putEntity( result, parentConnectionLine + 3, center - math.floor( ELEMENT_WIDTH / 2 ), node );
node.renderResult = result;
node.lineConnectionY = resultHeight;
node.lineConnectionX = center;
if (node.entityId == 'Q61466944') then mw.logObject( node ) end;
end
renderInterleaveWithParents = function( options, node )
local hasFathers = node.fatherNodes and #node.fatherNodes > 0;
local hasMothers = node.motherNodes and #node.motherNodes > 0;
if ( (not hasFathers) and (not hasMothers) ) then
renderSingle( node, true );
return;
end
local renderHeight = ELEMENT_HEIGHT;
local renderWidth = ELEMENT_WIDTH;
local fakeFatherNode;
local heightWithFathers = ELEMENT_HEIGHT;
local widthOfFathers = 0
if ( hasFathers ) then
fakeFatherNode = renderFakeParentsNodeWithoutChild( options, node.fatherNodes, EMPTY_TABLE );
heightWithFathers = fakeFatherNode.resultHeight + 2;
widthOfFathers = fakeFatherNode.resultWidth + 1;
end
local fakeMotherNode;
local heightWithMothers = ELEMENT_HEIGHT;
local widthOfMothers = 0
if ( hasMothers ) then
fakeMotherNode = renderFakeParentsNodeWithoutChild( options, node.motherNodes, EMPTY_TABLE );
heightWithMothers = fakeMotherNode.resultHeight + 2;
widthOfMothers = fakeMotherNode.resultWidth + 1;
end
local resultHeight = math.max( heightWithFathers, heightWithMothers );
local resultWidth = widthOfFathers + ELEMENT_WIDTH + widthOfMothers;
local result = {};
for y=1,resultHeight do result[y]={} end;
if ( hasFathers ) then
local fathersX = 1;
local fathersY = resultHeight - heightWithFathers + 1;
copyRendered( fakeFatherNode.renderResult, result, fathersX, fathersY, fakeFatherNode.resultWidth, fakeFatherNode.resultHeight );
-- draw line
local lineY = fathersY + fakeFatherNode.resultHeight;
local lineStart = fakeFatherNode.lineConnectionX;
local lineEnd = fakeFatherNode.resultWidth + 1;
result[lineY][lineStart] = "NE";
for x=(lineStart+1),lineEnd do
result[lineY][x] = "EW";
end
result[lineY - 1][lineStart] = APPEND_LINE_DOWN[ result[lineY - 1][lineStart] ]
end
local selfX = hasFathers and (widthOfFathers + 1) or 1;
local selfY = resultHeight - ELEMENT_HEIGHT + 1;
putEntity( result, selfY, selfX, node );
if ( hasMothers ) then
local mothersX = selfX + ELEMENT_WIDTH + 1;
local mothersY = resultHeight - heightWithMothers + 1;
copyRendered( fakeMotherNode.renderResult, result, mothersX, mothersY, fakeMotherNode.resultWidth, fakeMotherNode.resultHeight );
-- draw line
local lineY = mothersY + fakeMotherNode.resultHeight;
local lineStart = mothersX - 1;
local lineEnd = mothersX + fakeMotherNode.lineConnectionX - 1;
result[lineY][lineEnd] = "NW";
for x=lineStart,(lineEnd-1) do
result[lineY][x] = "EW";
end
result[lineY - 1][lineEnd] = APPEND_LINE_DOWN[ result[lineY - 1][lineEnd] ]
end
node.renderResult = result;
node.resultHeight = resultHeight;
node.resultWidth = resultWidth;
node.lineConnectionX = selfX + math.floor( ELEMENT_WIDTH / 2 );
node.lineConnectionY = resultHeight;
end
local function renderWithChildren( options, node )
if ( not node.childNodes or #node.childNodes == 0) then
renderSingle( node, false );
return;
end
local lineStartConnectFrom = 99999;
local lineEndConnectTo = 0;
local resultWidth = 0; -- self
local resultHeight = 0;
for _, childNode in pairs( node.childNodes ) do
renderWithChildren( options, childNode );
resultHeight = math.max( resultHeight, childNode.resultHeight );
end
resultHeight = resultHeight + 3 + ELEMENT_HEIGHT;
local result = {};
for y=1,resultHeight do result[y]={} end;
local horPositions = {};
for _, childNode in pairs( node.childNodes ) do
-- position by horizontal of current element
local blockPosition = _ ~= 1 and resultWidth + 2 or 1;
local childWidth = childNode.resultWidth;
local childHeight = childNode.resultHeight;
-- check if we have empty space in result to shift child block to left
while (blockPosition > 2) do
if ( not willOverride( childNode.renderResult, result, blockPosition - 2, 5, childWidth, childHeight ) ) then
blockPosition = blockPosition - 1;
else
break;
end
end
horPositions[_] = blockPosition;
-- define how parent horizontal line need to be drawn
local blockLineConnectionX = blockPosition + childNode.lineConnectionX - 1;
lineStartConnectFrom = math.min( lineStartConnectFrom, blockLineConnectionX )
lineEndConnectTo = math.max( lineEndConnectTo, blockLineConnectionX )
resultWidth = math.max(resultWidth, blockPosition + childWidth - 1);
copyRendered( childNode.renderResult, result, blockPosition, ELEMENT_HEIGHT + 3 + 1, childNode.resultWidth, childNode.resultHeight )
end
node.resultWidth = resultWidth;
node.resultHeight = resultHeight;
-- local center = math.max( math.floor( (resultWidth + 1) / 2 ), 1 );
local center = lineStartConnectFrom + math.floor( (lineEndConnectTo - lineStartConnectFrom) / 2 )
if ( center < 3 ) then error( "center < 3" ) end
lineStartConnectFrom = math.min( lineStartConnectFrom , center );
lineEndConnectTo = math.max( lineEndConnectTo , center );
if (lineStartConnectFrom > lineEndConnectTo) then
error('lineStartConnectFrom > lineEndConnectTo');
end
local childConnectionLine = ELEMENT_HEIGHT + 3;
-- draw child connection line
if ( lineStartConnectFrom == lineEndConnectTo) then
result[childConnectionLine][lineStartConnectFrom] = "NS";
else
result[childConnectionLine][lineStartConnectFrom] = "E";
for x=(lineStartConnectFrom + 1),(lineEndConnectTo - 1) do
result[childConnectionLine][x] = "EW";
end
result[childConnectionLine][lineEndConnectTo] = "W";
end
-- draw line down to children
for _, childNode in pairs( node.childNodes ) do
local childConnectionIndex = horPositions[_] - 1 + childNode.lineConnectionX;
result[childConnectionLine][ childConnectionIndex ] = APPEND_LINE_DOWN[ result[childConnectionLine][ childConnectionIndex ] ];
end
-- draw line up to parent
result[childConnectionLine][center] = APPEND_LINE_UP[ result[childConnectionLine][center] ];
result[childConnectionLine-1][center] = "NS";
result[childConnectionLine-2][center] = "NS";
putEntity( result, 1, center - math.floor( ELEMENT_WIDTH / 2 ), node );
node.renderResult = result;
node.lineConnectionX = center;
node.lineConnectionY = resultHeight;
end
local function renderRoot( options, node )
-- render with parents first
if ( options.interleave ) then
renderInterleaveWithParents( options, node );
else
renderWithParents( options, node, nil, nil );
end
local withParentsResult = node.renderResult;
local withParentsWidth = node.resultWidth;
local withParentsHeight = node.resultHeight;
local withParentsConnectionX = node.lineConnectionX;
local withParentsConnectionY = node.lineConnectionY;
node.renderResult = nil;
node.resultWidth = nil;
node.resultHeight = nil;
node.lineConnectionX = nil;
node.lineConnectionY = nil;
-- render with children
renderWithChildren( options, node );
local withChildrenResult = node.renderResult;
local withChildrenWidth = node.resultWidth;
local withChildrenHeight = node.resultHeight;
local withChildrenConnectionX = node.lineConnectionX;
local withChildrenConnectionY = node.lineConnectionY;
node.renderResult = nil;
node.resultWidth = nil;
node.resultHeight = nil;
node.lineConnectionX = nil;
node.lineConnectionY = nil;
local shiftParents = math.max(withChildrenConnectionX - withParentsConnectionX, 0);
local shiftChildren = math.max(withParentsConnectionX - withChildrenConnectionX, 0);
local resultWidth = math.max( withParentsWidth + shiftParents, withChildrenWidth + shiftChildren );
local resultHeight = withParentsHeight + withChildrenHeight - ELEMENT_HEIGHT;
local result = {};
for y=1,resultHeight do result[y]={} end;
copyRendered( withChildrenResult, result, 1 + shiftChildren, withParentsHeight - ELEMENT_HEIGHT + 1, withChildrenWidth, withChildrenHeight )
copyRendered( withParentsResult, result, 1 + shiftParents, 1, withParentsWidth, withParentsHeight )
node.renderResult = result;
node.resultWidth = resultWidth;
node.resultHeight = resultHeight;
end
--- Formate un lien vers la page dont l’ID Wikidata a été passé en paramètre.
--- Récupère le libellé et la page cible sur Wikidata. Tente de faire un lien vers la page cible.
--- Si elle n’existe pas, tente de faire un lien vers la page nommé avec le libellé.
--- @param entityId string l’ID de l’entité
local function printEntityLabel( entityId )
if ( type(entityId) ~= 'string' ) then error( 'Expected type of entityId to be string, but was ' .. type(entityId) ) end
local label = mw.wikibase.getLabel( entityId );
local link = mw.wikibase.sitelink( entityId )
if link then
if label and link ~= label then
return '[[' .. link .. '|' .. label .. ']]';
end
return '[[' .. link .. ']]';
end
if label then
local title = mw.title.new( label );
if title and not title.exists then
return RED_LINK_F( entityId, label);
end
local wdlink = '<span class="plainlinks noprint">([//www.wikidata.org/wiki/' .. entityId .. ' d])</span>'
return '<span class="iw" data-title="' .. label .. '">' .. label .. ' ' .. wdlink .. '</span>'
end
return MISSING_LABEL_LINK( entityId );
end
--- Extrait l’année, le mois et le jour d’une date au format ISO 8601
--- @param str string|table la date dont il faut extraire les informations
--- @return table une table contenant trois attributs : year pour l’année, month pour le mois et day pour le jour
local function extractYearMonthAndDayFromDateTime(str)
if type(str) == 'table' then
if str.args and str.args[1] then
str = '' .. str.args[1]
else
error('unknown argument type: ' .. type( str ) .. ': ' .. table.tostring( str ))
end
end
local pattern = "(%-?%d+)%-(%d+)%-(%d+)T"
local Y, M, D = mw.ustring.match( str, pattern )
return {year=tonumber(Y), month=tonumber(M), day=tonumber(D)};
end
local function parseYearFromValue( value )
local s = extractYearMonthAndDayFromDateTime( value.time );
if (not s) then return nil; end
local precision = value.precision;
if ( precision >= 0 and precision <= 8 ) then
local powers = { 1000000000 , 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10 }
local power = powers[ precision + 1 ];
local left = s.year - ( ( s.year - 1 ) % power );
return left, left - 1 + power / 2;
end
return s.year, s.year;
end
local function parseTimeValues( entityId, propertyId )
local claims = mw.wikibase.getBestStatements( entityId, propertyId );
if ( not claims ) then return nil; end
local hasAnyValue = false;
local values = {};
for _, claim in pairs(claims) do
local value = (((claim or EMPTY_TABLE).mainsnak or EMPTY_TABLE).datavalue or EMPTY_TABLE).value;
if ( value ~= nil ) then
local left, year = parseYearFromValue( value );
if (year ~= nil) then
values[year] = {
precision = value.precision,
year = year,
left = left
};
hasAnyValue = true;
end
end
end
return hasAnyValue and values or nil;
end
local function getAnyValue( src )
for _, value in pairs( src ) do
return value;
end
return nil;
end
local function compareEntitiesByBirthDate( node1, node2 )
local bYear1 = (getAnyValue( parseTimeValues( node1.entityId, 'P569' ) or EMPTY_TABLE) or EMPTY_TABLE).year or 9999;
local bYear2 = (getAnyValue( parseTimeValues( node2.entityId, 'P569' ) or EMPTY_TABLE) or EMPTY_TABLE).year or 9999;
return bYear1 < bYear2;
end
local function formatCentury( year )
local moduleRomain = require( "Module:Romain" )
if ( year < 0 ) then
local century = math.floor( (math.abs( year ) - 1) / 100 ) + 1;
local infix = ' в ';
if century == 2 then infix = ' во '; end;
if ( moduleRomain ) then
century = moduleRomain.toRoman( century );
end
return century .. '<sup>e</sup> siècle av. J.-C.'
else
local century = math.floor( ( year - 1) / 100 ) + 1;
local infix = ' в ';
if (century == 2) then infix = ' во ' end;
if ( moduleRomain ) then
century = moduleRomain.toRoman( century );
end
return century .. ' в.'
end
end
local function formatParsedTimeValue( parsedValue )
if ( parsedValue == nil ) then
return nil;
elseif ( parsedValue.precision > 8 ) then
if ( parsedValue.year >= 0 ) then
return parsedValue.year;
else
return ( 0 - parsedValue.year ) .. ' av. J.-C.';
end
return parsedValue.year;
elseif ( parsedValue.precision == 8) then
if ( parsedValue.year >= 0 ) then
return (parsedValue.left - 1) .. '-е';
else
return (0 - (parsedValue.left - 1) ) .. '-е av. J.-C.';
end
elseif ( parsedValue.precision == 7) then
return formatCentury( parsedValue.year );
else
mw.log( 'Unsupported precision: ' .. parsedValue.precision );
mw.log( parsedValue );
return 'unsupported';
end
end
local function compareAsStrings( a, b )
return ('' .. a) < ('' .. b);
end
local function concat( parsedTimeValuesTable )
local tmp = {};
for _, value in pairs( parsedTimeValuesTable ) do
table.insert( tmp , formatParsedTimeValue(value) );
end
table.sort( tmp, compareAsStrings );
return table.concat( tmp, ' / ' );
end
local function printLifespan( entityId )
local birth = parseTimeValues( entityId, 'P569' );
local death = parseTimeValues( entityId, 'P570' );
if (birth == nil and death == nil) then return nil end
if (birth ~= nil and death == nil) then return '°' .. concat( birth ) end
if (birth == nil and death ~= nil) then return '? - ' .. concat( death ) end
if (birth ~= nil and death ~= nil) then
local bString = concat( birth );
local dString = concat( death );
if (bString == dString) then
return bString;
else
return bString .. ' — ' .. dString;
end
end
return nil;
end
local function printEntity( options, entityId )
local html = printEntityLabel( entityId );
if ( options.years == "label-line" ) then
local lifespan = printLifespan( entityId );
if ( lifespan ~= nil) then html = html .. ' <span class="years">(' .. lifespan .. '</span>)' end
end
if ( options.descriptions ) then
local description = mw.wikibase.getDescription( entityId );
if ( description ) then
if ( options.descriptions == true or options.descriptions == "next-line" or options.descriptions == "new-line" ) then
html = html .. '<br>'
else
html = html .. ', ';
end
html = html .. '<span class="descriptions">' .. description .. '</span>';
end
end
if ( options.years == true or options.years == "next-line" or options.years == "new-line" ) then
local lifespan = printLifespan( entityId );
if ( lifespan ~= nil) then html = html .. '<br><div class="years">' .. lifespan .. '</div>' end
end
return html;
end
local function printEntityCell( options, attrs, node )
local entityId = node.entityId;
local generation = node.generation;
return '<td class="Q gen' .. generation ..'" ' .. attrs .. '>'.. printEntity( options, entityId ) ..'</td>';
end
local ROTATE = {W="N",S="E",E="S",N="W"};
local INVERT_HORIZONTAL = {W="E",S="S",E="W",N="N"};
function renderHtmlHorizontal( options, node )
local invert = options.invert;
local resultHeight = node.resultHeight;
local resultWidth = node.resultWidth;
if (type(resultHeight) ~= 'number') then error("node.resultHeight expected to be number"); end
if (type(resultWidth) ~= 'number') then error("node.resultWidth expected to be number"); end
local renderResult = node.renderResult;
local yStart = invert and resultHeight or 1;
local yEnd = invert and 1 or resultHeight;
local yStep = invert and -1 or 1;
local html = '<div class="wikidata-familyTree wikidata-familyTree-horizontal wikidata-familyTree-decorate-' .. options.decorate .. '"><table>\n'
for x=1,resultWidth do
html = html .. '<tr><td></td>\n'
for y=yStart,yEnd,yStep do
local cell = renderResult[y][x];
if (cell == nil) then
if ( y ~= yStart and renderResult[y - yStep][x] == nil ) then
-- skip, because handled by next block
elseif ( renderResult[y][x] == nil ) then
local colspan = 0;
for yToSpan = y, yEnd, yStep do
if ( renderResult[ yToSpan ][x] == nil ) then
colspan = colspan + 1;
else
break;
end
end
if (colspan == 1) then
html = html .. '<td class=Z ></td>\n';
else
html = html .. '<td colspan=' .. colspan .. ' class=Z ></td>\n';
end
end
elseif ( cell == BUSY ) then
-- just skip
elseif ( cell ~= nil and cell.entityId ) then
html = html .. printEntityCell( options, 'colspan=' .. ELEMENT_HEIGHT .. ' rowspan=' .. ELEMENT_WIDTH, cell ) .. '\n';
else
html = html .. '<td class="line ' .. cell .. '">';
for p = 1,string.len(cell) do
local lineClass = ROTATE[string.sub(cell, p, p)];
if ( invert ) then lineClass = INVERT_HORIZONTAL[lineClass] end
html = html .. '<span class=' .. lineClass .. '></span>';
end
html = html .. '</td>\n';
end
end
html = html .. '<td></td></tr>\n'
end
html = html .. '</table></div>'
return html;
end
function renderHtmlVertical( options, node )
local resultHeight = node.resultHeight;
local resultWidth = node.resultWidth;
if (type(resultHeight) ~= 'number') then error("node.resultHeight expected to be number"); end
if (type(resultWidth) ~= 'number') then error("node.resultWidth expected to be number"); end
local renderResult = node.renderResult;
local html = '<div class="wikidata-familyTree wikidata-familyTree-vertical wikidata-familyTree-decorate-' .. options.decorate .. '"><table>\n'
for y=1,resultHeight do
local renderLine = renderResult[y];
html = html .. '<tr>\n'
for x=1,resultWidth do
local cell = renderLine[x];
if (cell == nil) then
html = html .. '<td class=Z></td>\n';
elseif ( cell == BUSY ) then
-- just skip;
elseif ( cell ~= nil and cell.entityId ) then
html = html .. printEntityCell( options, 'colspan=' .. ELEMENT_WIDTH .. ' rowspan=' .. ELEMENT_HEIGHT, cell ) .. '\n';
else
html = html .. '<td class="line ' .. cell .. '">';
for p = 1,string.len(cell) do
html = html .. '<span class=' .. string.sub(cell, p, p) .. '></span>';
end
html = html .. '</td>\n';
end
end
html = html .. '</tr>\n'
end
html = html .. '</table></div>'
return html;
end
function p.drawTree( frame )
local args = frame.args or EMPTY_TABLE;
return p.drawTreeImpl( args );
end
local function toBoolean( valueToParse, defaultValue )
if ( valueToParse ~= nil ) then
if valueToParse == false or valueToParse == '' or valueToParse == 'false' or valueToParse == '0' then
return false
end
if valueToParse == true or valueToParse == 'true' or valueToParse == '1' then
return true
end
end
return defaultValue;
end
function p.drawTreeImpl( args )
local p_frame = mw.getCurrentFrame();
while p_frame do
if p_frame:getTitle() == mw.site.namespaces[10].name .. ':Arbre généalogique Wikidata' then
copyTo( p_frame.args, args, true );
end
if p_frame.args and p_frame.args.from and p_frame.args.from ~= '' then
args.entityId = p_frame.args.from;
end
p_frame = p_frame:getParent();
end
-- p.drawTreeImpl( { entityId="Q7200" } );
local entityId = args.entityId;
if (entityId == "" or entityId == nil) then entityId = mw.wikibase.getEntityIdForCurrentPage(); end
if (entityId == "" or entityId == nil) then return "entityId non spécifié (la page n'est pas liée à Wikidata)"; end
local options = {
mode = args.mode or DEFAULT_MODE,
invert = toBoolean( args.invert, false ),
interleave = toBoolean( args.interleave, false ),
ancestors = args.ancestors or ( ( args.mode or DEFAULT_MODE ) == "horizontal" and 2 or 3),
descendants = args.descendants or ( ( args.mode or DEFAULT_MODE ) == "horizontal" and 2 or 3),
compactParents = args.compactParents or ( ( args.mode or DEFAULT_MODE ) == "horizontal"),
compactChildren = args.compactChildren or true,
years = toBoolean( args.years, args.years ),
descriptions = toBoolean( args.descriptions, args.descriptions ),
decorate = args.decorate or DEFAULT_DECORATE,
}
local lines = {};
lines[0] = {};
local root = createNode(entityId, 0);
table.insert( lines[0], root );
for i=1,options.ancestors do
lines[0 - i] = {};
for _, node in pairs( lines[0 - i + 1] ) do
if ( options.interleave ) then
populateRelation( 'father', 'P22', lines[0 - i], node, node.generation - 1);
populateRelation( 'mother', 'P25', lines[0 - i], node, node.generation - 1);
else
populateRelation( 'parent', 'P22', lines[0 - i], node, node.generation - 1);
populateRelation( 'parent', 'P25', lines[0 - i], node, node.generation - 1);
end
end
end
for i=1,options.descendants do
lines[0 + i] = {};
for _, node in pairs( lines[0 + i - 1] ) do
populateRelation( 'child', 'P40', lines[0 + i], node, node.generation + 1);
if ( node.childNodes and #node.childNodes > 1 ) then
table.sort( node.childNodes, compareEntitiesByBirthDate );
end
end
end
renderRoot( options, root );
local html;
if (options.mode == "horizontal") then
html = renderHtmlHorizontal( options, root );
else
html = renderHtmlVertical( options, root );
end
return html;
end
return p