Module:Clade
![]() | This Lua module is used on approximately 9,200 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
![]() | This module is rated as ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
![]() | This module uses TemplateStyles: |
This module is designed to be used with the clade template to draw phylogenetic trees or cladograms. The new template-module combination extends the feature available with the clade and cladex templates, while replicating the behaviour of the older templates written with the template language. This module is copied from the test version Module:Sandbox/Jts1882/CladeN, which was used with the template Template:CladeN to test the features during development.
Additionally, the module has the code for template {{clade newick converter}}, which is a basic utility to convert Newick strings into nested clade structures. This is not to be used in wikipedia articles, but is a tool to help construct cladograms for inclusion in articles.
The diagram below gives an overview of the features. See the template documentation for a more detailed description of how to use the templates.
Usage
{{#invoke:Clade|main|style={{{style}}}}}
Examples
Simple examples mimicking the clade template:
- User:Jts1882/sandbox/templates/Template:Passeroidea
- User:Jts1882/sandbox/templates/Template:Passerida
Example demonstrating more advanced features of cladeN:
Example demonstrating features available:
Node structure |
|
|label1=Node structure |sublabel1=(brackets)
|style1=background-color:#ffdddd;
|1={{Clade
|label1=label1
|1=leaf 1
|label2=label2
|grouplabel2= leaf 3 is a nested clade structure
|2={{Clade |style=background-color:#ffaaaa;
|label1=label A
|1=leaf A
|label2=label B
|2=leaf B
|bar1=purple |bar2=purple
}}
|label3=label3 |sublabel3=sublabel3
|3=leaf 3
}}
| |||||||||||||||||||||||||||||||||||||||||||||||
(brackets) | |||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||
Leaf styling |
|
|label3=Leaf styling |sublabel3=(branches)
|style3=background-color:#eeeeee;
|3={{Clade
|label1=thicknessN
|1={{Clade
|1=1 (default) |thickness1=1 |barbegin1=blue
|2=2 |thickness2=2 |bar2=blue |barlabel2= line thickness
|3=3 |thickness3=3 |barend3=blue
}}
|label2=colorN
|2={{Clade
|1=black (default) |color1=black
|2=red |color2=red
|3=blue |color3=blue
|4= #00ff00 |color4=#00ff00
}}
|label3=stateN
|3={{Clade
|1=solid (default) |state1=solid |barbegin1=purple
|2=dotted |state2=dotted |bar2=purple
|3=dashed |state3=dashed |bar3=purple |barlabel3= line styles
|4=none |state4=none |bar4=purple
|5=double |state5=double |barend5=purple
}}
}}
| |||||||||||||||||||||||||||||||||||||||||||||||
(branches) | |||||||||||||||||||||||||||||||||||||||||||||||||
node styling |
|
|label4=node styling |sublabel4=(brackets)
|style4=background-color:#ffffee;
|4={{Clade
|label1=thickness
|1={{Clade |thickness=3
|1=I
|2=J
|3=K
}}
|label2=color
|2={{Clade |color=red
|1=A
|2=B
|3=C
}}
|label3=state
|3={{Clade |state=dashed
|1=X
|2=Y
|3=Z
}} }}
| |||||||||||||||||||||||||||||||||||||||||||||||
(brackets) | |||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||
newick |
|
|label6=newick
|style6=background-color:#ddffdd;
|6={{Clade
|label1=string
|1=((lion,jaguar,leopard),((siberian,bengal)tiger,snow leopard))panthera
|label2=subtree
|grouplabel2= expanded Newick string
|grouplabelstyle2=vertical-align: middle;
|2={{Clade |style=background-color:#ccffcc;
|newick1=((lion,jaguar,leopard),((siberian,bengal)tiger,snow leopard))panthera
|1=Leaf1 (redundant if newick1 set)
}}
}}
| |||||||||||||||||||||||||||||||||||||||||||||||
paraphyly example |
|
|label7=paraphyly example |style7=background-color:#ffeeff;
|7={{Clade
|1={{Clade
|1=[[Geraniales]]
|2=[[Myrtales]]
}}
|grouplabel2= label a paraphyletic group within a clade
|label2=clade
|style2=background-color:#ffddff;
|2={{Clade
|1=[[Crossosomatales]] |barbegin1=purple
|2={{Clade
|1=[[Picramniales]] |bar1=purple
|2={{Clade
|1=[[Sapindales]] |bar1=purple
|2={{Clade
|1=[[Huerteales]] |bar1=purple
|2={{Clade
|1=[[Brassicales]] |barend1=purple
|2=[[Malvales]] |style2=background-color:#ff99ff;
}} }} }} }} }} }}
| |||||||||||||||||||||||||||||||||||||||||||||||
A cladogram illustrating clade features. |
Subpages
--[[NOTE: this module contains functions for generating the table structure of the clade tree:
The main function is called by the template using the {{invoke}} instruction; the three main functions are:
p.main(frame) - opens and closes table, loops through the children of node, main is invoked once and controls the rest, calling ...
p.addTaxon(childNumber, nodeLeaf) - the nuts and bolts; code dealing with each child node
p.addLabel(childNumber) - adds the label text
now uses templatestyles
]]
local p = {}
--[[============================== main function ===========================
-- main function, which will generate the table structure of the tree
Test version:
Usage: {{#invoke:Module:Sandbox/Jts1882/CladeN|main|style={{{STYLE|}}} }}
Template:CladeN
Release version:
Usage: {{#invoke:Clade|main|style={{{STYLE|}}} }}
Template:Clade
]]
function p.main(frame)
local cladeString = ""
local maxChildren = 20 -- currently 17 in the clade/cladex templates
local childNumber = 0
lastNode = 0 -- make this global for now (declaring here prevents lua error for nil value in template)
--local nodeCount = 0 -- total leafs plus new clade branches
--local leafCount = 0 -- just the terminal leaves
--local cladeCount = 0 -- new clade calls (each with a table)
local childCount = 0 -- number of leaves in the clade (can use to set bottom of bracket in addTaxon()
local totalCount = 0
infoOutput = p.getCladeTreeInfo() -- get info about clade structure, e.g. lastNode (last |N= child number)
--[[ add the templatestyles tag
add to every clade table >> 137 times in Passeriformes test page
cladeCount==1 >> 43 insertions
nodeCount==2 >> 5 insertions (once for the style css, one link in each cladegram)
2 in Neosuchia (one for style css, one link for second cladogram)
this is because it counts "dummy" clades and outer clades with one leaf
cladeCount==1 AND nodeCount==2 >> 1 insertions
but none in Neosuchia
--]]
-- worked for wrong reasons if nodeCount==2 then
if cladeCount==1 then
local src = "Template:Clade/styles.css"
cladeString = cladeString .. p.templateStyle( frame, src ) .. '\n'
end
local tableStyle = frame.args.style or ""
--if tableStyle == '{{{style}}}' then tableStyle = "" end -- no longer needed as pipe added to template to suppress passing of {{{style}} when no value
if tableStyle ~= "" then
tableStyle = ' style="' .. tableStyle .. '"' -- include style= in string to suppress empty style elements
end
reverseClade =frame.args.reverse or false -- a global
--ENFORCE GLOBAL FOR DEVELOPMENT
--reverseClade = true
local captionName =mw.getCurrentFrame():getParent().args['caption'] or ""
local captionStyle = mw.getCurrentFrame():getParent().args['captionstyle'] or ""
-- add an element to mimick nowiki WORKS BUT DISABLE FOR DEMO PURPOSES
--cladeString = '<p class="mw-empty-elt"></p>\n'
-- open table
-- (border-collapse causes problems (see talk) -- cladeString = cladeString .. '{| style="border-collapse:collapse;border-spacing:0;margin:0;' .. tableStyle .. '"'
-- (before CSS styling) -- cladeString = cladeString .. '{| style="border-spacing:0;margin:0;' .. tableStyle .. '"'
cladeString = cladeString .. '{|class="clade"' .. tableStyle
-- add caption
if captionName ~= "" then
cladeString = cladeString .. '\n|+ style="' .. captionStyle .. '"|' .. captionName
end
-- global nodeParameters (unnumber, i.e. color, thickness, state) apply to whole node bracket,
-- but can be overrriden by branchParameters (numbered, e.g. color2, thickness2, state2)
nodeColor = mw.getCurrentFrame():getParent().args['color'] or ""
nodeThickness = tonumber(mw.getCurrentFrame():getParent().args['thickness']) or 1
nodeState = mw.getCurrentFrame():getParent().args['state'] or "solid"
local moreNeeded = true
childNumber = 0
--lastNode = 0
--[[get child elements (add more rows for each child of node; each child is two rows)
the function addTaxon is called to add the rows for each child element;
each child add two rows: the first cell of each row contains the label or sublabel (below the line label), respectively;
the second cell spans both rows and contains the leaf name or a new clade structure
a third cell on the top row is sometimes added to contain a group to the right
]]
-- main loop
while childNumber < lastNode do -- use the last number determined in the preprocessing
childNumber = childNumber + 1 -- so we start with 1
local nodeLeaf = mw.getCurrentFrame():getParent().args[tostring(childNumber)] or "" -- get data from |N=
local nodeLabel = mw.getCurrentFrame():getParent().args['label'..tostring(childNumber)] or "" -- get data from |labelN=
local newickString = mw.getCurrentFrame():getParent().args['newick'..tostring(childNumber)] or "" -- get data from |labelN=
if newickString ~= "" then -- if using a newick string instead of a clade structure
if nodeLabel == "" then -- use labelN by default, otherwise use root name from Newick string
nodeLabel = p.getNewickOuterterm(newickString) -- need to use terminal part of newick string for label
end
newickString = p.processNewickString(newickString,childNumber)
cladeString = cladeString .. '\n' .. p.addTaxon(childNumber, p.newick(0, newickString), nodeLabel, lastNode)
--lastNode=lastNode+1 -- there is a counting problem with the newickstring
elseif nodeLeaf ~= "" then -- if the node contains a leaf name or clade structue
--if reverseClade2 then
-- cladeString = cladeString .. '\n' .. p.addTaxonReverse(childNumber, nodeLeaf, nodeLabel, lastNode)
--else
cladeString = cladeString .. '\n' .. p.addTaxon(childNumber, nodeLeaf, nodeLabel, lastNode)
--end
end
end
local footerText = mw.getCurrentFrame():getParent().args['footer'] or ""
local footerStyle = mw.getCurrentFrame():getParent().args['footerstyle'] or ""
if footerText ~= "" then
cladeString = cladeString .. '\n|-style="' .. footerStyle .. '"\n|colspan="2"|<p>' .. footerText .. '</p>||'
-- note the footer causes a problem with tr:last-child so need either
-- (1) use <tfoot> but it is not allowed or incompatable
-- cladeString = cladeString .. '<tfoot><tr style="' .. footerStyle .. '"><td colspan="2"><p>' .. footerText .. '</p></td></tr></tfoot>'
-- (2) always add footer and use nth:last-child(2) but is this backwards compatible
-- (3) if footer= set the style inline for the last sublabel row (more a temp fix)
-- (4) set class for first and last element (DONE. Also works well with reverse class)
end
-- close table (wikitext to close table)
cladeString = cladeString .. '\n|}'
cladeString = p.addSubTrees(cladeString) -- add subtrees
return cladeString
--return '<div style="width:auto;">\n' .. cladeString .. '</div>'
end
--[[ =============================function to add subtrees ========================================== ]]
function p.addSubTrees(cladeString)
local pargs = mw.getCurrentFrame():getParent().args
local suffix = { [1]="A", [2]="B", [3]="C", [4]="D", [5]="E"}
for i=1, 5, 1 do
local subclade = pargs['subclade'..suffix[i]]
local target = pargs['target'..suffix[i]] or "SUBCLADE_" .. suffix[i]
if subclade then
if string.find(cladeString, target) then
cladeString = string.gsub(cladeString,target,subclade)
end
end
end
return cladeString
end
--[[ function to add child elements
adds wikitext for two rows of the table for each child node,
the first cell in each is used for the label and sublabel; the bottom border forms the horizonal branch of the bracket
the second cell is used for the leafname or a transcluded clade structure and spans both rows
note that the first and last child nodes need to be handled differently from the middle elements
the middle elements (|2, |3 ...) use a left border to create the vertical line of the bracket
the first child element doesn't use a left border for the first cell in the top row (as it is above the bracket)
the last child doesn't use a left border for the first cell in the second row (as it is below the bracket)
]]
function p.addTaxon(childNumber, nodeLeaf, nodeLabel, lastNode)
-- get border formating parameters for branch (default to global nodeParameters)
-- - the branch parameters have a number, e.g. |colorN, |thicknessN, |stateN
-- - the node parameters have no number, e.g. |color, |thickness, |state
local branchThickness = tonumber(mw.getCurrentFrame():getParent().args['thickness'..tostring(childNumber)]) or nodeThickness
local branchColor = mw.getCurrentFrame():getParent().args['color'..tostring(childNumber)] or nodeColor
local branchStyle = mw.getCurrentFrame():getParent().args['style'..tostring(childNumber)] or ""
local branchState = mw.getCurrentFrame():getParent().args['state'..tostring(childNumber)] or nodeState -- "solid"
if branchState == 'double' then if branchThickness < 2 then branchThickness = 3 end end -- need thick line for double
-- the left border takes node parameters, the bottom border takes branch parameters
-- this has coding on the colours for green on black
local bottomBorder = tostring(branchThickness) ..'px ' .. branchState .. (branchColor~="" and ' ' .. branchColor or '')
local leftBorder = tostring(nodeThickness) ..'px ' .. nodeState .. (nodeColor~="" and ' ' .. nodeColor or '')
--The default border styles are in the CSS (styles.css)
-- the inline styling is applied when thickness, color or state are change
local useInlineStyle = false
-- use inline styling non-default color, line thickness or state have been set
if branchColor ~= "" or branchThickness ~= 1 or branchState ~= "solid" then
useInlineStyle = true
end
-- variables for right hand bar or bracket
--local barColor = ""
local barRight = mw.getCurrentFrame():getParent().args['bar'..tostring(childNumber)] or "0"
local barBottom = mw.getCurrentFrame():getParent().args['barend'..tostring(childNumber)] or "0"
local barTop = mw.getCurrentFrame():getParent().args['barbegin'..tostring(childNumber)] or "0"
local barLabel = mw.getCurrentFrame():getParent().args['barlabel'..tostring(childNumber)] or ""
local groupLabel = mw.getCurrentFrame():getParent().args['grouplabel'..tostring(childNumber)] or ""
local groupLabelStyle = mw.getCurrentFrame():getParent().args['labelstyle'..tostring(childNumber)] or ""
--replace colours with format string; need right bar for all three options
if barRight ~= "0" then barRight = "2px solid " .. barRight end
if barTop ~= "0" then barRight = "2px solid " .. barTop end
if barBottom ~= "0" then barRight = "2px solid " .. barBottom end
if barTop ~= "0" then barTop = "2px solid " .. barTop end
if barBottom ~= "0" then barBottom = "2px solid " .. barBottom end
-- now construct wikitext
local cladeString = ''
local styleString = ''
local borderStyle = '' -- will be used if border color, thickness or state is to be changed
local classString = ''
local reverseClass = ''
-- class to add if using reverse (rtl) cladogram;
if reverseClade then reverseClass = ' reverse' end
-- (1) wikitext for new row
--cladeString = cladeString .. '\n|-'
-- (2) now add cell with label
if useInlineStyle then
if childNumber == 1 then
borderStyle = 'border-left:none;border-right:none;border-bottom:' .. bottomBorder .. ';'
--borderStyle = 'border-bottom:' .. bottomBorder .. ';'
else -- for 2-17
if reverseClade then
borderStyle = 'border-left:none;border-right:' .. leftBorder .. ';border-bottom:' .. bottomBorder .. ';'
else
borderStyle = 'border-left:' .. leftBorder .. ';border-bottom:' .. bottomBorder .. ';'
end
end
end
if useInlineStyle or (branchStyle ~= '') then
styleString = 'style="' .. borderStyle .. branchStyle .. '"'
end
if childNumber == 1 then
classString= 'class="clade-label first" ' -- add class first for top row
else
classString = 'class="clade-label' .. reverseClass .. '" ' -- add "reverse" class if ltr cladogram
end
-- wikitext for cell with label
local labelCellString = '\n|' .. classString .. styleString .. '|' .. p.addLabel(childNumber,nodeLabel) -- p.addLabel(nodeLabel)
--cladeString = cladeString .. labelCellString
---------------------------------------------------------------------------------
-- (3) add cell with leaf (which may be a table with transluded clade content)
if barRight ~= "0" then
if reverseClade then -- we want the bar on the left
styleString = ' style="border-left:' .. barRight .. ';border-bottom:' .. barBottom .. ';border-top:' .. barTop .. ';' .. branchStyle .. '"'
else
styleString = ' style="border-right:' .. barRight .. ';border-bottom:' .. barBottom .. ';border-top:' .. barTop .. ';' .. branchStyle .. '"'
end
else
if (branchStyle ~= '') then
styleString = ' style="' .. branchStyle .. '"'
else
styleString = '' -- use defaults in styles.css
end
end
-- wikitext for leaf cell (note: nodeLeaf needs to be on newline if new wikitable)
classString = 'class="clade-leaf' .. reverseClass .. '"'
local leafCellString = '\n|rowspan=2 ' .. classString .. styleString .. ' |\n' .. nodeLeaf
--cladeString = cladeString .. leafCellString
-------------------------------------------
-- (4) stuff for right-hand bracket labels
classString='class="clade-bar' .. reverseClass .. '"'
local barLabelCellString = ''
if barRight ~= "0" and barLabel ~= "" then
barLabelCellString = '\n|rowspan=2 ' .. classString .. ' |' .. barLabel
else -- uncomment following line to see the cell structure
--barLabelCellString = '\n|rowspan=2 ' .. classString .. ' |' .. 'BL'
end
if groupLabel ~= "" then
barLabelCellString = barLabelCellString .. '\n|rowspan=2 ' .. classString .. ' style="'.. groupLabelStyle .. '" |' .. groupLabel
else -- uncomment following line to see the cell structure
--barLabelCellString = barLabelCellString .. '\n|rowspan=2 ' .. classString .. '" |' .. 'GL'
end
--cladeString = cladeString .. barLabelCellString
-------------------------------------------------------------------------------------
-- (5) add second row (only one cell needed for sublabel because of rowspan=2);
-- note: earlier versions applied branch style to row rather than cell
-- for consistency, it is applied to the sublabel cell as with the label cell
--cladeString = cladeString .. '\n|-'
-----------------------------------
-- (6) add cell containing sublabel
local subLabel = mw.getCurrentFrame():getParent().args['sublabel'..tostring(childNumber)] or "" -- request in addLabel
-- FOR TESTING: use subLabel for annotating the clade structues to use structure information (DEBUGGIING ONLY)
--if childNumber==lastNode then subLabel= infoOutput end
-- END TESTING
borderStyle = ''
styleString = ''
if useInlineStyle then
if childNumber==lastNode then -- if childNumber==lastNode we don't want left border, otherwise we do
borderStyle = 'border-right:none;border-left:none;'
else
if reverseClade then
borderStyle = 'border-left:none;border-right:' .. leftBorder .. ';'
else
borderStyle = 'border-right:none;border-left:' .. leftBorder .. ';'
end
end
end
if borderStyle ~= '' or branchStyle ~= '' then
styleString = ' style="' .. borderStyle .. branchStyle .. '"'
end
--local sublabel = p.addLabel(childNumber,subLabel)
if childNumber == lastNode then
classString = 'class="clade-slabel last" '
else
classString = 'class="clade-slabel' .. reverseClass .. '" '
end
local sublabelCellString = '\n|' .. classString .. styleString .. '|' .. p.addLabel(childNumber,subLabel)
--cladeString = cladeString .. sublabelCellString
-- constuct child element wikitext
if reverseClade then
cladeString = cladeString .. '\n|-'
cladeString = cladeString .. barLabelCellString
cladeString = cladeString .. leafCellString
cladeString = cladeString .. labelCellString
cladeString = cladeString .. '\n|-'
cladeString = cladeString .. sublabelCellString
else
cladeString = cladeString .. '\n|-'
cladeString = cladeString .. labelCellString
cladeString = cladeString .. leafCellString
cladeString = cladeString .. barLabelCellString
cladeString = cladeString .. '\n|-' -- add second row (only one cell needed for sublabel because of rowspan=2);
cladeString = cladeString .. sublabelCellString
end
return cladeString
end
---------------------------------------------------------------------------------------------
--[[ reverse version;
currently duplicates the code changing order of elements (bars, leaf, label, sublabel)
variables border-right
--]]
function p.addTaxonReverse(childNumber, nodeLeaf, nodeLabel, lastNode)
-- (1) get formating parameters for branch (default to global nodeParameters)
-- - the branch parameters have a number, e.g. |colorN, |thicknessN, |stateN
-- - the node parameters have no number, e.g. |color, |thickness, |state
local branchThickness = tonumber(mw.getCurrentFrame():getParent().args['thickness'..tostring(childNumber)]) or nodeThickness
local branchColor = mw.getCurrentFrame():getParent().args['color'..tostring(childNumber)] or nodeColor
local branchStyle = mw.getCurrentFrame():getParent().args['style'..tostring(childNumber)] or ""
local branchState = mw.getCurrentFrame():getParent().args['state'..tostring(childNumber)] or nodeState -- "solid"
if branchState == 'double' then if branchThickness < 2 then branchThickness = 3 end end -- need thick line for double
-- the left border takes node parameters, the bottom border takes branch parameters
local bottomBorder = tostring(branchThickness) ..'px ' .. branchState .. (branchColor~="" and ' ' .. branchColor or '')
local leftBorder = tostring(nodeThickness) ..'px ' .. nodeState .. (nodeColor~="" and ' ' .. nodeColor or '')
-- variables for right hand bar or bracket
--local barColor = ""
local barRight = mw.getCurrentFrame():getParent().args['bar'..tostring(childNumber)] or "0"
local barBottom = mw.getCurrentFrame():getParent().args['barend'..tostring(childNumber)] or "0"
local barTop = mw.getCurrentFrame():getParent().args['barbegin'..tostring(childNumber)] or "0"
local barLabel = mw.getCurrentFrame():getParent().args['barlabel'..tostring(childNumber)] or ""
local groupLabel = mw.getCurrentFrame():getParent().args['grouplabel'..tostring(childNumber)] or ""
local groupLabelStyle = mw.getCurrentFrame():getParent().args['labelstyle'..tostring(childNumber)] or ""
--replace colours with format string; need right bar for all three options
if barRight ~= "0" then barRight = "2px solid " .. barRight end
if barTop ~= "0" then barRight = "2px solid " .. barTop end
if barBottom ~= "0" then barRight = "2px solid " .. barBottom end
if barTop ~= "0" then barTop = "2px solid " .. barTop end
if barBottom ~= "0" then barBottom = "2px solid " .. barBottom end
-- now construct wikitext
local styleString = ''
local cladeString = ''
-- (1) wikitext for new row
cladeString = cladeString .. '\n|-'
-- (4) stuff for right-hand brackets (or left hand brackets in reverse version)
-- note: it might be useful to add rowspan=2 to allow full range of positions
if groupLabel ~= ""then
cladeString = cladeString .. '\n|rowspan=2 class="clade-bar" style="'.. groupLabelStyle .. '" |' .. groupLabel
end
--[[ don't use bar label in the reverse version (probably best to get rid altogether)
if barRight ~= "0" then
cladeString = cladeString .. ' ' -- add spaces between leaf text and bar
if barLabel ~= "" then
cladeString = cladeString .. '\n|rowspan=2 style="vertical-align:middle;" |' .. barLabel
end
end
--]]
-- (3) add cell with leaf (which may be a table with transluded clade content)
--cladeString = cladeString .. '|| rowspan=2 style="border: 0; padding: 0; border-right: ' .. barRight .. ';border-bottom: ' .. barBottom .. ';border-top: ' .. barTop .. ';' .. branchStyle .. ' " | '
styleString = ''
if barRight ~= "0" then
--(before CSS styling) -- styleString = 'style="border:0;padding:0;border-right:' .. barRight .. ';border-bottom:' .. barBottom .. ';border-top:' .. barTop .. ';' .. branchStyle .. '"'
styleString = ' style="border-left:' .. barRight .. ';border-bottom:' .. barBottom .. ';border-top:' .. barTop .. ';' .. branchStyle .. '"'
else
--(before CSS styling) -- styleString = 'style="border:0;padding:0;' .. branchStyle .. '"'
if (branchStyle ~= '') then
styleString = ' style="' .. branchStyle .. '"'
end
end
-- add wikitext for leaf cell (note: nodeLeaf needs to be on newline if new wikitable)
cladeString = cladeString .. '\n|rowspan=2 class="clade-leafR"' .. styleString .. '|\n' .. nodeLeaf
--cladeString = cladeString .. '\n' .. nodeLeaf -- needs to be on newline if new wikitable
-- (2) now add cell with label
styleString = ''
if childNumber == 1 then
-- the width gives minimum spacing when all labels are empty (was 1.5em)
--(before CSS styling) -- styleString = ' style="width:1em;border:0;padding:0 0.2em;border-bottom:' .. bottomBorder .. ';vertical-align:bottom;text-align:center;' .. branchStyle .. '"'
styleString = 'style="border-bottom:' .. bottomBorder .. ';' .. branchStyle .. '"'
else -- for 2-17
--(before CSS styling) -- styleString = ' style="border:0;padding:0 0.2em;border-left:' .. leftBorder .. ';border-bottom:' .. bottomBorder .. ';vertical-align:bottom;text-align:center;' .. branchStyle .. '"'
styleString = 'style="border-right:' .. leftBorder .. ';border-bottom:' .. bottomBorder .. ';' .. branchStyle .. '"'
end
-- add wikitext for cell with label
cladeString = cladeString .. '\n|class="clade-label" ' .. styleString .. '|' .. p.addLabel(childNumber,nodeLabel) -- p.addLabel(nodeLabel)
-- (5) add second row (only one cell needed for sublabel because of rowspan=2)
local branchStyleString = 'style="' .. branchStyle .. '" '
if branchStyle == '' then branchStyleString = '' end -- avoid empty style elements
cladeString = cladeString .. '\n|-' .. branchStyleString
local subLabel = mw.getCurrentFrame():getParent().args['sublabel'..tostring(childNumber)] or "" -- request in addLabel
-- FOR TESTING: use subLabel for annotating the clade structues to use structure information (DEBUGGIING ONLY)
--subLabel= '(N=' .. infoOutput .. ')'
-- END TESTING
-- (6) add cell containing sublabel
if childNumber~=lastNode then -- if childNumber==lastNode we don't want left border, otherwise we do
styleString = ' style="border-right:' .. leftBorder ..';"'
else
--cladeString = cladeString .. '\n' .. '| style="border: 0; padding: 0; vertical-align: top;" | <br/> '
--(before CSS styling) -- styleString = 'style="border:0;vertical-align:top;text-align:center;"'
styleString = ''
end
local sublabel = p.addLabel(childNumber,subLabel)
local classString = '' -- class only needed if there is a label to display
if sublabel ~= '<br/>' then classString = 'class="clade-slabel" ' end
cladeString = cladeString .. '\n|' .. classString .. styleString .. '|' .. sublabel
return cladeString
end
--[[ adds text for label or sublabel to a cell
]]
function p.addLabel(childNumber,nodeLabel)
--local nodeLabel = mw.getCurrentFrame():getParent().args['label'..tostring(childNumber)] or ""
--local firstChars = string.sub(nodeLabel, 1,2) -- get first two characters; will be {{ if no parameter (for Old method?)
--if firstChars == "{{{" or nodeLabel == "" then
if nodeLabel == "" then
--return '<br/>' --' <br/>' -- remove space to reduce post-expand include size (the width=1.5em handles spacing)
--return '<br/>' -- must return something; this is critical for clade structure
return ' ' --    (thin nbsp)
else
-- spaces can cause wrapping and can break tree structure, hence use span with nowrap class
--return '<span class="nowrap">' .. nodeLabel .. '</span>'
-- a better method for template expansion size is to replace spaces with nonbreaking spaces
-- however, there is a problem if labels have a styling element (e.g. <span style= ..., <span title= ...)
local stylingElementDetected = false
if string.find(nodeLabel, "span ") ~= nil then stylingElementDetected = true end
if string.find(nodeLabel, " style") ~= nil then stylingElementDetected = true end
if stylingElementDetected == true then
return '<span class="nowrap">' .. nodeLabel .. '</span>'
else
local nowrapString = string.gsub(nodeLabel," ", " ") -- replace spaces with non-breaking space
-- could replace hyphen with non-breaking hyphen (‑)
return nowrapString
end
end
end
--[[=================== Newick string handling function =============================
]]
function p.getNewickOuterterm(newickString)
return string.gsub(newickString, "%b()", "") -- delete parenthetic term
end
function p.newick(count,newickString)
local cladeString = ""
count = count+1
--start table
--cladeString = cladeString .. '{| style="border-collapse:collapse;border-spacing:0;border:0;margin:0;'
cladeString = cladeString .. '{| class="clade" '
local j,k
j,k = string.find(newickString, '%(.*%)') -- find location of outer parenthesised term
local innerTerm = string.sub(newickString, j+1, k-1) -- select content in parenthesis
local outerTerm = string.gsub(newickString, "%b()", "") -- delete parenthetic term
if outerTerm == 'panthera' then outerTerm = "x" end -- how is this set in local variable for inner nodes?
outerTerm = tostring(count)
-- need to remove commas in bracket terms before split, so temporarily replace commas between brackets
local innerTerm2 = string.gsub(innerTerm, "%b()", function (n)
return string.gsub(n, ",%s*", "XXX") -- also strip spaces after commas here
end)
--cladeString = cladeString .. '\n' .. p.addTaxon(1, innerTerm2, "")
-- this needs a lastNode variable
local s = strsplit(innerTerm2, ",")
--oldLastNode=lastNode
local lastNode=table.getn(s) -- number of child branches
local i=1
while s[i] do
restoredString = string.gsub(s[i],"XXX", ",") -- convert back to commas
--restoredString = s[i]
local outerTerm = string.gsub(restoredString, "%b()", "")
if string.find(restoredString, '%(.*%)') then
--cladeString = cladeString .. '\n' .. p.addTaxon(i, restoredString, "x")
cladeString = cladeString .. '\n' .. p.addTaxon(i, p.newick(count,restoredString), outerTerm, lastNode)
-- p.addTaxon(2, p.newick(count,newickString2), "root")
else
cladeString = cladeString .. '\n' .. p.addTaxon(i, restoredString, "", lastNode) --count)
end
i=i+1
end
-- lastNode=oldLastNode
-- close table
--cladeString = cladeString .. '\n' .. '| style="border: 0; padding: 0; vertical-align: top;" | <br/> \n|}'
--cladeString = cladeString .. '\n| <br/> \n|}' -- is this legacy for extra sublabel?
cladeString = cladeString .. '\n|}'
return cladeString
end
-- emulate a standard split string function
function strsplit(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={} ; i=1
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
t[i] = str
i = i + 1
end
return t
end
-- =================== experimental Newick to clade parser function =============================
--[[Function of convert Newick strings to clade format
Usage: {{#invoke:Module:Sandbox/Jts1882/CladeN|newickConverter|newickstring={{{NEWICK_STRING}}} }}
]]
function p.newickConverter(frame)
local newickString = frame.args['newickstring']
if newickString == '{{{newickstring}}}' then return newickString end
-- show the Newick string
local cladeString = ''
local levelNumber = 1 -- for depth of iteration
local childNumber = 1 -- number of sister elements on node (always one for root)
-- converted the newick string to the clade structure
cladeString = cladeString .. '{{clade'
cladeString = cladeString .. p.newickParseLevel(newickString, levelNumber, childNumber)
cladeString = cladeString .. '\r}}'
local resultString = ''
local option = mw.getCurrentFrame():getParent().args['option'] or ''
if option == 'tree' then
--show the transcluded clade diagram
resultString = cladeString
else
-- show the Newick string
resultString = '<pre>'..newickString..'</pre>'
-- show the converted clade structure
resultString = resultString .. '<pre>'.. cladeString ..'</pre>'
end
--resultString = frame:expandTemplate{ title = 'clade', frame:preprocess(cladeString) }
return resultString
end
--[[ Parse one level of Newick string
This function receives a Newick string, which has two components
1. the right hand term is a clade label: |labelN=labelname
2. the left hand term in parenthesis has common delimited child nodes, each of which can be
i. a taxon name which just needs: |N=leafname
ii. a Newick string which needs further processing through reiteration
]]
function p.newickParseLevel(newickString,levelNumber,childNumber)
local cladeString = ""
local indent = p.getIndent(levelNumber)
--levelNumber=levelNumber+1
local j=0
local k=0
j,k = string.find(newickString, '%(.*%)') -- find location of outer parenthesised term
local innerTerm = string.sub(newickString, j+1, k-1) -- select content in parenthesis
local outerTerm = string.gsub(newickString, "%b()", "") -- delete parenthetic term
cladeString = cladeString .. indent .. '|label'..childNumber..'=' .. outerTerm
cladeString = cladeString .. indent .. '|' .. childNumber..'=' .. '{{clade'
levelNumber=levelNumber+1
indent = p.getIndent(levelNumber)
-- protect commas in inner parentheses from split; temporarily replace commas between parentheses
local innerTerm2 = string.gsub(innerTerm, "%b()", function (n)
return string.gsub(n, ",%s*", "XXX") -- also strip spaces after commas here
end)
local s = strsplit(innerTerm2, ",")
local i=1
while s[i] do
restoredString = string.gsub(s[i],"XXX", ",") -- convert back to commas
local outerTerm = string.gsub(restoredString, "%b()", "")
if string.find(restoredString, '%(.*%)') then
--cladeString = cladeString .. indent .. '|y' .. i .. '=' .. p.newickParseLevel(restoredString,levelNumber+1,i)
cladeString = cladeString .. p.newickParseLevel(restoredString,levelNumber,i)
else
cladeString = cladeString .. indent .. '|' .. i .. '=' .. restoredString --.. '(level=' .. levelNumber .. ')'
end
i=i+1
end
-- end -- end splitting of strings
cladeString = cladeString .. indent .. '}}'
return cladeString
end
function p.getIndent(levelNumber)
local indent = "\r"
local extraIndent = mw.getCurrentFrame():getParent().args['indent'] or 0
while tonumber(extraIndent) > 0 do
indent = indent .. " " -- an extra indent to make aligining compound trees easier
extraIndent = extraIndent - 1
end
while levelNumber > 1 do
indent = indent .. " "
levelNumber = levelNumber-1
end
return indent
end
function p.newickstuff(newickString)
end
function p.processNewickString(newickString,childNumber)
local pattern = mw.getCurrentFrame():getParent().args['newick-pattern'..tostring(childNumber)]
local replace = mw.getCurrentFrame():getParent().args['newick-replace'..tostring(childNumber)]
newickString = string.gsub (newickString, "_", " ")
newickString = string.gsub (newickString, pattern, replace)
return newickString
end
------------------------------------------------------------------------------------------
function p.test2(target)
local target ="User:Jts1882/sandbox/templates/Template:Passeroidea"
local result = mw.getCurrentFrame():expandTemplate{ title = target, args = {['style'] = '' } }
return result
end
-------------------------------------------------------------------------------------------
function p.toggle(frame)
if 1==2 then return 'some text' end
--local toggleSymbol = 'toggle all'
local toggleSymbol = mw.getCurrentFrame():getParent().args['button'] or ""
local toggleString = '<div class="'
local i=0
while i < 20 do -- limit on number of toggle elements controlled by the trigger button
i = i + 1 -- so we start with 1
local target = mw.getCurrentFrame():getParent().args['id'..tostring(i)]
-- add classes for the three elements of each target: expand symbol, collapse symbol and contents
if target ~= nil then
toggleString = toggleString .. ' mw-customtoggle-myClade' .. target
.. ' mw-customtoggle-collapseSymbol' .. target
.. ' mw-customtoggle-expandSymbol' .. target
end
end
toggleString = toggleString .. '">' .. toggleSymbol .. '</div>'
return toggleString
end
-----------------------------------------------------------------------------------------------
function p.hidden(frame)
local id = mw.getCurrentFrame():getParent().args['id'] or ""
local mode = mw.getCurrentFrame():getParent().args['mode'] or "right"
local expandSymbol = mw.getCurrentFrame():getParent().args['expand-symbol'] or "⊞"
local collapseSymbol = mw.getCurrentFrame():getParent().args['collapse-symbol'] or "⊟"
local initialState = mw.getCurrentFrame():getParent().args['expanded']
-- default is content collapsed
local contentState = " mw-collapsed" -- class to collapse content at start
local collapseSymbolState = " mw-collapsed"
local expandSymbolState = ""
if initialState then
contentState = ""
collapseSymbolState = ""
expandSymbolState = " mw-collapsed"
end
-- collapsible element containing the expand sympol and/or text
local expandSymbolString = '<td style="padding:0 0 0.25em 0;">'
.. '<div class="mw-collapsible' .. expandSymbolState .. '" id="mw-customcollapsible-expandSymbol' .. id .. '">'
.. '<div class="mw-collapsible-content mw-customtoggle-expandSymbol' .. id .. '">'
.. '<span class="mw-customtoggle-myClade' .. id
.. ' mw-customtoggle-collapseSymbol' .. id
.. ' mw-customtoggle-expandSymbol' .. id
.. '" style="font-size:100%;">' .. expandSymbol .. '</span>'
.. '</div></div></td>'
-- collapsible element containing the clade content
local contentString = '<td style="padding:0;">'
.. '<div class="mw-collapsible' .. contentState .. '" id="mw-customcollapsible-myClade' .. id .. '>'
.. '<div class="mw-collapsible-content mw-customtoggle-NOT_ON_CONTENT" >' -- don't toggle on the content
.. '\n' .. p.main(frame) -- important to start wikitext tables on new line
.. '</div></div></td>'
-- collapsible element containing the collapse sympol and/or text
local collapseSymbolString = '<td style="padding:0 0 0.4em 0;">'
.. '<div class="mw-collapsible' .. collapseSymbolState .. '" id="mw-customcollapsible-collapseSymbol' .. id .. '">'
.. '<div class="mw-collapsible-content mw-customtoggle-collapseSymbol' .. id .. '" >'
.. '<span class="mw-customtoggle-expandSymbol' .. id
.. ' mw-customtoggle-myClade' .. id
.. ' mw-customtoggle-collapseSymbol' .. id
.. ' " style="font-size:100%;" >' .. collapseSymbol .. '</span>'
.. '</div></div></td>'
local tableStyle = frame.args.style or ""
if tableStyle == '{{{style}}}' then tableStyle = "" end
local cladeString = '<table style="border-spacing:0;margin:0;'..tableStyle ..'"><tr>'
cladeString = cladeString .. expandSymbolString
if mode == "left" then
cladeString = cladeString .. collapseSymbolString
end
cladeString = cladeString .. contentString
if mode == "right" then
cladeString = cladeString .. collapseSymbolString
end
-- Note: if we want collapse string left and right it needs an extra element with a different id
cladeString = cladeString .. '</tr></table>'
return cladeString
end
------------------------------------------------------------------------------------------
--[[function getCladeTreeInfo()
this preprocessing loop gets information about the whole structure (number of nodes, leaves etc)
it makes a redundant calls to the templates through transclusion, but doen't affect the template depths;
it provides the global lastNode that is used to limit the main while loop
--]]
function p.getCladeTreeInfo()
-- enable proprocessing loop
local childNumber = 0
local childCount =0
local maxChildren =20
--info veriables (these are global for now)
nodeCount=0
cladeCount=0
leafCount=0
while childNumber < maxChildren do -- preprocessing loop
childNumber = childNumber + 1 -- so we start with 1
local nodeLeaf,data = mw.getCurrentFrame():getParent().args[tostring(childNumber)] or "" -- get data from |N=
local newickString = mw.getCurrentFrame():getParent().args['newick'..tostring(childNumber)] or "" -- get data from |labelN=
if newickString ~= "" or nodeLeaf ~= "" then
--if nodeLeaf ~= "" then
childCount = childCount + 1 -- this counts child elements in this clade
--[[]
for i in string.gmatch(nodeLeaf, "||rowspan") do -- count number of rows started (transclusion)
nodeCount = nodeCount + 1
end
for i in string.gmatch(nodeLeaf, '{|class="clade"') do -- count number of tables started (transclusion)
cladeCount = cladeCount + 1
end
]]
-- count occurences of clade structure using number of classes used and add to counters
local _, nClades = string.gsub(nodeLeaf, 'class="clade"', "")
local _, nNodes = string.gsub(nodeLeaf, 'class="clade%-leaf"', "")
cladeCount = cladeCount + nClades
nodeCount = nodeCount + nNodes
lastNode = childNumber -- this gets the last node with a valid entry, even when missing numbers
end
end
--]]
-- nodes can be either terminal leaves or a clade structure (table)
-- note: should change class clade-leaf to clade-node to reflect this
nodeCount = nodeCount -- number of nodes (class clade-leaf) passed down by transduction
+ childCount + 1 -- plus one for current clade and one for each of its child element
cladeCount = cladeCount + 1 -- number of clade structure tables passed down by transduction (plus one for current clade)
leafCount = nodeCount-cladeCount -- number of terminal leaves (equals height of cladogram)
-- output for testing: number of clades / total nodes / terminal nodes (=leaves)
-- (internal nodes) (cladogram height)
infoOutput = '<small>[' .. cladeCount .. '/' .. nodeCount .. '/' .. leafCount .. ']</small>'
return infoOutput
end
--[[ code for placing TemplateStyles from the module
source: Anomie (CC-0) https://phabricator.wikimedia.org/T200442
]]
function p.templateStyle( frame, src )
return frame:extensionTag( 'templatestyles', '', { src = src } );
end
function p.showClade(frame)
--local code = frame.args.code or ""
local code = frame:getParent().args['code2'] or ""
--return code
--return mw.text.unstrip(code)
--local test = "<pre>Hello</pre>"
--return string.sub(test,6,-7)
local o1 =frame:getParent():getArgument('code2')
return o1:expand()
--return string.sub(code,2,-1) -- strip marker \127'"`UNIQ--tagname-8 hex digits-QINU`"'\127
--return frame:preprocess(string.sub(code,3))
end
function p.testTable(frame)
-- build HTML table
local galleryTable = mw.html.create('table')
galleryTable:addClass('wikitable')
galleryTable:addClass('sortable')
galleryTable:css('text-align', 'center')
galleryTable:tag('caption'):wikitext('Table created with mw.html')
local row = galleryTable:tag('tr')
row:tag('th'):wikitext('Header 1')
row:tag('th'):wikitext('Header 2')
row:tag('th'):wikitext('Header 3')
--:attr('rowspan', 2)
row = galleryTable:tag('tr')
row:tag('td'):css('text-align', 'left'):wikitext('A1')
row:tag('td'):css('text-align', 'left'):wikitext('B1')
row:tag('td'):css('text-align', 'left'):wikitext('C1')
local row2 = mw.html.create('tr') -- create an independent row element
row2:tag('td'):css('text-align', 'left'):wikitext('A3')
row2:tag('td'):css('text-align', 'left'):wikitext('B3')
row2:tag('td'):css('text-align', 'left'):wikitext('C3')
row = galleryTable:tag('tr')
row:tag('td'):css('text-align', 'left'):wikitext('A2')
row:tag('td'):css('text-align', 'left'):wikitext('B2')
row:tag('td'):css('text-align', 'left'):wikitext('C2')
galleryTable:node(row2) -- now add row2 as node
return tostring(galleryTable)
end
function p.example(frame)
local params = mw.getCurrentFrame():getParent().args
-- build HTML table
local exampleTable = mw.html.create('table')
exampleTable:addClass('wikitable')
local align = params['align'] or 'center'
if align == 'center' then
exampleTable:css('margin-left', 'auto')
exampleTable:css('margin-right','auto')
elseif align == 'right' then
exampleTable:css('float',align)
end
local columns = {'description','code','output','comment'}
--local headers = {'Description','Code','Output','Comment'}
-- create header row and add requested headers
local row = exampleTable:tag('tr')
local headerText
for k,v in pairs(columns) do
if params[v] then
if params[v]~='' then headerText=params[v] else headerText = firstToUpper(v) end
row:tag('th'):wikitext(headerText)
end
end
-- now deal with the data rows
local i=0
while i<10 do
i=i+1
local moreRows = false
for k,v in pairs(columns) do
if params[v..i] then moreRows = true end
end
if not moreRows then break end
row = exampleTable:tag('tr')
for k,v in pairs(columns) do
if params[v] then
if params[v] then
row:tag('td'):css('text-align', 'left'):wikitext(params[v..i])
else
row:tag('td')
end
end
end
end
return tostring(exampleTable)
end
function firstToUpper(str)
return (str:gsub("^%l", string.upper))
end
-- display a gallery of cladograms using floating div elements
function p.gallery(frame)
local params = mw.getCurrentFrame():getParent().args
local outerBorders = false
if params['caption'] or params['footer'] then outerBorders = true end
-- the gallery container
local gallery = mw.html.create('div'):addClass('clade-gallery'):css('float',params['align'])
-- main caption for gallery if there is one
if params['main-caption'] then
gallery:tag('div'):wikitext(params['main-caption']):addClass('main-caption')
end
-- a gallery of floating divs with content
local shading = ''
--if params['shading'] then shading = 'shading' end -- class for shading
if params['shading'] then shading = 'wikitable' end -- class for shading
local vertical = '' -- defaults to horizontal
if params['vertical'] then vertical = 'vertical' end -- class for vertical display
local sizeStyling = ''
if params['width'] then sizeStyling = sizeStyling .. 'width:' .. params['width'] .. ';' end
if params['height'] then sizeStyling = sizeStyling .. 'height:' .. params['height'] .. ';' end
local noborder = ''
if not params['main-borders'] then noborder = 'no-border' end
-- the gallery contents
local content = gallery:tag('div'):addClass('main-content'):addClass(noborder)
:css('height',params['height'])
local i=0
while i<15 do
i=i+1
local caption = params['caption'..i] --or ''
local header = params['header'..i] --or ''
local footer = params['footer'..i] --or ''
-- for each gallery element containing a cladogram
if params['cladogram'..i] then
local cladogramTable = content:tag('table')
:addClass(shading):addClass('gallery-element'):addClass(vertical)
:css('width',params['width'])
:css('height',params['height'])
if caption then
cladogramTable:tag('caption'):wikitext(caption):addClass('caption')
end
if header then
local row = cladogramTable:tag('tr'):addClass('header')
row:tag('th'):wikitext(header)
end
-- if params['cladogram'..i] already checked
local row = cladogramTable:tag('tr')
row:tag('td'):wikitext(params['cladogram'..i])
:addClass('cladogram')
--:css("padding", "20px")
:css('width',params['cladogram-width'])
:css('height',params['cladogram-height'])
if footer then
local row = cladogramTable:tag('tr')
row:tag('td'):wikitext(footer):addClass('footer'):addClass(shading)
end
--[[DIV VERSION - different browsers behave differently
-- the div containing each element in the gallery
local div = content:tag('div'):addClass('gallery-element'):addClass(direction)
:css('width',params['width'])
:css('height',params['height'])
-- the caption, header, content and footer for each element
if caption then div:tag('div'):wikitext(caption):addClass('caption'):addClass(shading) end
if header then div:tag('div'):wikitext(header):addClass('header'):addClass(shading) end
div:tag('div'):wikitext(params['cladogram'..i]):addClass('cladogram'):addClass(shading)
:css('width',params['cladogram-width'])
:css('height',params['cladogram-height'])
if footer then div:tag('div'):wikitext(footer):addClass('footer'):addClass(shading) end
END DIV VERSION--]]
else
break
end
end
-- main footer
if params['main-footer'] then
gallery:tag('div'):wikitext(params['main-footer']):addClass('main-footer'):addClass(noborder)
end
-- TemplateStyles file
local src = "Template:Clade gallery/styles.css"
return p.templateStyle( frame, src ) .. tostring(gallery)
end
-- this must be at end
return p