Module:Timeline
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Timeline/doc
-- Module: Timeline -- Author: [[User:DuckeyD]] -- Inspired by: EasyTimeline by Erik Zachte -- Version: 1.1 -- <nowiki> local Timeline = {} -- Dependencies local colors = require('Module:Colors') local entrypoint = require('Module:Entrypoint') -- Constants local YEAR_LENGTH = 60 * 60 * 24 * 365.2 local MONTH_LENGTH = 60 * 60 * 24 * 30.44 -- Template function Timeline.main = entrypoint(Timeline) -- Main invokable function function Timeline.create(frame) -- Load config from a given module local conf = mw.loadData('Module:'..assert(frame.args[1], 'Config module not passed as a frame argument')) local container = mw.html.create('div') -- Style parameters local background_color = colors.parse('$color-text'):invert() local text_color = colors.params['color-text'] local timeline_padding = 4 local labels_width = 120 local bar_height = 16 local bar_margin = 8 local chart_margin = 10 local chart_major = colors.params['color-text'] local chart_minor = colors.parse('$color-text'):alpha(50):hex() local bar_background = background_color local bar_alpha = 40 local legend_columns = 3 -- Visibility parameters local timeline_hidden = false local legend_hidden = false local background_hidden = false -- Custom styling from config if conf.style then if conf.style.background_color then background_color = colors.parse(conf.style.background_color) end -- CSS Color if conf.style.text_color then text_color = conf.style.text_color end -- CSS Color if conf.style.timeline_padding then timeline_padding = conf.style.timeline_padding end -- number if conf.style.labels_width then labels_width = conf.style.labels_width end -- number if conf.style.bar_height then bar_height = conf.style.bar_height end -- number if conf.style.bar_margin then bar_margin = conf.style.bar_margin end -- number if conf.style.chart_margin then chart_margin = conf.style.chart_margin end -- number if conf.style.chart_major then chart_major = conf.style.chart_major end -- CSS Color if conf.style.chart_minor then chart_minor = conf.style.chart_minor end -- CSS Color if conf.style.bar_background then bar_background = conf.style.bar_background end -- CSS Color if conf.style.bar_alpha then bar_alpha = conf.style.bar_alpha end -- number if conf.style.legend_columns then legend_columns = conf.style.legend_columns end -- number -- conf.style.label_format -- string end -- Custom visibility settings from config if conf.hidden then if conf.hidden.timeline then timeline_hidden = conf.hidden.timeline end if conf.hidden.legend then legend_hidden = conf.hidden.legend end if conf.hidden.background then background_hidden = conf.hidden.background end end local chart_width = 700 - (labels_width + timeline_padding*2 + chart_margin) -- Root element styling container:css({ ['box-sizing'] = 'border-box', ['display'] = 'flex', ['width'] = '700px', ['padding'] = timeline_padding..'px', ['background-color'] = background_color:hex(), ['color'] = text_color, ['flex-wrap'] = 'wrap', ['margin'] = (timeline_padding*3)..'px 0' }) -- Separator for Mercury container:node(mercuryOnly(mw.html.create('hr'))) -- Labels local labels = mw.html.create('div') labels:css({ ['width'] = labels_width..'px', ['text-align'] = 'right', ['line-height'] = bar_height..'px', ['font-size'] = (0.75 * bar_height)..'px' }) for _, label in pairs(assert(conf.dataset, 'No dataset found in config')) do local label_elem = mw.html.create('div'):css({ ['height'] = bar_height..'px', ['margin'] = bar_margin..'px 0' }) local oasis_elem = oasisOnly(mw.html.create('span')) local mercury_elem = mercuryOnly(mw.html.create('span')) -- Custom label formatting - add style.label_format string containing "$name" to config if conf.style and conf.style.label_format then oasis_elem:wikitext((string.gsub(conf.style.label_format, '$name', (assert(label.name, 'No "name" on label '.._))))) mercury_elem:wikitext('<br>\'\'\''..string.gsub(conf.style.label_format, '$name', label.name)..'\'\'\'') else oasis_elem:wikitext((assert(label.name, 'No "name" on label '.._))) mercury_elem:wikitext('<br>\'\'\''..label.name..'\'\'\'') end label_elem:node(oasis_elem) label_elem:node(mercury_elem) -- Main Mercury design local mercury_list = mercuryOnly(mw.html.create('ul')) for _, bar in ipairs(assert(label.bars, 'No "bars" on label '..label.name)) do if assert(assert(conf.bar_types, 'No bar_types defined in config')[assert(bar.bar_type, 'No "bar_type" key on a bar in label '..label.name)], 'Bar type '..bar.bar_type..' not found').legend then local mercury_from = os.date('%d/%m/%Y', dateToTimestamp(assert(bar.from, 'No "from" key on a bar in label '..label.name), conf)) local mercury_till = os.date('%d/%m/%Y', dateToTimestamp(assert(bar.till, 'No "till" key on a bar in label '..label.name), conf)) local mercury_label = conf.bar_types[bar.bar_type].legend mercury_list:node(mw.html.create('li'):wikitext(mercury_label..':<br>'..mercury_from..' – '..mercury_till)) end end label_elem:node(mercury_list) labels:node(label_elem) end container:node(labels) -- Chart local chart = oasisOnly(mw.html.create('div')) chart:css({ ['width'] = chart_width..'px', ['margin-left'] = (chart_margin-1)..'px', ['border-left'] = '1px solid '..chart_major, ['border-bottom'] = '1px solid '..chart_major }) if not background_hidden then local chart_bg, chart_offset = generateBackground( assert(conf.from, 'No "from" key found in config'), assert(conf.till, 'No "till" key found in config'), chart_width, chart_major, chart_minor ) chart:css({ ['background-image'] = chart_bg, ['background-position-x'] = chart_offset }) end local chart_from = dateToTimestamp(conf.from) local chart_till = dateToTimestamp(conf.till) local chart_diff = chart_till - chart_from bar_background:alpha(bar_alpha) -- Chart bars for _, label in pairs(conf.dataset) do local bar_container = mw.html.create('div'):css({ ['height'] = bar_height..'px', ['margin'] = bar_margin..'px 0', ['background-color'] = bar_background:rgb(), ['position'] = 'relative' }) for _, bar in ipairs(label.bars) do local bar_from = dateToTimestamp(bar.from, conf) local bar_till = dateToTimestamp(bar.till, conf) bar_container:node(mw.html.create('div'):css({ ['height'] = bar_height..'px', ['background'] = assert(conf.bar_types[bar.bar_type].color, 'No color on bar type '..bar.bar_type), ['position'] = 'absolute', ['left'] = (((bar_from - chart_from)/chart_diff)*chart_width)..'px', ['right'] = (((chart_till - bar_till)/chart_diff)*chart_width)..'px' })) end chart:node(bar_container) end container:node(chart) -- Timeline if not timeline_hidden then local chart_timeline = mw.html.create('div'):css({ ['width'] = chart_width..'px', ['height'] = (0.75 * bar_height)..'px', ['line-height'] = (0.75 * bar_height)..'px', ['margin-left'] = (labels_width + chart_margin)..'px', ['font-size'] = (0.625 * bar_height)..'px', ['position'] = 'relative' }) for i = math.floor(chart_from/YEAR_LENGTH) + 1, math.floor(chart_till/YEAR_LENGTH), 1 do chart_timeline:node(mw.html.create('div'):wikitext(1970+i):css({ ['position'] = 'absolute', ['left'] = (((dateToTimestamp('01/01/'..(1970+i)) - chart_from)/chart_diff)*chart_width)..'px', ['transform'] = 'translate(-50%, 0)' })) end container:node(oasisOnly(chart_timeline)) end -- Legend if not legend_hidden then local legend = mw.html.create('div'):css({ ['margin-top'] = bar_height..'px', ['margin-left'] = (labels_width + chart_margin)..'px', ['font-size'] = (0.75*bar_height)..'px', ['width'] = chart_width..'px', ['display'] = 'flex', ['flex-wrap'] = 'wrap' }) for _, bar_type in pairs(conf.bar_types) do if bar_type.legend then local label_elem = mw.html.create('div'):css({ ['display'] = 'flex', ['align-items'] = 'center', ['height'] = bar_height..'px', ['width'] = (100/legend_columns)..'%' }) label_elem:node(mw.html.create('div'):css({ ['width'] = (0.75*bar_height)..'px', ['height'] = (0.75*bar_height)..'px', ['background'] = bar_type.color, ['margin-right'] = (bar_margin/2)..'px' })) if bar_type.order then label_elem:css('order', bar_type.order) end label_elem:wikitext(bar_type.legend) legend:node(label_elem) end end container:node(oasisOnly(legend)) end -- Separator for Mercury container:node(mercuryOnly(mw.html.create('br'))) container:node(mercuryOnly(mw.html.create('hr'))) return container end -- Helper functions function generateBackground(from, till, width, year_color, month_color) local start_date = dateToTimestamp(from) local end_date = dateToTimestamp(till) local diff = end_date - start_date local background = 'repeating-linear-gradient(to right, transparent' local month_multiplier = (MONTH_LENGTH*width)/diff for i = 1, 11, 1 do background = background..generateBar(i * month_multiplier, month_color) end background = background..generateBar(12 * month_multiplier, year_color)..')' local offset = (start_date % YEAR_LENGTH)*width/diff offset = '-'..offset..'px' return background, offset end function generateBar(pos, bar) pos = math.floor(pos) return ', transparent '..pos..'px, '..bar..' '..pos..'px, '..bar..' '..(pos+1)..'px, transparent '..(pos+1)..'px' end function mercuryOnly(elem) return elem:css('display', 'none !important') end function oasisOnly(elem) return elem:addClass('mobile-hidden') end function dateToTimestamp(d, conf) if d == 'now' then return os.time() end if d == 'start' and conf.from then return dateToTimestamp(conf.from) end if d == 'end' and conf.till then return dateToTimestamp(conf.till) end return os.time({ year = d:sub(7, 10), month = d:sub(4, 5), day = d:sub(1, 2), hour = 0, min = 0, sec = 0 }) end return Timeline -- </nowiki>