Module:KWCChart
Appearance
Documentation for this module may be created at Module:KWCChart/doc
-- Module:KWCChart — interactive ECharts builder for King Warz Charts.
-- Rendered client-side by the loader in [[MediaWiki:Common.js]] (ECharts via
-- cdnjs, which Miraheze's CSP allows). Parameter interface mirrors the
-- familiar Fandom chart template: weeks, positions N, name N, color N,
-- places, height, width — plus type=line|bar and inverse=0|1.
local p = {}
-- keep only characters that can legally appear in a JSON number/null/string
-- fragment list; blocks wikitext/HTML injection into the config JSON.
local function cleanFragment(s)
if not s then return nil end
s = mw.text.trim(s)
if s == '' then return nil end
-- allow: digits, quotes, letters (labels), spaces, commas, dots, dashes,
-- slashes, colons, apostrophes, parentheses, null
if s:find('[{}<>%[%]\\]') then return nil end
return s
end
local function cleanText(s)
if not s then return nil end
s = mw.text.trim(s)
if s == '' then return nil end
return (s:gsub('[{}<>%[%]\\"]', ''))
end
function p.chart(frame)
local pargs = frame:getParent().args
local fargs = frame.args
local charttype = cleanText(fargs.type or pargs.type) or 'line'
if charttype ~= 'line' and charttype ~= 'bar' then charttype = 'line' end
local inverse = (fargs.inverse or pargs.inverse or '') ~= ''
local places = tonumber(pargs.places) or 60
local height = cleanText(pargs.height) or '400px'
local width = cleanText(pargs.width) or '100%'
local labels = cleanFragment(pargs.weeks or pargs.labels or pargs[1])
if not labels then
return '<span class="error">KWCChart: missing weeks/labels</span>'
end
-- optional per-label years, shown in the hover tooltip as "143 (2026)"
local years = cleanFragment(pargs.years)
-- optional peak marker: peak week / peak pos / peak series (1-based)
local peakWeek = cleanText(pargs['peak week'])
local peakPos = tonumber(pargs['peak pos'])
local peakSeries = tonumber(pargs['peak series']) or 1
-- optional shaded band for the longest consecutive chart run
local runStart = cleanText(pargs['run start'])
local runEnd = cleanText(pargs['run end'])
-- series 1..20 (positions/positions N, values/values N aliases)
local series = {}
for i = 1, 20 do
local key = (i == 1)
and (pargs['positions'] or pargs['positions 1']
or pargs['values'] or pargs['values 1'] or pargs[2])
or (pargs['positions ' .. i] or pargs['values ' .. i])
local data = cleanFragment(key)
if data then
local name = cleanText(pargs['name ' .. i]
or (i == 1 and pargs['name'] or nil))
local color = cleanText(pargs['color ' .. i]
or (i == 1 and pargs['color'] or nil))
local s = '{"type":"' .. charttype .. '","smooth":true,' ..
'"connectNulls":false,"symbolSize":5,'
if name then s = s .. '"name":"' .. name .. '",' end
if color then
s = s .. '"lineStyle":{"color":"' .. color .. '"},' ..
'"itemStyle":{"color":"' .. color .. '"},'
end
if peakWeek and peakPos and (#series + 1) == peakSeries then
s = s .. '"markPoint":{"symbol":"pin","symbolSize":34,' ..
'"itemStyle":{"color":"#d4a72c"},' ..
'"label":{"fontSize":9,"color":"#fff"},' ..
'"data":[{"coord":["' .. peakWeek .. '",' .. peakPos ..
'],"value":"#' .. peakPos .. '"}]},'
end
if runStart and runEnd and #series == 0 then
s = s .. '"markArea":{"silent":true,' ..
'"itemStyle":{"color":"rgba(47,111,235,0.07)"},' ..
'"data":[[{"xAxis":"' .. runStart .. '"},' ..
'{"xAxis":"' .. runEnd .. '"}]]},'
end
s = s .. '"data":[' .. data .. ']}'
series[#series + 1] = s
end
end
if #series == 0 then
return '<span class="error">KWCChart: no positions/values given</span>'
end
local yaxis
if inverse then
yaxis = '{"type":"value","min":1,"max":' .. places ..
',"inverse":true}'
else
yaxis = '{"type":"value"}'
end
local legend = (#series > 1)
and '"legend":{"top":0,"type":"scroll"},' or ''
local yearsPart = ''
if years then
yearsPart = '"kwcYears":[' .. years .. '],'
end
local json = '{' ..
yearsPart ..
'"textStyle":{"fontSize":12},' ..
'"tooltip":{"trigger":"axis"},' ..
legend ..
'"grid":{"top":' .. (#series > 1 and 48 or 40) ..
',"left":40,"right":16,"bottom":54},' ..
'"dataZoom":[{"type":"inside"},{"type":"slider","height":18,"bottom":6}],' ..
'"xAxis":{"type":"category","axisLabel":{"interval":"auto","rotate":45},' ..
'"data":[' .. labels .. ']},' ..
'"yAxis":' .. yaxis .. ',' ..
'"series":[' .. table.concat(series, ',') .. ']' ..
'}'
return '<div class="kwc-echart" style="width:' .. width ..
';height:' .. height .. '">' ..
'<pre class="kwc-echart-config" style="display:none">' ..
mw.text.nowiki(json) .. '</pre></div>'
end
return p