Module:Graphical timeline
Appearance
From Wikipedia, the free encyclopedia
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.
Page semi-protected 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.
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 tests.
Usage
{{#invoke:Graphical timeline|main}}: generates a graphical timeline.
See {{Graphical timeline/doc }} for parameters
The above documentation is transcluded from Module:Graphical timeline/doc. (edit | history)
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Subpages of this module.
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Subpages of this module.
localgetArgs=require('Module:Arguments').getArgs localcompressSparseArray=require('Module:TableTools').compressSparseArray localyesNo=require('Module:Yesno') localp={} -- 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: localperiodColor="period color" localperiodStart="period start" localperiodEnd="period end" -- ================= -- UTILITY FUNCTIONS -- ================= -- Default colors for first 28 bars/periods localdefaultColor={"#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) localdefaultAW=8 -- Previous version default width (in em) localoldDefaultAW=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) localfunctionignoreBlank(s) ifs==""then returnnil end returns 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 localfunctioncheckDim(val,unit,nonneg,formatstr) ifnotvalthen returnnil end val=tonumber(val) ifnotvalor(nonnegandval<0)then returnnil end ifformatstrthen returnmw.ustring.format(formatstr,val)..unit end returnval..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) functionp._scanArgs(args,patterns,other) localresult={} for_,pinpairs(patterns)do fork,vinpairs(args)do localm=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 ifmandv~=""andnotresult[m]then localsingleResult={} for_,oinipairs(other)do localfoundVal=args[(o[2]or"")..m..(o[3]or"")] iffoundValthen 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) returncompressSparseArray(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 functionp._calculateIncrement(p1,p2) locald=math.abs(p1-p2) ifd<1e-10then return1e-10 end locallogd=math.log10(d) localn=math.floor(logd) localfrac=logd-n localprevPower=math.pow(10,n-1) iffrac<0.5*math.log10(2)then returnprevPower elseiffrac<0.5then return2*prevPower elseiffrac<0.5*math.log10(50)then return5*prevPower else return10*prevPower end end -- Signed power function for squashing timeline to be more readable functionp._signedPow(x,p) ifx<0then return-math.pow(-x,p) end returnmath.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') functionp._scaleTime(t,from,to,height,scaling,power) ifscaling=='pow'then from=p._signedPow(from,power) to=p._signedPow(to,power) t=p._signedPow(t,power) end returnheight*(to-t)/(to-from) end -- Utility function: is this a recognized unit? localfunctionrecognizedUnit(unit) returnunit=="px"orunit=="em" end -- Utility function to convert between units -- Currently only supports em and px, -- assume M size of 13.3 pixels localfunctionconvertUnits(value,srcUnit,dstUnit) ifsrcUnit==dstUnitthen--- if src and dst units match, then return value returnvalue end value=tonumber(value) ifvalue==nilthen returnnil end ifsrcUnit=="px"anddstUnit=="em"then returnvalue/13.3 end ifsrcUnit=="em"anddstUnit=="px"then returnvalue*13.3 end returnnil--- 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. localfunctioncreateContainer(args) args.align=args.alignor"right" localcontainer=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") ifnotargs.embeddedthen ifargs.state=="collapsed"then args.collapsible=true container:addClass("mw-collapsed") container:addClass("nomobile") elseifargs.state=="autocollapse"then args.collapsible=true container:addClass("autocollapse") container:addClass("nomobile") end ifargs.collapsiblethen container:addClass("mw-collapsible") end end container:css("text-align","left") container:css("padding","0 0.5em") container:css("border-style",args.embeddedand"none"or"solid") ifargs.embeddedthen container:css("margin","auto") else container:css("float",args.align) ifargs.align=="right"orargs.align=="left"then container:css("clear",args.align) end localmargins={} margins[1]=args.marginor"0.3em" margins[2]=(args.align=="right"and0)orargs.marginor"1.4em" margins[3]=args.marginor"0.8em" margins[4]=(args.align=="left"and0)orargs.marginor"1.4em" container:css("margin",table.concat(margins," ")) end container:css("overflow","hidden") returncontainer 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]] localfunctioncreateTitle(container,args) container:attr("id","Title"..(args["instance-id"]or"")) localbottomPadding=args["link-to"]and(notargs.embedded) and(notargs.collapsible)and"0"or"1em" container:css("padding","1em 1em "..bottomPadding.." 1em") localtitle=container:tag('div') title:css("background-color",ignoreBlank(args["title-colour"]orargs["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]] localfunctionnavboxHeader(container,args) localframe=mw.getCurrentFrame() container:attr("id","Navbox"..(args["instance-id"]or"")) localtopMargin=args.titleand"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]] functionp._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") localincr=args["scale-increment"]orp._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 locallabelFreq=args["label-freq"]or1 labelFreq=labelFreq*2-- account for minor ticks localhalfIncr=incr/2 localtIndex=math.ceil(args.from/incr)*2-- always start on a label localtoIndex=math.floor(args.to/halfIncr) localtickCount=0 whiletIndex<=toIndexdo localt=tIndex*halfIncr localdiv=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%)") localspan=div:tag("span") span:css("font-size","90%") localtext="" iftickCount%labelFreq==0then ift<0then text=mw.ustring.format("−%g ",-t) else text=mw.ustring.format("%g ",t) end end iftickCount%2==0then 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 localfunctioncreateTimeline(container,args) localcolor=ignoreBlank(args["plot-colour"]orargs["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")or10 args.computedWidth=args.computedWidth+args.timelineWidth container:css("border","none") container:css("background-color",color) container:css("color","inherit") container:addClass("notheme") returncontainer 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) functionp._singleBar(container,args) args.text=args.textor" " args.nudgedown=(tonumber(args.nudgedown)or0)-(tonumber(args.nudgeup)or0) args.nudgeright=(tonumber(args.nudgeright)or0)-(tonumber(args.nudgeleft)or0) args.colour=args.colourorargs.defaultColor args.left=tonumber(args.left)or0 args.right=tonumber(args.right)or1 args.to=tonumber(args.to)orargs["tl-to"] args.from=tonumber(args.from)orargs["tl-from"] args.border=tonumber(args.border) args["border-style"]=args["border-style"]or((args.borderorargs["border-colour"])and"solid")or"none" -- the HTML element for the box/bar itself localbar=container:tag('div') bar:css("font-size","100%") bar:css("background-color",ignoreBlank(args.colouror"#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") localbar_top=p._scaleTime(args.to,args["tl-from"],args["tl-to"],args.height,args.scaling,args.power) localbar_bottom=p._scaleTime(args.from,args["tl-from"],args["tl-to"],args.height,args.scaling,args.power) localbar_height=bar_bottom-bar_top bar:css("top",checkDim(bar_top,args["height-unit"],nil,"%.3f")) ifargs["border-style"]~="none"andargs.borderthen 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 localtextParent=bar ifnotargs.alignBoxTextthen localnudge=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) localtext=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") localtext_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. functionp._bars(container,args) localbarArgs=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_,barginipairs(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=notargs["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 functionp._periods(container,args) localframe=mw.getCurrentFrame() localperiodArgs=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_,parginipairs(periodArgs)do parg.text=parg.textor("[["..parg.period.."]]") parg.textsize="90%" parg.colour=parg.colourorframe:expandTemplate{title=periodColor,args={parg.period}} parg.from=parg.fromortonumber("-"..frame:expandTemplate{title=periodStart,args={parg.period}}) parg.to=parg.toortonumber("-"..frame:expandTemplate{title=periodEnd,args={parg.period}}) iftonumber(parg.from)<tonumber(args.from)then parg.from=args.from end iftonumber(parg.to)>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=notargs["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) functionp._singleNote(container,args) -- Ensure some parameters default to sensible values args.at=tonumber(args.at)or0.5*(args.to+args.from) args.colour=args.colouror"var( --color-base, #000)" args.aw=tonumber(args.aw) -- if string is centering, use old width to not break it ormw.ustring.find(args.text,"center",1,true)andoldDefaultAW ordefaultAW args.textsize=args.textsizeor"90%" -- Convert 4 nudge arguments to 2 numeric signed nudge dimensions (right, down) args.nudgeright=(tonumber(args.nudgeright)or0)-(tonumber(args.nudgeleft)or0) args.nudgedown=(tonumber(args.nudgedown)or0)-(tonumber(args.nudgeup)or0) -- 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 ifargs.noarrthen -- First, place a bar that pushes annotation down to right spot localbar=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 localnudge=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 localtext=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 localtbl=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") localat_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%") localrow=tbl:tag('tr') localarrowCell=row:tag('td') arrowCell:css("padding","0") arrowCell:css("text-align","left") arrowCell:css("vertical-align","middle") localarrowSpan=arrowCell:tag('span') arrowSpan:css("color",args.colour) arrowSpan:wikitext("←")--- HTML for left-pointing arrow localtextCell=row:tag('td') textCell:css("padding","0") textCell:css("text-align","left") textCell:css("vertical-align","middle") localtextParent=textCell -- If disable-arrow-align is true, nudge the text per nudge dimensions: ifnotargs.alignArrowthen localnudge=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 localtext=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 functionp._annotations(container,args) localnoteArgs=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==0then return end -- a div to hold all of the notes localnotes=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) localrealNote=false for_,narginipairs(noteArgs)do localleft=(tonumber(narg.nudgeleft)or0)-(tonumber(narg.nudgeright)or0) ifnarg.text~=""and(notnarg.noarrorleft<=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 localaw=tonumber(args["annotations-width"])or(realNoteanddefaultAW)or0 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_,narginipairs(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=notargs["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 functionp._singleLegend(container,args) ifnotargs.textthen-- if no text, not a sensible legend return end args.colour=args.colourorargs.defaultColoror"transparent" localrow=container:tag('tr') localsquareCell=row:tag('td') squareCell:css("padding",0) localsquare=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(" ") localtextCell=row:tag('td') textCell:css("padding",0) localtext=textCell:tag('div') text:wikitext(args.text) end functionp._legends(container,args) locallegendArgs=p._scanArgs(args,{"^legend(%d+)$"}, {{"text","legend"}, {"colour","bar","-colour"}, {"colour","bar","-color"}, {"colour","legend","-colour"}, {"colour","legend","-color"} }) if#legendArgs==0then return end locallegendRow=container:tag('tr') locallegendCell=container:tag('td') legendCell:attr("id","Legend"..(args["instance-id"]or"")) legendCell:attr("colspan",3) legendCell:css("padding","0 0.2em 0.7em 1em") locallegend=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_,larginipairs(legendArgs)do p._singleLegend(legend,larg) end end localhelpString=[=[ ---- '''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}}}}]=] localfunctioncreateCaption(container,args) localcaptionRow=container:tag("tr") localcaptionCell=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") localcaption=captionCell:tag("div") caption:attr("id","Caption"..(args["instance-id"]or"")) caption:addClass("toccolours") ifargs.embeddedthen 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") localwidthUnit=args["width-unit"] localaw=tonumber(args["annotations-width"])or(args.hasRealNoteanddefaultAW)or-0.25 aw=aw+5+args.timelineWidth ifaw>args.computedWidththen args.computedWidth=aw end caption:css("width",checkDim(aw,"em",true,"%.3f")) caption:wikitext((args.captionor"")..((args.helpandargs.help~="off"andhelpString)or"")) end functionp._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_,attrinpairs({"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 localfrom=tonumber(args.from)or0 localto=tonumber(args.to)or0 iffrom>tothen args.from=to args.to=from else args.from=from args.to=to end ifargs.scaling=='sqrt'then args.scaling='pow' args.power=0.5 end ifargs.scaling=='pow'then args.power=args.poweror0.5 end args.computedWidth=1.7 -- Determine width/height values and units, with defaults args["height-unit"]=args.unitorargs["height-unit"]or"em" args["width-unit"]=args.unitorargs["width-unit"]or"em" args.height=args.heightorconvertUnits(36,"em",args["height-unit"]) args.width=args.widthorconvertUnits(10,"em",args["width-unit"]) ifnotargs.heightandnotrecognizedUnit(args["height-unit"])then args.height=36 args["height-unit"]="em" end ifnotargs.widthandnotrecognizedUnit(args["width-unit"])then args.width=10 args["width-unit"]="em" end -- Create container table localcontainer=createContainer(args) -- TITLE ifargs.titleandnotargs.embeddedthen localtitleRow=container:tag('tr') localtitleCell=titleRow:tag('td') titleCell:attr("colspan",3) createTitle(titleCell,args) end -- NAVBOX HEADER ifargs["link-to"]andnotargs.embeddedthen localnavboxRow=container:tag('tr') localnavboxCell=navboxRow:tag('td') navboxCell:attr("colspan",3) navboxHeader(navboxCell,args) end localcentralRow=container:tag('tr') centralRow:css("vertical-align","top") -- SCALEBAR localscaleCell=centralRow:tag('td') scaleCell:css("padding","0") scaleCell:css("margin","0.7em 0 0.7em 0") p._scalemarkers(scaleCell,args) -- TIMELINE localtimelineCell=centralRow:tag('td') timelineCell:css("padding","0") timelineCell:css("margin","0.7em 0 0.7em 0") localtimeline=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")) returncontainer end functionp.main(frame) localargs=getArgs(frame,{frameOnly=false,parentOnly=false,parentFirst=true,removeBlanks=false}) returntostring(p._main(args):allDone()) end returnp