Module:Graphical timeline
| This module is rated as ready for general use. It has reached a mature state, is considered relatively stable and bug-free, and may be used wherever appropriate. It can be mentioned on help pages and other Wikipedia resources as an option for new users. To minimise server load and avoid disruptive output, improvements should be developed through sandbox testing rather than repeated trial-and-error editing. |
| Editing of this module by new or unregistered users is currently disabled. See the protection policy and protection log for more details. If you cannot edit this module and you wish to make a change, you can submit an edit request, discuss changes on the talk page, request unprotection, log in, or create an account. |
| This module depends on the following other modules: |
| See {{Graphical timeline/testcases}} for test cases. |
The easy way
Type {{include timeline}} in your article, press preview, and follow the link generated. You'll be presented with a ready-to-go template; once this is finished, return to your article, and {{include timeline}} will display your timeline.
Development
As of May 2021, the template has been converted to Lua and is now implemented at Module:Graphical timeline. New features include:
- Unlimited numbered parameters — use note435, note34234532, whatever is convenient.
- HTML size, memory, and run-time are all proportional to the number of items, not the number of potential items.
- Incorrect HTML and fragile CSS now cleaned up.
- Annotation text now automatically aligned with arrows. (
|disable-arrow-align=trueto turn off.) - Box text now automatically centered. (
|disable-box-align=trueto turn off.)
Template function
This template provides an intuitive, user-friendly and flexible way to insert timelines into articles. It's designed to require the minimum number of variables, and to generate an HTML table.
Why use this template?
The alternative to this template is m:EasyTimeline, using the <timeline> syntax. EasyTimeline has the following weaknesses:
- Pixelated image produced, which looks different and increases page load time.
- Long set-up time — taking 30 minutes plus even when you know what you are doing.
- Impenetrable code requiring precise syntax.
- Difficult to place bars exactly where you want them.
- Changing minimum dates and sizes requires modification in many places.
- Everything must be specified — nothing is automatic.
- It is not scalable — it does not enlarge with text size.
Using the template
Where to use it
Whilst short timelines can be inserted directly into the article, some editors complain that long, complex timelines break up the flow of the page and make editing difficult. Therefore, you may want to use {{Include timeline}} to host the timeline code on a separate page, which will be automatically included.
Getting started
You can set the switch |help=on in the template to produce some quick pointers.
When you are getting started, you might want to use {{Graphical timeline|help=on}} to generate a ready-made, empty template – or type {{subst:Graphical timeline/blank}} into a sandbox page, save the page, and edit the resulting code. Hopefully, the parameter names are pretty self-explanatory.
What numbers mean
Numeric values are by default in units of em, that is, the height and width of a capital M.
The exception to this is the left and right parameters of a bar, which are set using fractional coordinates. That is to say, the code
|bar1-right=0.5 |bar2-left=0.666 |bar3-left=0.5 |bar3-right=0.666
...will produce bar1 covering the left half of the area, bar2 covering the right-most third, and bar3 in between them. Further, for operational reasons, the height-units are always used to generate border widths.
Bar borders
Borders appear only on the top and bottom of any given bar. Unfortunately, this cannot be changed – to have a border at the top or bottom only of a bar, you should create a separate bar to overlay the end.
Border style can be set to the CSS standards of solid, dotted, dashed, double, groove, ridge, inset or outset. Width is in the same units as height, and if none is specified 0.1 to 0.2 is a suitable hairline value.
Blank parameters
Leaving a parameter blank is NOT the same as not specifying it – it will override the template's default value. Be sure to remove any lines you do not specify.
Geological periods
To draw a geological period, use the syntax |period3=Cenozoic, with |period3-left=0.1 as usual. The template will then calculate the beginning, end and correct color of the bar. For an example, see Template:Cenozoic graphical timeline (backlinks edit)
Considerations
Browsers
Unfortunately, different browsers have different ways of dealing with lines of text that overflow their container – some stretch the container whilst others wrap the text. This means it's probably worth checking your finished timeline in at least IE and Firefox if you are making a particularly complex timeline.
Colors
If you are setting colors using html values that look like #e0b539, do consider that some older monitors, and many projectors, cannot display some colours. Sticking to Web-safe colors ensures maximum compatibility, which is often appreciated; i.e. multiples of 33, e.g. #ff99cc or #03C.
Easy editing
If you create a timeline on a template page, do use the | link-to= parameter. Specify the page name without Template: (e.g. My graphical timeline for Template:My graphical timeline), and "view", "talk", "edit" links will appear.
Parameter list
The list may be long, but do not be daunted – you only need to use a couple, and the rest give you incredible flexibility!
Replace any instance of # with a number.
To and from are mandatory, all other parameters are optional.
Either spelling of colour/color is always acceptable.
General parameters
General parameters | ||
−200 — – 0 — – 200 — – 400 — – 600 — – 800 — – 1000 — – 1200 — – 1400 — – 1600 — – 1800 — – | ||
| Parameter | Function | Default |
|---|---|---|
| from | The value of the bottom of the timeline. May be negative. | Required |
| to | The value of the top of the timeline. May be negative. | Required |
| instance-id | Differentiates this timeline from others that appear on the page | Sometimes required |
| title | Writes the title bar | No title |
| title-colour | Colours the title bar's background | No colour |
| plot-colour | Colours the background for the timeline space | No colour |
| unit | Defines the unit of (graphical) measurement | em
|
| height-unit width-unit |
Overrides either the height or width unit | <unit>
|
| height width |
Sizes the timeline container | 3610
|
| disable-arrow-align | If true (or equivalent), enables text in notes to be nudged away from arrow | false |
| disable-box-align | If true (or equivalent), enables text in bars to be nudged away from center | false |
| collapsible | If true (or equivalent), enables infobox to be collapsed | false |
| state | Use |state=expanded to force the box to the expanded state, or |state=collapsed to hide it.
|
expanded |
| embedded | If yes/true/1, the graphical timeline is embedded in an infobox. Borders etc. are removed. | false |
Bars
Bars are sizable coloured rectangles with a text label in the middle.
Bar parameters are prefixed with bar#, where # is the bar's ID. The number doesn't imply any particular order, but it does have to be unique.
−200 — – 0 — – 200 — – 400 — – 600 — – 800 — – 1000 — – 1200 — – 1400 — – 1600 — – 1800 — – | bar1 bar2 bar3 bar4 bar5 bar6 bar7 bar8 bar9 | |
| Parameter | Function | Default |
|---|---|---|
| -colour (-color) |
Sets the background colour | Required |
| -from | Positions the bottom edge of the bar along the timeline | Required |
| -to | Positions the top edge of the bar along the timeline | Required |
| -left -right |
Positions the left & right edges, as a fraction of the width (0.0 to 1.0)
|
0.01.0
|
| -border-width -border-colour -border-style |
Stylises the top & bottom borders, simultaneously; styles are solid dotted dashed double groove ridge inset outset. Border width has same units has height (em by default).
|
No border |
| -text | Writes the central label | No text |
| -font-size | Sizes the label | 90%
|
| -nudge-down -nudge-up -nudge-right -nudge-left |
Nudges the label around (Units are in ems). Only enabled if |disable-box-align=1
|
0.0
|
Geological periods
Gets data to create a bar with default values for -colour, -from, -to, -text.
Period parameters are prefixed with period#, where # is the period's unique number.
−520 — – −500 — – −480 — – −460 — – −440 — – −420 — – −400 — – −380 — – −360 — – −340 — – −320 — – −300 — – −280 — – −260 — | ||
| Parameter | Function | Default |
|---|---|---|
| null | Names the geological division (e.g. |period1=Phanerozoic)
|
Required |
| -colour (-color) |
Overrides the division's colour | {{Period color|<period>}}
|
| -text | Overrides the division's label | <period>
|
| -from -to |
Overrides the division's start & end times | -{{Period start|<period>}}-{{Period end|<period>}}
|
| -left -right |
Positions the left & right edges, as a fraction of the width (0.0 to 1.0)
|
0.01.0
|
| -border-width -border-colour -border-style |
Stylises the top & bottom borders, simultaneously; styles are solid dotted dashed double groove ridge inset outset
|
No border |
| -font-size | Sizes the central label | 90%
|
| -nudge-down -nudge-up -nudge-right -nudge-left |
Nudges the label around (Units are in ems). Only enabled if |disable-box-align=1
|
0.0
|
Legend
Legend entries coordinate with bars (i.e. legend1 matches the colour of bar1, etc.).
−400 — – −380 — – −360 — – −340 — – −320 — – −300 — – −280 — – −260 — | bar1 bar2 bar3 |
| ||||||
| ||||||||
caption | ||||||||
| Parameter | Function | Default |
|---|---|---|
| null (-text) | Writes the entry's label (e.g. |legend1=)
|
Required |
| -colour (-color) | Overrides the entry's colour | <bar#-colour>
|
Notes
Notes are annotations that show up to the right of the timeline with an arrow (←).
Note parameters are prefixed with note#, where # is the note's unique number.
| Parameter | Function | Default |
|---|---|---|
| null | Writes the note's content (e.g. note1=Cambrian explosion)
|
Required |
| -at | Positions the arrow & note text vertically along the timeline | Required |
| -remove-arrow (-no-arrow) |
Removes the note's arrow, if true | 0
|
| -nudge-down -nudge-up -nudge-right -nudge-left |
Nudges the note's text around (units are in ems) If arrow, then text is centered next to arrow unless |
0.0
|
| -size (-font-size) |
Sizes the note's font | 100%
|
| -colour (-color) |
Colours the note's font |
In use: an example
The code on the left produces the timeline on the right. For another example, please see Ediacaran biota.
Example Timeline | ||||||||||
−550 — – −540 — – −530 — – −520 — – −510 — – −500 — |
| |||||||||
| ||||||||||
{{Graphical timeline
|title=Example Timeline
|align=right
|plot-colour=#bbeebb
|from=-550
|to=-500
|scale-increment=10
|width=10
|height=250
|height-unit=px
|legend1=[[Phanerozoic]]
|bar1-from=-542
|bar1-left=0
|bar1-right=0.1
|legend2=[[Precambrian]]
|bar2-to=-542
|bar2-left=0
|bar2-right=0.2
|legend3=[[Infracambrian]]
|legend3-colour=#e0aacc
|bar3-to=-534
|bar3-left=0.1
|bar3-right=0.2
|bar3-colour=#e0aacc
|<!-- If this were bar 4, it would be overlapped by the Botomian bar -->
|bar14-from=-530
|bar14-to=-520
|bar14-left=0.95
|bar14-right=1.05
|bar14-colour=red
|legend14=Cambrian explosion
|<!--Epochs & stages-->
|bar5-text=[[Ediacaran]]
|bar5-to=-540
|bar5-left=0.2
|bar5-border-width=1
|bar5-border-colour= #996666
|bar5-colour=#cc9999
|bar6-text=[[Nemakit-Daldynian]]
|bar6-from=-542
|bar6-to=-534
|bar6-left=0.2
|bar6-border-width=0.1
|bar6-colour=#99cc99
|bar7-text=[[Tommotian]]
|bar7-from=-534
|bar7-to=-530
|bar7-left=0.1
|bar7-colour=#99cc99
|bar7-border-width=0.1
|bar8-from=-530.1
|bar8-to=-529.9
|bar8-left=0.2
|bar8-colour=black
|bar9-text=[[Atdabanian]]
|bar9-from=-530
|bar9-to=-524
|bar9-left=0.1
|bar9-right=0.95
|bar9-colour=#99cc99
|bar10-text=[[Botomian]]
|bar10-from=-524
|bar10-to=-518
|bar10-left=0.1
|bar10-colour=#99cc99
|bar10-border-width=0.1
|bar11-text=[[Toyonian]]
|bar11-from=-518
|bar11-to=-513
|bar11-left=0.1
|bar11-border-width=0.1
|bar11-colour=#99cc99
|note1=Base of the Cambrian
|note1-at=-542
|note1-colour=green
|note2=[[Middle Cambrian]]
|note2-nudge-left=8.5
|note2-nudge-down=0
|note2-at=-507
|note2-remove-arrow=yes
|caption=An approximate time-scale of events<br>at the base of the [[Cambrian]] [[Period (geology){{!}}Period]].
<br>Axis scale is in millions of years ago.
}}
Notes on the example
{{!}}must be used wherever you want a | to appear (e.g. the caption)- If you do not specify when a bar should start or end, it will continue to the edge of the plot
- Text should not be too long for the bar
- The way that overflowing text wraps is also handled differently in different browsers – it pokes out of the right of the bar on Firefox, but is wrapped within it by IE. Try to manually enter newlines when required rather than relying on browsers to sort it out. Or position a note over the bar – check out note2, which is nudged left over the plot background.
Blank template to copy
{{Graphical timeline
|<!--you MUST remove any lines you don't need-->
| help=off
| link-to={{subst:PAGENAMEE}}
<!--use that^ line if the timeline's on a
page of its own, so it can be edited easily-->
| embedded={{{embedded|}}}
<!--Leave if might be used in table-->
| from=REQUIRED
| to=REQUIRED
| scale-increment=
| align=
| margin=
| width=
| annotations-width=<!--defines space for notes right to the timelines width-->
| width-unit=
| height=
| height-unit=
| unit=<!--overridden by: width-unit or height-unit-->
| title=
| title-colour=
| plot-colour=
<!-- replace # with 1, 2, 3 etc.-->
| bar#-from=
| bar#-to=
| bar#-left=<!--0 is the far left of the timeline, 1 the far right.-->
| bar#-right=
| bar#-colour=
| bar#-border-width=<!--in the same units as height-->
| bar#-border-colour=
| bar#-text=
| bar#-nudge-left=
| bar#-nudge-right=<!--overrides nudge-left-->
| bar#-nudge-up=
| bar#-nudge-down=<!--overrides nudge-up-->
| legend#=
| legend#-colour=<!-- defaults to bar#-colour-->
| note#=
| note#-nudge-down=<!--overrides nudge-up-->
| note#-nudge-up=
| note#-nudge-left=
| note#-nudge-right=<!--overrides nudge-left-->
| note#-remove-arrow=yes
| note#-size=<!--Default value is 90%-->
| note#-colour=
| caption=
}}
local getArgs = require('Module:Arguments').getArgs
local compressSparseArray = require('Module:TableTools').compressSparseArray
local yesNo = require('Module:Yesno')
local p = {}
-- Note for translators of this module:
-- This module depends on [[:Template:period color]], [[:template:period start]], and [[:template:period end]].
-- Those templates must be implemented on the wiki. If the names are changed, they need to be changed here:
local periodColor = "period color"
local periodStart = "period start"
local periodEnd = "period end"
-- =================
-- UTILITY FUNCTIONS
-- =================
-- Default colors for first 28 bars/periods
local defaultColor = {"#6ca","#ff9","#6cf","#c96","#fcc","#9f9","#96c","#cc6","#ccc","#f66","#6c6","#99f","#c66","#f9c",
"#396","#ff3","#06c","#963","#c9c","#9c6","#c63","#c96","#999","#c03","#393","#939","#996","#f69"}
-- The default width of annotations (in em)
local defaultAW = 8
-- Previous version default width (in em)
local oldDefaultAW = 7
-- Function to turn blank arguments back into nil
-- Parameters:
-- s = a string argument
-- Returns
-- if s is empty, turn back into nil (considered false by Lua)
local function ignoreBlank(s)
if s == "" then
return nil
end
return s
end
-- Function to suppress incorrect CSS values
-- Parameters:
-- val = dimensional value
-- unit = unit of value
-- nonneg = [bool] value needs to be non-negative
-- formatstr = optional format string
-- Returns:
-- correct string for html, or nil if val is negative
local function checkDim(val, unit, nonneg, formatstr)
if not val then
return nil
end
val = tonumber(val)
if not val or (nonneg and val < 0) then
return nil
end
if formatstr then
return mw.ustring.format(formatstr,val)..unit
end
return val..unit
end
-- function to scan argument list for pattern
-- Parameters:
-- args = an argument dict that will be scanned for one or more patterns
-- patterns = a list of Lua string patters to scan for
-- other = a list of other argument specification lists
-- each element o corresponds to a new argument to produce in the results
-- o[1] = key in new argument list
-- o[2] = prefix of old argument
-- o[3] = suffix of old argument
-- Returns:
-- new argument list that matches patterns specified, with new key names
--
-- This function makes the Lua module scalable, by specifying a list of string patterns that
-- contain relevant arguments for a single graphical element, e.g., "period(%d+)". These
-- patterns should have exactly one capture that returns a number.
--
-- When such a pattern is detected, the number is extracted and then other arguments
-- with the same number is searched for. Thus, if "period57" is detected, other relevant
-- arguments like "period57-text" are searched for and, if non-empty, are copied to the
-- output list with a new argument key. Thus, there is {"text","period","-text"}, and
-- "period(%d+)" detects period57, the code will look for "period57-text" in the input
-- and copy it's value to "text" on the output.
--
-- This function thus pulls all relevant arguments for a single graphical item out, and
-- makes an argument list to call a function to produce a single element (such as a bar or note)
function p._scanArgs(args,patterns,other)
local result = {}
for _, p in pairs(patterns) do
for k, v in pairs(args) do
local m = tonumber(mw.ustring.match(k,p))
-- if there is a matching argument, and it's not blank
-- and we haven't handled that match yet, then find other
-- arguments and copy them into output arg list.
-- We have to handle blank arguments for backward compatibility with the template
-- we check for an existing output with item m to save time
if m and v ~= "" and not result[m] then
local singleResult = {}
for _, o in ipairs(other) do
local foundVal = args[(o[2] or "")..m..(o[3] or "")]
if foundVal then
singleResult[o[1]] = foundVal
end
end
-- A hack: for any argument number m, there is a magic list of default
-- colors. We copy that default color for m into the new argument list, in
-- case it's useful. After this, m is discarded
singleResult.defaultColor = defaultColor[m]
result[m] = singleResult
end
end
end
-- Squeeze out all skipped values. Thus, continguous argument numbers are not
-- required: the module can get called with bar3, bar17, bar59 and it will only produce
-- three bars, in numerical order that they were called (3, 17, 59)
return compressSparseArray(result)
end
-- Function to compute the numeric step in the timescale
-- Parameters:
-- p1, p2 = lower and upper bounds of timescale
-- Returns:
-- round step size that produces ~10 steps between p1 and p2
--
-- Implements [[Template:Calculate increment]], except with a slight tweak:
-- The round value (0.1, 0.2, 0.5, 1.0) is selected based on minimum log
-- distance, so the thresholds are slightly tweaked
function p._calculateIncrement(p1, p2)
local d = math.abs(p1-p2)
if d < 1e-10 then
return 1e-10
end
local logd = math.log10(d)
local n = math.floor(logd)
local frac = logd-n
local prevPower = math.pow(10,n-1)
if frac < 0.5*math.log10(2) then
return prevPower
elseif frac < 0.5 then
return 2*prevPower
elseif frac < 0.5*math.log10(50) then
return 5*prevPower
else
return 10*prevPower
end
end
-- Signed power function for squashing timeline to be more readable
function p._signedPow(x,p)
if x < 0 then
return -math.pow(-x,p)
end
return math.pow(x,p)
end
-- Function to convert from time to location in HTML
-- Arguments:
-- t = time
-- from = earliest time in timeline
-- to = latest time in timeline
-- height = height of timeline (in some units)
-- scaling = method of scaling ('linear' or 'sqrt' or 'pow')
-- power = power law of scaling (if scaling='pow')
function p._scaleTime(t, from, to, height, scaling, power)
if scaling == 'pow' then
from = p._signedPow(from,power)
to = p._signedPow(to,power)
t = p._signedPow(t,power)
end
return height*(to-t)/(to-from)
end
-- Utility function: is this a recognized unit?
local function recognizedUnit(unit)
return unit == "px" or unit == "em"
end
-- Utility function to convert between units
-- Currently only supports em and px,
-- assume M size of 13.3 pixels
local function convertUnits(value,srcUnit,dstUnit)
if srcUnit == dstUnit then --- if src and dst units match, then return value
return value
end
value = tonumber(value)
if value == nil then
return nil
end
if srcUnit == "px" and dstUnit == "em" then
return value/13.3
end
if srcUnit == "em" and dstUnit == "px" then
return value*13.3
end
return nil --- TODO: handle more cases?
end
-- Utility function to create HTML container for entire graphical timeline
-- Parameters:
-- container = HTML container for title
-- args = arguments passed to main
-- args["instance-id"] = unique string per Graphical timeline per page
-- args.embedded = is timeline embedded in another infobox?
-- args.align = float of timeline (default=right)
-- args.margin = uniform margin around timeline
-- args.bodyclass = CSS class for whole container
-- args.collapsible = make timeline collapsible
-- args.state = set collapse state
-- Returns;
-- html div object that is root of DOM for graphical timeline
--
-- CSS taken from previous version of [[Template:Grpahical timeline]]
--
-- #TODO: Convert table into <figure> parent element with aria-labelledby
-- pointing to <figcaption> i.e. the #title. Then the rest should be divs.
local function createContainer(args)
args.align = args.align or "right"
local container = mw.html.create('table')
container:attr("id","Container"..(args["instance-id"] or ""))
container:attr("role","figure")
container:attr("aria-labelledby","Title")
container:addClass(args.bodyclass)
container:addClass("toccolours")
container:addClass("searchaux")
if not args.embedded then
if args.state == "collapsed" then
args.collapsible = true
container:addClass("mw-collapsed")
container:addClass("nomobile")
elseif args.state == "autocollapse" then
args.collapsible = true
container:addClass("autocollapse")
container:addClass("nomobile")
end
if args.collapsible then
container:addClass("mw-collapsible")
end
end
container:css("text-align","left")
container:css("padding","0 0.5em")
container:css("border-style",args.embedded and "none" or "solid")
if args.embedded then
container:css("margin","auto")
else
container:css("float",args.align)
if args.align == "right" or args.align == "left" then
container:css("clear",args.align)
end
local margins = {}
margins[1] = args.margin or "0.3em"
margins[2] = (args.align == "right" and 0) or args.margin or "1.4em"
margins[3] = args.margin or "0.8em"
margins[4] = (args.align == "left" and 0) or args.margin or "1.4em"
container:css("margin",table.concat(margins," "))
end
container:css("overflow","hidden")
return container
end
-- Utility function to create title for graphical timeline
-- Parameters:
-- args = arguments passed to main
-- args["instance-id"] = unique string per Graphical timeline per page
-- args["title-color"] = background color for title
-- args.title = title of timeline
-- Returns;
-- html div object that is the title
--
-- CSS taken from previous version of [[Template:Grpahical timeline]]
local function createTitle(container,args)
container:attr("id","Title"..(args["instance-id"] or ""))
local bottomPadding = args["link-to"] and (not args.embedded)
and (not args.collapsible) and "0" or "1em"
container:css("padding","1em 1em "..bottomPadding.." 1em")
local title = container:tag('div')
title:css("background-color",ignoreBlank(args["title-colour"] or args["title-color"] or "#77bb77"))
title:css("color","inherit")
title:css("padding","0 0.2em 0 0.2em")
title:css("font-weight","bold")
title:css("text-align","center")
title:wikitext(args.title)
end
-- Utility function to create optional navbox header for timeline
-- Parameters:
-- container = container for navbox header
-- args = arguments passed to main
-- args.title = title of timeline
-- args["link-to"] = name of parent template (without namespace)
-- Returns;
-- html div object that is the navbox header
--
-- CSS taken from previous version of [[Template:Grpahical timeline]]
local function navboxHeader(container,args)
local frame = mw.getCurrentFrame()
container:attr("id","Navbox"..(args["instance-id"] or ""))
local topMargin = args.title and "0" or "0.2em"
container:css("padding","0")
container:css("margin",topMargin.." 1em 0 0")
container:css("text-align","right")
container:wikitext(frame:expandTemplate{title="Navbar",args={"Template:"..args["link-to"]}})
end
-- ==================
-- TIME AXIS AND BARS
-- ==================
--Function to create HTML time axis on left side of timeline
--Arguments:
-- container = HTML parent object
-- args = arguments passed to main
-- args.from = beginning (earliest) time of timeline
-- args.to = ending (latest) time of timeline
-- args.height = height of timeline
-- args["height-unit"] = unit of height
-- args["instance-id"] = unique string per Graphical timeline per page
-- args["scale-increment"] = gap between time ticks (default=automatically computed)
-- args.scaling = method of scaling (linear or sqrt, linear by default)
-- args["label-freq"] = frequency of labels (per major tick)
-- Returns;
-- html div object for the time axis
--
-- CSS taken from previous version of [[Template:Grpahical timeline]]
function p._scalemarkers(container,args)
container:attr("id","Scale"..(args["instance-id"] or ""))
container:css("width","4.2em")
args.computedWidth = args.computedWidth+4.2
container:css("position","relative")
container:css("float","left")
container:css("font-size","100%")
container:css("height",checkDim(args.height,args["height-unit"],true))
container:css("border-right","1px solid #242020")
local incr = args["scale-increment"] or p._calculateIncrement(args.from,args.to)
-- step through by half the desired increment, alternating small and large ticks
-- put labels every args["label-freq"] large ticks
local labelFreq = args["label-freq"] or 1
labelFreq = labelFreq*2 -- account for minor ticks
local halfIncr = incr/2
local lo_time = args.from < args.to and args.from or args.to
local hi_time = args.from > args.to and args.from or args.to
local tIndex = math.ceil(lo_time/incr)*2 -- always start on a label
local toIndex = math.floor(hi_time/halfIncr)
local tickCount = 0
while tIndex <= toIndex do
local t = tIndex*halfIncr
local div = container:tag("div")
div:css("float","right")
div:css("position","absolute")
div:css("right","-1px")
div:css("top",checkDim(p._scaleTime(t,args.from,args.to,args.height,args.scaling,args.power),
args["height-unit"],nil,"%.2f"))
div:css("transform","translateY(-50%)")
local span = div:tag("span")
span:css("font-size","90%")
local text = ""
if tickCount%labelFreq == 0 then
if t < 0 then
text = mw.ustring.format("−%g ",-t)
else
text = mw.ustring.format("%g ",t)
end
end
if tickCount%2 == 0 then
text = text.."—"
else
text = text.."–"
end
span:wikitext(text)
tIndex = tIndex + 1
tickCount = tickCount + 1
end
end
-- Function to create timeline container div
-- Arguments:
-- container = HTML parent object
-- args = arguments passed to main
-- args["plot-colour"] = background color for timeline
-- args["instance-id"] = unique string per graphical timeline per page
-- args.height = height of timeline
-- args.width = width of timeline
-- args["height-unit"] = unit of height measurement
-- args["width-unit"] = unit of width measurement
-- Returns:
-- timeline HTML object created
local function createTimeline(container,args)
local color = ignoreBlank(args["plot-colour"] or args["plot-color"])
container:attr("id","Timeline"..(args["instance-id"] or ""))
container:addClass("toccolours")
container:css("position","relative")
container:css("font-size","100%")
container:css("width","100%")
container:css("height",checkDim(args.height,args["height-unit"],true))
container:css("padding","0px")
container:css("float","left")
container:css("width",checkDim(args.width,args["width-unit"],true))
args.timelineWidth = convertUnits(args.width,args["width-unit"],"em") or 10
args.computedWidth = args.computedWidth+args.timelineWidth
container:css("border","none")
container:css("background-color",color)
container:css("color","inherit")
container:addClass("notheme")
return container
end
-- Function to draw single bar (or box)
-- Arguments:
-- container = parent HTML object for bar
-- args = arguments for this box
-- args.text = text to display
-- args.nudgedown = distance to nudge text down (in em)
-- args.nudgeup = distance to nudge text up (in em)
-- args.nudgeright = distance to nudge text right (in em)
-- args.nudgeleft = distance to nudge text left (in em)
-- args.colour = color of bar (default to color assigned to bar number)
-- args.left = fraction of timeline width for left edge of bar (default 0)
-- args.right = fraction of timeline width for right edge of bar (default 1)
-- args.to = beginning (bottom) of bar, in time units (default timeline begin)
-- args.from = end (top) of bar, in time units (default timeline end)
-- args.height = timeline height
-- args.width = timeline width
-- args["height-unit"] = units of timeline height
-- args["width-unit"] = units of timeline width
-- args.border-style = CSS style for top/bottom of border (default "solid" if args.border)
function p._singleBar(container,args)
args.text = args.text or " "
args.nudgedown = (tonumber(args.nudgedown) or 0) - (tonumber(args.nudgeup) or 0)
args.nudgeright = (tonumber(args.nudgeright) or 0) - (tonumber(args.nudgeleft) or 0)
args.colour = args.colour or args.defaultColor
args.left = tonumber(args.left) or 0
args.right = tonumber(args.right) or 1
args.to = tonumber(args.to) or args["tl-to"]
args.from = tonumber(args.from) or args["tl-from"]
args.border = tonumber(args.border)
args["border-style"] = args["border-style"] or ((args.border or args["border-colour"]) and "solid") or "none"
-- the HTML element for the box/bar itself
local bar = container:tag('div')
bar:css("font-size","100%")
bar:css("background-color",ignoreBlank(args.colour or "#aaccff"))
bar:css("color","inherit")
bar:css("border-width",checkDim(args.border,args["height-unit"],true))
bar:css("border-color",ignoreBlank(args["border-colour"]))
bar:css("border-style",args["border-style"].." none")
bar:css("position","absolute")
bar:css("text-align","center")
bar:css("margin","0")
bar:css("padding","0")
bar:css("pointer-events", "none")
bar:addClass("notheme")
local bar_top = p._scaleTime(args.to,args["tl-from"],args["tl-to"],args.height,args.scaling,args.power)
local bar_bottom = p._scaleTime(args.from,args["tl-from"],args["tl-to"],args.height,args.scaling,args.power)
local bar_height = bar_bottom-bar_top
bar:css("top",checkDim(bar_top,args["height-unit"],nil,"%.3f"))
if args["border-style"] ~= "none" and args.border then
bar_height = bar_height-2*args.border
end
bar:css("height",checkDim(bar_height,args["height-unit"],true,"%.3f"))
bar:css("left",checkDim(args.left*args.width,args["width-unit"],nil,"%.3f"))
bar:css("width",checkDim((args.right-args.left)*args.width,args["width-unit"],true,"%.3f"))
-- within the bar, use a div to nudge text away from center
local textParent = bar
if not args.alignBoxText then
local nudge = bar:tag('div')
nudge:css("font-size","100%")
nudge:css("position","relative")
nudge:css("top",checkDim(args.nudgedown,"em",nil))
nudge:css("left",checkDim(args.nudgeright,"em",nil))
nudge:css("pointer-events", "none")
textParent = nudge
end
-- put text div as child of nudge div (if exists)
local text = textParent:tag('div')
text:css("position","relative")
text:css("text-align","center")
text:css("font-size",ignoreBlank(args.textsize))
text:css("vertical-align","middle")
text:addClass("notheme")
local text_bottom = -0.5*bar_height
text:css("display","block")
text:css("bottom",checkDim(text_bottom,args["height-unit"],nil,"%.3f"))
text:css("transform","translateY(-50%)")
text:css("z-index","5")
text:css("pointer-events", "initial")
text:wikitext(ignoreBlank(args.text))
end
-- Function to render all bars/boxes in timeline
-- Arguments:
-- container = parent HTML object
-- args = arguments to main function
--
-- Global (main) arguments are parsed, individual box arguments are picked out
-- and passed to p._singleBar() above
--
-- The function looks for bar*-left, bar*-right, bar*-from, or bar*-to,
-- where * is a string of digits. That string of digits is then used to
-- find corresponding parameters of the individual bar.
-- For example, if bar23-left is found, then bar23-colour turns into local colour,
-- bar23-left turns into local left, bar23-from turns into local from, etc.
function p._bars(container,args)
local barArgs = p._scanArgs(args,{"^bar(%d+)-left$","^bar(%d+)-right$","^bar(%d+)-from","^bar(%d+)-to"},
{{"text","bar","-text"},
{"textsize","bar","-font-size"},
{"nudgedown","bar","-nudge-down"},
{"nudgeup","bar","-nudge-up"},
{"nudgeright","bar","-nudge-right"},
{"nudgeleft","bar","-nudge-left"},
{"colour","bar","-colour"},
{"colour","bar","-color"},
{"border","bar","-border-width"},
{"border-colour","bar","-border-colour"},
{"border-colour","bar","-border-color"},
{"border-style","bar","-border-style"},
{"left","bar","-left"},
{"right","bar","-right"},
{"from","bar","-from"},
{"to","bar","-to"}})
-- The individual bar arguments are placed into the barArgs table
-- Iterating through barArgs picks out the
for _, barg in ipairs(barArgs) do
-- barg is a table with the local arguments for one bar.
-- barg needs to have some global arguments copied into it:
barg["tl-from"] = args.from
barg["tl-to"] = args.to
barg.height = args.height
barg.width = args.width
barg["height-unit"] = args["height-unit"]
barg["width-unit"] = args["width-unit"]
barg.unit = args.unit
barg.scaling = args.scaling
barg.power = args.power
barg.alignBoxText = not args["disable-box-align"]
-- call _singleBar with the local arguments for one bar
p._singleBar(container,barg)
end
end
-- Function to draw a bar corresponding to a geological period
-- Arguments:
-- container = parent HTML object
-- args = global arguments passed to main
--
-- This function is just like _bars(), above, except with defaults for periods:
-- a period bar is triggered by period* (* = string of digits)
-- all other parameters start with "period", not "bar"
-- colour, from, and to parameters default to data from named period
-- text is a wikilink to period article
function p._periods(container,args)
local frame = mw.getCurrentFrame()
local periodArgs = p._scanArgs(args,{"^period(%d+)$"},
{{"text","period","-text"},
{"textsize","period","-font-size"},
{"period","period"},
{"nudgedown","period","-nudge-down"},
{"nudgeup","period","-nudge-up"},
{"nudgeright","period","-nudge-right"},
{"nudgeleft","period","-nudge-left"},
{"colour","period","-colour"},
{"colour","period","-color"},
{"border-width","period","-border-width"},
{"border-colour","period","-border-colour"},
{"border-colour","period","-border-color"},
{"border-style","period","-border-style"},
{"left","period","-left"},
{"right","period","-right"},
{"from","period","-from"},
{"to","period","-to"}})
-- Iterate through period* arguments, translating much like bar* arguments
-- Supply period defaults to local arguments, also
for _, parg in ipairs(periodArgs) do
parg.text = parg.text or ("[["..parg.period.."]]")
parg.textsize = "90%"
parg.colour = parg.colour or frame:expandTemplate{title=periodColor,args={parg.period}}
parg.from = parg.from or tonumber("-"..frame:expandTemplate{title=periodStart,args={parg.period}})
parg.to = parg.to or tonumber("-"..frame:expandTemplate{title=periodEnd,args={parg.period}})
-- bound local time to lie within entire timeline
local from_time = tonumber(args.from) or 0
local to_time = tonumber(args.to) or 0
-- figure out bounds on entire timeline
local lo_time = from_time < to_time and from_time or to_time
local hi_time = from_time > to_time and from_time or to_time
-- now figure out bounds on period
from_time = tonumber(parg.from) or 0
to_time = tonumber(parg.to) or 0
-- clip period to timeline
from_time = from_time < lo_time and lo_time or from_time > hi_time and hi_time or from_time
to_time = to_time < lo_time and lo_time or to_time > hi_time and hi_time or to_time
parg.from = from_time
parg.to = to_time
parg["tl-from"] = args.from
parg["tl-to"] = args.to
parg.height = args.height
parg.width = args.width
parg["height-unit"] = args["height-unit"]
parg["width-unit"] = args["width-unit"]
parg.unit = args.unit
parg.scaling = args.scaling
parg.power = args.power
parg.alignBoxText = not args["disable-box-align"]
p._singleBar(container,parg)
end
end
-- ===========
-- ANNOTATIONS
-- ===========
-- Function to render a single note (annotation)
-- Arguments:
-- container = parent HTML object
-- args = arguments for this single note
-- args.text = text to display in note
-- args.noarr = bool, true if no arrow should be used
-- args.height = height of timeline
-- args.unit = height units
-- args.at = position of annotation (in time units)
-- args.colour = color of text in note
-- args.textsize = size of text (default 90%)
-- args.nudgeright = nudge text (and arrow) to right (in em)
-- args.nudgeleft = nudge text (and arrow) to left (in em)
-- Following parameters are only applicable to "no arrow" case or when
-- args.alignArrow is false:
-- args.nudgedown = nudge text down (in em)
-- args.nudgeup = nudge text up (in em)
-- args.aw = annotation width (in em)
function p._singleNote(container,args)
-- Ensure some parameters default to sensible values
args.at = tonumber(args.at) or 0.5*(args.to+args.from)
args.colour = args.colour or "var( --color-base, #000)"
args.aw = tonumber(args.aw)
-- if string is centering, use old width to not break it
or mw.ustring.find(args.text,"center",1,true) and oldDefaultAW
or defaultAW
args.textsize = args.textsize or "90%"
-- Convert 4 nudge arguments to 2 numeric signed nudge dimensions (right, down)
args.nudgeright = (tonumber(args.nudgeright) or 0)-(tonumber(args.nudgeleft) or 0)
args.nudgedown = (tonumber(args.nudgedown) or 0)-(tonumber(args.nudgeup) or 0)
-- Container should have no pointer events, only the text should.
-- This prevents issues with containers overlapping and blocking pointer events on links.
container:css("pointer-events", "none")
-- Two cases: no arrow, and arrow
-- For no arrow case, use previous CSS which works well to position text
if args.noarr then
-- First, place a bar that pushes annotation down to right spot
local bar = container:tag('div')
bar:addClass("annot-bar")
bar:css("width","auto")
bar:css("font-size","100%")
bar:css("position","absolute")
bar:css("text-align","center")
bar:css("pointer-events", "none")
bar:css("margin-top",checkDim(p._scaleTime(args.at,args.from,args.to,args.height,args.scaling,args.power),
args.unit,nil,"%.3f"))
-- Now, nudge the text per nudge dimensions
local nudge = bar:tag('div')
nudge:addClass("annot-nudge")
nudge:css("font-size","100%")
nudge:css("float","left")
nudge:css("position","relative")
nudge:css("text-align","left")
nudge:css("pointer-events", "none")
nudge:css("top",checkDim(args.nudgedown-0.75,"em",nil))
nudge:css("left",checkDim(args.nudgeright,"em",nil))
nudge:css("width",checkDim(args.aw,"em",true))
-- Finally, place a dev for the text
local text = nudge:tag('div')
text:css("position","relative")
text:css("width","auto")
text:css("z-index","10")
text:css("font-size",ignoreBlank(args.textsize))
text:css("color",ignoreBlank(args.colour))
text:css("vertical-align","middle")
text:css("line-height","105%")
text:css("bottom","0")
-- Ensure that the text can be interacted with:
text:css("pointer-events","initial")
text:wikitext(ignoreBlank(args.text))
else
-- In the arrow case, previous code didn't correctly line up the text
-- Now that we're in Lua, it's easy to use a table to hold the arrow against the text
-- One row: first td is arrow, second td is text
-- Table gets placed directly using top CSS and absolute position
local tbl = container:tag('table')
-- Removes semantic meaning from table element. Equivalent of div now.
tbl:attr("role","presentation")
-- choose a reasonable height for table, then position middle of that height in the timeline
tbl:css("position","absolute")
tbl:css("z-index","15")
local at_location = p._scaleTime(args.at,args.from,args.to,args.height,args.scaling,args.power)
tbl:css("top",checkDim(at_location,args.unit,nil,"%.3f"))
tbl:css("left",checkDim(args.nudgeright,"em",nil))
tbl:css("transform","translateY(-50%)")
tbl:css("padding","0")
tbl:css("margin","0")
tbl:css("font-size","100%")
local row = tbl:tag('tr')
local arrowCell = row:tag('td')
arrowCell:css("padding","0")
arrowCell:css("text-align","left")
arrowCell:css("vertical-align","middle")
local arrowSpan = arrowCell:tag('span')
arrowSpan:css("color",args.colour)
arrowSpan:wikitext("←") --- HTML for left-pointing arrow
local textCell = row:tag('td')
textCell:css("padding","0")
textCell:css("text-align","left")
textCell:css("vertical-align","middle")
local textParent = textCell
-- If disable-arrow-align is true, nudge the text per nudge dimensions:
if not args.alignArrow then
local nudge = textCell:tag('div')
nudge:addClass("annot-nudge")
nudge:css("font-size","100%")
nudge:css("float","left")
nudge:css("position","relative")
nudge:css("top",checkDim(args.nudgedown,"em",nil))
textParent = nudge
end
local text = textParent:tag('div')
text:css("z-index","10")
text:css("font-size",ignoreBlank(args.textsize))
text:css("color",ignoreBlank(args.colour))
text:css("display","block")
text:css("line-height","105%") --- don't crunch multiple lines of text
text:css("bottom","0")
-- Ensure that the text can be interacted with:
text:css("pointer-events","initial")
text:wikitext(ignoreBlank(args.text))
end
end
-- Function to render all annotations in timeline
-- Arguments:
-- container = parent HTML object
-- args = arguments to main function
--
-- Global (main) arguments are parsed, individual box arguments are picked out
-- and passed to p._singleNote() above
--
-- The function looks for note*, where * is a string of digits
-- That string of digits is then used to find corresponding parameters of the individual note.
-- For example, if note23 is found, then note23-colour turns into local colour,
-- note-at turns into local at, note-texdt turns into local text, etc.
--
-- args["annotation-width"] overrides automatically determined width of annotation div
function p._annotations(container,args)
local noteArgs = p._scanArgs(args,{"^note(%d+)$"},
{{"text","note"},
{"noarr","note","-remove-arrow"},
{"noarr","note","-no-arrow"},
{"textsize","note","-size"},
{"textsize","note","-font-size"},
{"nudgedown","note","-nudge-down"},
{"nudgeup","note","-nudge-up"},
{"nudgeright","note","-nudge-right"},
{"nudgeleft","note","-nudge-left"},
{"colour","note","-colour"},
{"colour","note","-color"},
{"at","note","-at"}})
if #noteArgs == 0 then
return
end
-- a div to hold all of the notes
local notes= container:tag('td')
notes:attr("id","Annotations"..(args["instance-id"] or ""))
notes:css("padding","0")
notes:css("margin","0.7em 0 0.7em 0")
notes:css("float","left")
notes:css("position","relative")
-- Is there a "real" note? If so, leave room for it
-- real is: is non-empty and (has arrow or isn't nudged left)
local realNote = false
for _, narg in ipairs(noteArgs) do
local left = (tonumber(narg.nudgeleft) or 0)-(tonumber(narg.nudgeright) or 0)
if narg.text ~= "" and (not narg.noarr or left <= 0) then
realNote = true
args.hasRealNote = true -- record realNote boolean in args for further use
break
end
end
-- width of notes holder depends on whethere there are any "real" notes
-- width can be overriden
local aw = tonumber(args["annotations-width"]) or (realNote and defaultAW) or 0
aw = aw+0.22*args.timelineWidth
notes:css("width",checkDim(aw,"em",true,"%.3f"))
args.computedWidth = args.computedWidth+aw
notes:css("height",checkDim(args.height,args["height-unit"],true))
for _, narg in ipairs(noteArgs) do
--- copy required global parameters to local note args
narg.from = args.from
narg.to = args.to
narg.height = args.height
narg.unit = args["height-unit"]
narg.aw = args["annotations-width"]
narg.alignArrow = not args["disable-arrow-align"]
narg.scaling = args.scaling
narg.power = args.power
p._singleNote(notes,narg)
end
end
-- ====================
-- LEGENDS AND CAPTIONS
-- ====================
-- Function to render a single legend (below the timeline)
-- Arguments:
-- container = parent HTML object
-- args = argument table for this legend
-- args.colour = color to show in square
-- args.text = text that describes color
function p._singleLegend(container,args)
if not args.text then -- if no text, not a sensible legend
return
end
args.colour = args.colour or args.defaultColor or "transparent"
local row = container:tag('tr')
local squareCell = row:tag('td')
squareCell:css("padding",0)
local square = squareCell:tag('span')
square:css("background",ignoreBlank(args.colour))
square:css("color","inherit")
square:css("padding","0em .1em")
square:css("border","solid 1px #242020")
square:css("height","1.5em")
square:css("width","1.5em")
square:css("margin",".25em .9em .25em .25em")
square:wikitext(" ")
local textCell = row:tag('td')
textCell:css("padding",0)
local text = textCell:tag('div')
text:wikitext(args.text)
end
function p._legends(container,args)
local legendArgs = p._scanArgs(args,{"^legend(%d+)$"},
{{"text","legend"},
{"colour","bar","-colour"},
{"colour","bar","-color"},
{"colour","legend","-colour"},
{"colour","legend","-color"}
})
if #legendArgs == 0 then
return
end
local legendRow = container:tag('tr')
local legendCell = container:tag('td')
legendCell:attr("id","Legend"..(args["instance-id"] or ""))
legendCell:attr("colspan",3)
legendCell:css("padding","0 0.2em 0.7em 1em")
local legend = legendCell:tag('table')
legend:attr("id","Legend"..(args["instance-id"] or ""))
legend:attr("role","presentation")
legend:addClass("toccolours")
legend:css("margin-left","3.1em")
legend:css("border-style","none")
legend:css("float","left")
legend:css("clear","both")
for _,larg in ipairs(legendArgs) do
p._singleLegend(legend,larg)
end
end
local helpString = [=[
----
'''Usage instructions'''
----
Copy the text below, adding multiple bars, legends and notes as required.
<br>Comments, enclosed in <code><!-</code><code>- -</code><code>-></code>, should be removed.
Remember:
* You must use <code>{</code><code>{!}</code><code>}</code> wherever you want a {{!}} to be
: rendered in the timeline
* Large borders will displace bars in many browsers
* Text should not be wider than its containing bar,
: as this may cause compatibility issues
* Units default to [[em (typography){{!}}em]], the height and width of an 'M'.
See {{tl|Graphical timeline}} for full documentation.
{{Graphical timeline/blank}}}}]=]
local function createCaption(container,args)
local captionRow = container:tag("tr")
local captionCell = captionRow:tag("td")
captionCell:attr("id","Caption"..(args["instance-id"] or ""))
captionCell:attr("colspan",3)
captionCell:css("padding","0")
captionCell:css("margin","0 0.2em 0.7em 0.2em")
local caption = captionCell:tag("div")
caption:attr("id","Caption"..(args["instance-id"] or ""))
caption:addClass("toccolours")
if args.embedded then
caption:css("margin","0 auto")
caption:css("float","left")
else
caption:css("margin","0 0.5em")
end
caption:css("border-style","none")
caption:css("clear","both")
caption:css("text-align","center")
local widthUnit = args["width-unit"]
local aw = tonumber(args["annotations-width"]) or (args.hasRealNote and defaultAW) or -0.25
aw = aw+5+args.timelineWidth
if aw > args.computedWidth then
args.computedWidth = aw
end
caption:css("width",checkDim(aw,"em",true,"%.3f"))
caption:wikitext((args.caption or "")..((args.help and args.help ~= "off" and helpString) or ""))
end
function p._main(args)
-- For backward compatibility with template, all empty arguments are accepted.
-- But, for some parameters, empty will cause a Lua error, so for those, we convert
-- empty to nil.
for _, attr in pairs({"title","link-to","embedded","align","margin",
"height","width","unit","height-unit","width-unit","scale-increment",
"annotations-width","disable-arrow-align","disable-box-align","from","to"}) do
args[attr] = ignoreBlank(args[attr])
end
-- parse yes/no/true/false in embedded argument
args.embedded = yesNo(args.embedded,false)
-- Check that to > from, and that they're both defined
args.from = tonumber(args.from) or 0
args.to = tonumber(args.to) or 0
if args.scaling == 'sqrt' then
args.scaling = 'pow'
args.power = 0.5
end
if args.scaling == 'pow' then
args.power = args.power or 0.5
end
args.computedWidth = 1.7
-- Determine width/height values and units, with defaults
args["height-unit"] = args.unit or args["height-unit"] or "em"
args["width-unit"] = args.unit or args["width-unit"] or "em"
args.height = args.height or convertUnits(36,"em",args["height-unit"])
args.width = args.width or convertUnits(10,"em",args["width-unit"])
if not args.height and not recognizedUnit(args["height-unit"]) then
args.height = 36
args["height-unit"] = "em"
end
if not args.width and not recognizedUnit(args["width-unit"]) then
args.width = 10
args["width-unit"] = "em"
end
-- Create container table
local container = createContainer(args)
-- TITLE
if args.title and not args.embedded then
local titleRow = container:tag('tr')
local titleCell = titleRow:tag('td')
titleCell:attr("colspan",3)
createTitle(titleCell,args)
end
-- NAVBOX HEADER
if args["link-to"] and not args.embedded then
local navboxRow = container:tag('tr')
local navboxCell = navboxRow:tag('td')
navboxCell:attr("colspan",3)
navboxHeader(navboxCell,args)
end
local centralRow = container:tag('tr')
centralRow:css("vertical-align","top")
-- SCALEBAR
local scaleCell = centralRow:tag('td')
scaleCell:css("padding","0")
scaleCell:css("margin","0.7em 0 0.7em 0")
p._scalemarkers(scaleCell,args)
-- TIMELINE
local timelineCell = centralRow:tag('td')
timelineCell:css("padding","0")
timelineCell:css("margin","0.7em 0 0.7em 0")
local timeline = createTimeline(timelineCell,args)
-- PERIODS
p._periods(timeline,args)
-- BARS
p._bars(timeline,args)
-- ANNOTATIONS
p._annotations(centralRow,args)
-- LEGEND
p._legends(container,args)
-- CAPTION
createCaption(container,args)
container:css("min-width",checkDim(args.computedWidth,"em",true,"%.3f"))
return container
end
function p.main(frame)
local args = getArgs(frame,{frameOnly=false,parentOnly=false,parentFirst=true,removeBlanks=false})
return tostring(p._main(args):allDone())
end
return p