Module:Horizontal timeline
local horizontal_timeline = {};
 
 local getArgs = require('Module:Arguments').getArgs
 local builder = mw.html.create()
 
 local function defaultInvokeFunc(funcName)
 	return function (frame)
 		args = getArgs(frame, {
                        trim = true,
                        removeBlanks = true,
                        parentFirst = true
                     })
 
         local from = getNotNilValue(tonumber(args['from']))
         local to =  getNotNilValue(tonumber(args['to']))
 
         if not from or not to or from == to then
             return (&quot;&lt;strong class='error'&gt;&lt;code&gt;from&lt;/code&gt; and &lt;code&gt;to&lt;/code&gt; cannot be &lt;code&gt;nil&lt;/code&gt; or equal.&lt;/strong&gt;&quot;)
         else
         	return horizontal_timeline[funcName](args)
         end
 	end
 end
 
 horizontal_timeline.showTimeLine = defaultInvokeFunc('_showTimeLine')
 function horizontal_timeline._showTimeLine(args)
     local wdth = getNotNilValue(args['width'], '100%' )
     local bordr = getNotNilValue(args['border'], '1px solid rgb(170, 170, 170)' )
     local bgCol = getNotNilValue(args['plot-color'], args['plot-colour'], 'transparent')
     local mrgn = getNotNilValue(args['margin'], '1em')
 
     local div_root = builder
         :tag('div')
         :cssText('float:left;border:'..bordr .. ';width:'..wdth)
 
     local cntnt = div_root
         :tag('div')
         :cssText('text-align:left; padding:1em; font-size:95%; margin:' ..mrgn.. '; background:'..bgCol)
            
     local rowNums = affixNums(args, 'row') -- Gets numbers for row1, row2, etc. with nil arguments removed.
     for _, num in ipairs(rowNums) do
         local rowType = args['row' .. num] -- Gets args.rowtype1, args.rowtype2, etc. with nil arguments removed.
         if rowType == 'scale' then
             cntnt:wikitext(horizontal_timeline.scaleRow(args))
         elseif rowType == 'note' then
             cntnt:wikitext(horizontal_timeline.noteRow(num, args))
         elseif rowType == 'timeline' then
             cntnt:wikitext(horizontal_timeline.timelineRow(num, args))
         else
             cntnt:wikitext(rowType)
         end
     end
     if args.caption then
         cntnt:tag('p')
             :cssText('clear:both; text-align:center')
             :wikitext(args.caption)
             :done()
     end
     return tostring(div_root) .. &quot;&lt;div style='clear:left;'&gt;&lt;/div&gt;&quot;
 end
 
 horizontal_timeline.showOneRow = defaultInvokeFunc('_showOneRow')
 function horizontal_timeline._showOneRow(args)
     local rowNums = affixNums(args, 'row') -- Gets numbers for row1, row2, etc. with nil arguments removed.
     for _, num in ipairs(rowNums) do
         local rowType = args['row' .. num] -- Gets args.rowtype1, args.rowtype2, etc. with nil arguments removed.
         if rowType == 'scale' then
             return horizontal_timeline.scaleRow(args)
         elseif rowType == 'note' then
             return horizontal_timeline.noteRow(num, args)
         elseif rowType == 'timeline' then
             return horizontal_timeline.timelineRow(num, args)
         else
             return wikitext(rowType)
         end
     end
     return &quot;?&quot;
 end
 
 function horizontal_timeline.timelineRow(num, args)
     local root = mw.html.create()
     
     local from = getNotNilValue(tonumber(args['from']))
     local to =  getNotNilValue(tonumber(args['to']))
     
     local style    = getNotNilValue(args['row' .. num .. '-style'], '')
     local hght     = getNotNilValue (args['row' .. num .. '-height'],
 									 args[style .. '-height'],
 									 '2.5em')
     local bordrTop = getNotNilValue (args['row' .. num .. '-bordertop'],
 									 args[style .. '-bordertop'],
 									 'none')
     local bordrBtm = getNotNilValue (args['row' .. num .. '-borderbottom'],
 									 args[style .. '-borderbottom'],
 									 'none')
     local txtTop   = getNotNilValue (args['row' .. num .. '-texttop'],
 									 args[style .. '-texttop'],
 									 '0em')
     local colr     = getNotNilValue (args['row' .. num .. '-colour'],
     	                             args['row' .. num .. '-color'],
     	                             args[style .. '-colour'],
     	                             args[style .. '-color'],
     	                             'transparent')
     	                             
     if bordrTop ~= 'none' then
     	bordrTop = 'border-top:' .. bordrTop .. ';'
     else
     	bordrTop = ''
 	end
 	
 	if bordrBtm ~= 'none' then
     	bordrBtm = 'border-bottom:' .. bordrBtm .. ';'
     else
     	bordrBtm = ''
 	end
 
     local p = root
          :tag('div')
             :cssText(&quot;clear:both;width:100%; padding:0px; height:&quot;.. hght)
             :cssText(bordrTop.. bordrBtm .. &quot;background-color:&quot;..colr)
 
     local rowDat = affixNums(args, 'row'..num..'%-', '%-[a-zA-Z]*')
     local lastTo = from
     local firstNode = true
     for _, vals in ipairs(rowDat) do
 
     	local styleL    = getNotNilValue(args['row' .. num .. '-'.. vals ..  '-style'], style)
 
         --These vars should be initialized every iteration. Do not move outside of loop
         local bar_to = tonumber(getNotNilValue(args['row' .. num .. '-'.. vals .. '-to'],
                                         args[styleL .. '-to'],
                                         args[style.. '-'.. vals .. '-to'], to  ) )
         local bar_fontsize =getNotNilValue(args['row' .. num .. '-'.. vals .. '-fontsize'],
         								args[styleL .. '-fontsize'],
                                         args[style..'-'.. vals .. '-fontsize'], '0.9em' )
         local bar_bordr= getNotNilValue(args['row' .. num .. '-'.. vals .. '-border'],
         								args[styleL .. '-border'],
                                         args[style..'-'.. vals .. '-border'],
                                         'none')
         local bar_txtTop= getNotNilValue(args['row' .. num .. '-'.. vals .. '-texttop'],
         								args[styleL .. '-texttop'],
                                         args[style..'-'.. vals .. '-texttop'], txtTop )
         local bar_text = getNotNilValue(args['row' .. num .. '-'.. vals .. '-text'],
         								args[styleL .. '-text'],
                                         args[style..'-'.. vals .. '-text'], '')
         local bar_colour = getNotNilValue(args['row' .. num .. '-'.. vals .. '-colour'],
                                         args['row' .. num .. '-'.. vals .. '-color'],
                                         args[styleL .. '-boxcolour'],
                                         args[styleL .. '-boxcolor'],
                                         args[style..'-'.. vals .. '-colour'],
                                         args[style..'-'.. vals .. '-color'],
                                         'transparent' )
                                         
         if from &lt; to then
             if bar_to &gt; to then bar_to = to end
             if lastTo &lt; from then lastTo = from end
         else
             if bar_to &lt; to then bar_to = to end
             if lastTo &gt; from then lastTo = from end
     	end
 
         local width =( (bar_to-lastTo)*100 / (to-from) ) --math.abs
 
         if width &gt; 0 and width &lt;= 100 then
             if bar_bordr == 'none' then
                 if firstNode then -- for first box both left and right border needed
                     bar_bordr = &quot;1px solid #000; border-left:1px solid #000&quot;
                     firstNode = false
                 else
                     bar_bordr = &quot;1px solid #000&quot;
     	        end
 		    end
             p:tag('div')
                 :cssText(&quot;float:left; height:100%; text-align:center; overflow: hidden; background-color:&quot;..bar_colour)
                 :cssText(&quot;width:&quot;..width ..&quot;%&quot;)
                     :tag('div')
                     :cssText(&quot;-moz-box-sizing: border-box; -webkit-box-sizing: border-box;box-sizing: border-box;&quot;)
                     :cssText(&quot;float:right; height:100%; width:100%; border-right:&quot;..bar_bordr)
                         :tag('div')
                             :cssText('position: relative; top:'..bar_txtTop .. '; font-size:'.. bar_fontsize)
                             :wikitext(bar_text)
                         :done()
                     :done()
                 :done()
         end
 
         lastTo = bar_to
     end
     return tostring(root)
 end
 
 function horizontal_timeline.noteRow(num, args)
     local root = mw.html.create()
     
     local from = getNotNilValue(tonumber(args['from']))
     local to =  getNotNilValue(tonumber(args['to']))
     
     local hght = getNotNilValue(args['row' .. num .. '-height'], '2.5em')
 
     local p = root
          :tag('div')
             :cssText(&quot;width:100%; position:relative; left:-0.2em; top:0.8em; clear:both; height:&quot;.. hght)
 
     local rowDat = affixNums(args, 'row'..num..'%-', '%-at')
     if not rowDat then
         return (&quot;&lt;strong class='error'&gt;Please specify location for note at &lt;code&gt;&quot;..'row' .. num .. '-'.. vals .. '-at'..&quot;&lt;/code&gt; parameter.&lt;/strong&gt;&quot;)
     end
     for _, vals in ipairs(rowDat) do
         local note_at   =args['row' .. num .. '-'.. vals .. '-at'] --will never be nil as it is what is sued to recieve rowDat
         local note_text =getNotNilValue(args['row' .. num .. '-'.. vals .. '-text'], '' )
         local note_shift=getNotNilValue(args['row' .. num .. '-'.. vals .. '-shift'], '0em' )
         local note_lift =getNotNilValue(args['row' .. num .. '-'.. vals .. '-lift'], '0em' )
         local note_arrow=getNotNilValue(args['row' .. num .. '-'.. vals .. '-arrow'], '↓' )
 
 		local note_sft = 100*(note_at - from) / (to-from)
         
         p:tag('div')
             :cssText(&quot;position:absolute; top:0px; width:100%&quot;)
                 :tag('div')
                     :cssText(&quot;margin-left:&quot;.. note_sft ..&quot;%; margin-top:0; position:relative&quot;)
                     :tag('span')
                         :cssText(&quot;position:relative; top:0.25em; left:-1.5px&quot;)
                         :wikitext(note_arrow)
                         :done()
                     :tag('span')
                         :cssText(&quot;font-size:smaller; position:relative; line-height:3px; overflow:visible&quot;)
                         :cssText(&quot;left:&quot;..note_shift..&quot;; top:&quot;..note_lift..&quot;; z-index:&quot;.. (1000- tonumber(num)))
                         :wikitext(note_text)
                         :done()
                     :done()
                 :done()
             :done()
     end
     return tostring(root)
 end
 
 function horizontal_timeline.scaleRow(args)
 	local from = getNotNilValue(tonumber(args['from']))
     local to =  getNotNilValue(tonumber(args['to']))
     local inc = getNotNilValue( tonumber(args['inc']), (math.floor( (from - to) / 5 ) * -1) )
     local negativeFmt = getNotNilValue(args['axis-negativeFmt'], '−%s')
     local positiveFmt = getNotNilValue(args['axis-positiveFmt'], '%s')
     local zeroFmt     = getNotNilValue(args['axis-zeroFmt'], '%s')
     local nudge       = getNotNilValue(args['axis-nudge'], '-1.8em')
 
     local wdth = math.abs ( (100 * inc) / (from - to) )
     local root = mw.html.create()
 
     local p = root
         :wikitext(&quot;&lt;div name='line' style='clear:both;width:100%;max-width:100%;border-top:0.1em solid black;height:1em;'&gt;&lt;/div&gt;&quot;)
         :tag('div')
             :attr('id', 'Scale')
             :cssText('clear:both;position:relative;top:-1.4em;left:-0.2em;width:100%;padding:0;height:2.5em')
             
     for var=from, to, inc do
         if from &lt; to then
             if var+inc &gt; to then wdth = 0 end
         else
             if var+inc &lt; to then wdth = 0 end
         end
         
         local lbl
         if var &lt; 0 then
             lbl = string.format( negativeFmt, math.abs(var) )
         elseif var &gt; 0 then
             lbl = string.format( positiveFmt, math.abs(var) )
         else
             lbl = string.format( zeroFmt, math.abs(var) )
         end
 
         local markr = getNotNilValue(args['axis-marker-'..lbl], '│') 
 		lbl = getNotNilValue(args['axis-'..lbl], lbl)
 		
         p:tag('div')
             :cssText('float:left;overflow:visible;width:'.. wdth .. '%')
             :wikitext(markr)
             :tag('div')
                 :cssText('font-size:86%; position:relative; left:'..nudge..'; overflow:visible; white-space:nowrap')
                 :wikitext(lbl)
                 :done()
         :done()
     end
     p:done()
     return tostring(root)
 end
 
 --Returns the first non nil value from the list of parameters.
 function getNotNilValue(...)
     for _,v in pairs(arg) do --Do not use ipairs. Will stop at first nil
         if v then return v end
     end
     return nil
 end
 
 function affixNums(t, prefix, suffix)
     prefix = prefix or ''
     suffix = suffix or ''
     
     local pattern = '^' .. prefix .. '([1-9]%d*)' .. suffix .. '$'
     local nums = {}
     for k, v in pairs(t) do
         if type(k) == 'string' then            
             local num = mw.ustring.match(k, pattern)
             if num then
                 nums[#nums + 1] = tonumber(num)
             end
         end
     end
     table.sort(nums)
     return nums
 end