Bureaucrats, Moderators (CommentStreams), Interface administrators, Push subscription managers, Suppressors, Administrators
4,206
edits
(from https://dev.fandom.com/wiki/Global_Lua_Modules/Timeline) |
No edit summary |
||
Line 1: | Line 1: | ||
-- Module: Timeline | |||
-- Author: [[User:DuckeyD]] | |||
-- Inspired by: EasyTimeline by Erik Zachte | |||
-- Version: 1.1 | |||
-- <nowiki> | |||
local Timeline = {} | |||
-- Dependencies | |||
local colors = require('Dev:Colors') | |||
local entrypoint = require('Dev: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> |