Mô đun:Graphical timeline

local getArgs = require('Mô đun:Arguments').getArgs
local compressSparseArray = require('Mô đun:TableTools').compressSparseArray
local p = {}

--
-- 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 tonumber(args.to) then
parg.to = args.to
end
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.height = tonumber(args.height) or 36
args.at = tonumber(args.at) or 0.5(args.to+args.from)
args.colour = args.colour or "black"
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)
-- 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("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("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")
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')
tbl:attr("role","presentation") -- warn screen readers this table is for layout only
-- 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("&8592;") --- 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")
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 Comments, enclosed in - -->, should be removed.

Remember:
You must use {{!}} 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 for full documentation.

}}]=]

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"] or args.unit or "em"
local aw = tonumber(args["annotations-width"]) or (args.hasRealNote and defaultAW) or -0.25
aw = aw+5+((widthUnit == "em" and tonumber(args.width)) or 10)
if aw > args.computedWidth then
args.computedWidth = aw
end
caption:css("width",checkDim(aw,"em",true))
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
-- Check that to > from, and that they're both defined
local from = tonumber(args.from) or 0
local to = tonumber(args.to) or 0
if from > to then
args.from = to
args.to = from
else
args.from = from
args.to = to
end
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
-- 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"))
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