Jump to content

Welcome to King Warz Charts Wiki! Please note that the site is still under construction as I work on creating past chart week pages, as well as templates and pages for artists, songs, and albums. Because of this, you may see a lot of red links throughout the site.

Module:KWCChart

From King Warz Charts

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