打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
登录后可编辑和发表评论。

Module:虚拟歌手外语排行榜/列表

来自Vocawiki

此模块的文档可以在Module:虚拟歌手外语排行榜/列表/doc创建

local p = {}

local jsonEncode = mw.text.jsonEncode

local function trim(v)
    if v == nil then return '' end
    v = tostring(v)
    return mw and mw.text and mw.text.trim and mw.text.trim(v) or (v:gsub('^%s+', ''):gsub('%s+$', ''))
end

local function toNumber(v)
    return tonumber(trim(v)) or 0
end

local segmentColors = {
    '#00B8C6','#60A5FA','#34D399','#FBBF24',
    '#F87171','#A78BFA','#22D3EE','#4ADE80',
    '#FB923C','#F472B6','#2DD4BF','#94A3B8',
    '#C084FC','#38BDF8','#86EFAC','#FDBA74'  
}

local function orderedArgs(frame)
    local parent = frame:getParent()
    local source = parent and parent.args or frame.args or {}
    local args, i = {}, 1
    while source[i] ~= nil do
        args[#args + 1] = source[i]
        i = i + 1
    end
    return args
end

local function parseArgs(frame)
    local args = orderedArgs(frame)
    local rawData = {}
    local lastSong = ''
    for i = 1, #args, 3 do
        local currentSong = trim(args[i])
        local startIssueArg = args[i + 1]
        local endIssueArg = args[i + 2]
        
        local song = currentSong ~= '' and currentSong or lastSong
        if currentSong ~= '' then
            lastSong = currentSong
        end

        if song == '' or startIssueArg == nil then break end
        
        local startIssue = toNumber(startIssueArg)
        if startIssue == 0 then break end
        
        local endIssue = toNumber(endIssueArg)
        if endIssue <= 0 or endIssue < startIssue then
            endIssue = startIssue
        end
        
        local count = endIssue - startIssue + 1
        local issues = count == 1 and ('#' .. startIssue) or ('#' .. startIssue .. '~#' .. endIssue)
        
        rawData[#rawData + 1] = {
            song = song,
            count = count,
            startIssue = tostring(startIssue),
            issues = issues
        }
    end
    return rawData
end

function p.chart(frame)
    local parent = frame:getParent()
    local args = parent and parent.args or frame.args
    local rawData = parseArgs(frame)
    
    if #rawData == 0 then return '' end

    local dimName = trim(args.dimName) ~= '' and trim(args.dimName) or '歌曲'
    local dimCount = trim(args.dimCount) ~= '' and trim(args.dimCount) or '次数'
    local dimIssue = trim(args.dimIssue) ~= '' and trim(args.dimIssue) or '期数'
    local xAxisName = trim(args.xAxisName) ~= '' and trim(args.xAxisName) or dimCount
    local dimSegment = 'INTERNAL_SEGMENT'
    local dimTotal = 'INTERNAL_TOTAL'

    local totals = {}
    for _, item in ipairs(rawData) do
        totals[item.song] = (totals[item.song] or 0) + item.count
    end

    local songCount = 0
    for _ in pairs(totals) do songCount = songCount + 1 end
    
    local source = {}
    local segmentCounters = {}
    local maxSegment = 0
    for _, item in ipairs(rawData) do
        local song = item.song
        local segmentNum = (segmentCounters[song] or 0) + 1
        segmentCounters[song] = segmentNum
        maxSegment = math.max(maxSegment, segmentNum)
        source[#source + 1] = { song, tostring(segmentNum), item.count, item.issues, totals[song] }
    end
    
    local datasets = {
        { id = 'source', dimensions = { dimName, dimSegment, dimCount, dimIssue, dimTotal }, source = source },
        { id = 'sorted_source', fromDatasetId = 'source', transform = { type = 'sort', config = { dimension = dimTotal, order = 'desc' } } },
        { id = 'unique_source', fromDatasetId = 'sorted_source', transform = { type = 'filter', config = { dimension = dimSegment, value = '1' } } }
    }
    for idx = 1, maxSegment do
        datasets[#datasets + 1] = {
            id = 'seg' .. idx,
            fromDatasetId = 'sorted_source',
            transform = { type = 'filter', config = { dimension = dimSegment, value = tostring(idx) } }
        }
    end
    
    local series = {}
    for idx = 1, maxSegment do
        local colorIndex = ((idx - 1) % #segmentColors) + 1
        series[#series + 1] = {
            type = 'bar',
            stack = 'total',
            datasetId = 'seg' .. idx,
            itemStyle = { color = segmentColors[colorIndex] },
            encode = { x = dimCount, y = dimName, tooltip = { dimCount, dimIssue } }
        }
    end

    series[#series + 1] = {
        type = 'scatter',
        datasetId = 'unique_source',
        symbolSize = 0,
        hoverAnimation = false,
        itemStyle = { color = '#00B8C6' },
        label = {
            show = true,
            position = 'right',
            color = '#333333',
            fontWeight = 'bold',
            formatter = string.format('{@%s}', dimTotal),
            fontSize = 12
        },
        encode = { x = dimTotal, y = dimName, tooltip = {dimTotal}, itemName = dimName }
    }
    
    local option = {
        tooltip = { trigger = 'item', confine = true },
        legend = { show = false },
        grid = { left = '20%', top = 20, bottom = 20 },
        xAxis = { type = 'value', name = xAxisName },
        yAxis = { type = 'category', inverse = true, axisLabel = { fontSize = 12 },name = dimName,nameLocation = 'start',nameGap = 5},
        dataset = datasets,
        series = series
    }
    
    local width = trim(args.width) ~= '' and trim(args.width) or '1000'
    local calculatedHeight = 80 + (songCount * 15)
    
    return frame:expandTemplate{ title = 'Echart', args = {
        data = jsonEncode(option),
        height = calculatedHeight,
        width = width
    }}
end

return p