Module:Piechart Explained

local p = ---- Author: Maciej Nux (pl.wikipedia.org).

--

--function p.color(frame) local index = tonumber(trim(frame.args[1])) return ' ' .. defaultColor(index)end

-- function p.pie(frame) local json_data = trim(frame.args[1]) local options = nil if (frame.args.meta) then options = trim(frame.args.meta) end local html = p.renderPie(json_data, options) return trim(html)end

-- Setup chart options.function p.setupOptions(json_options) local options = if json_options then local rawOptions = mw.text.jsonDecode(json_options) if rawOptions then if type(rawOptions.size)

"number" then options.size = math.floor(rawOptions.size) end options.autoscale = rawOptions.autoscale or false if rawOptions.legend then options.legend = true end if rawOptions.ariahidechart then options.ariahidechart = true end if (type(rawOptions.direction)

"string") then -- Remove unsafe/invalid characters local sanitized = rawOptions.direction:gsub("[^a-z0-9%-]", "") -- also adjust width so that row-reverse won't push things to the right options.direction = 'width: max-content; flex-direction: ' .. sanitized end end end if (options.legend) then options.ariahidechart = true end return optionsend

--Render piechart. @param json_data JSON string with pie data.function p.renderPie(json_data, json_options) local data = mw.text.jsonDecode(json_data) local options = p.setupOptions(json_options)

-- prepare local ok, total = p.prepareEntries(data, options)

-- init render local html = "

"

-- error info if not ok then html = html .. renderErrors(data) end

-- render legend if options.legend then html = html .. p.renderLegend(data, options) end

-- render items local header, items, footer = p.renderEntries(ok, total, data, options) html = html .. header .. items .. footer

-- end .smooth-pie-container html = html .. "\n

"

return htmlend

-- Prepare data (slices etc)function p.prepareEntries(data, options) local sum = sumValues(data); -- force autoscale when over 100 if (sum > 100) then options.autoscale = true end -- pre-format entries local ok = true local no = 0 local total = #data for index, entry in ipairs(data) do no = no + 1 if not prepareSlice(entry, no, sum, total, options) then no = no - 1 ok = false end end total = no -- total valid

return ok, totalend

function sumValues(data) local sum = 0; for _, entry in ipairs(data) do local value = entry.value if not (type(value) ~= "number" or value < 0) then sum = sum + value end end return sumend

-- render error infofunction renderErrors(data) local html = "\n

\n"end

-- Prepare single slice data (modifies entry).function prepareSlice(entry, no, sum, total, options) local autoscale = options.autoscale local value = entry.value if (type(value) ~= "number" or value < 0) then if autoscale then entry.error = "cannot autoscale unknown value" return false end value = 100 - sum end -- entry.raw only when scaled if autoscale then entry.raw = value value = (value / sum) * 100 end entry.value = value

-- prepare final label entry.label = prepareLabel(entry.label, entry) -- prepare final slice bg color local index = no if no

total then index = -1 end entry.bcolor = backColor(entry, index)

return trueend

-- render legend for pre-processed entriesfunction p.renderLegend(data, options) local html = "\n

    " for _, entry in ipairs(data) do if not entry.error then html = html .. renderLegendItem(entry, options) end end return html .. "\n
\n"end-- render legend itemfunction renderLegendItem(entry, options) local label = entry.label local bcolor = entry.bcolor local html = "\n
  • " html = html .. '

    ' html = html .. ''..label..'' return html .. "
  • "end

    -- Prepare data (slices etc)function p.renderEntries(ok, total, data, options) -- cache for some items (small slices) p.cuts = mw.loadJsonData('Module:Piechart/cuts.json')

    local first = true local previous = 0 local no = 0 local items = "" local header = "" for index, entry in ipairs(data) do if not entry.error then no = no + 1 if no

    total then header = renderFinal(entry, options) else items = items .. renderOther(previous, entry, options) end previous = previous + entry.value end end local footer = '\n

    '

    return header, items, footerend-- final, but header...function renderFinal(entry, options) local label = entry.label local bcolor = entry.bcolor local size = options.size

    -- hide chart for readers, especially when legend is there local aria = "" if (options.ariahidechart) then aria = 'aria-hidden="true"' end

    -- slices container and last slice local style = 'width:'..size..'px; height:'..size..'px;'..bcolor local html = ..style.." title="..label.." ..aria..> return htmlend-- any other then finalfunction renderOther(previous, entry, options) local value = entry.value local label = entry.label local bcolor = entry.bcolor

    -- value too small to see if (value < 0.03) then mw.log('value too small', value, label) return "" end local html = "" local size = -- mw.logObject if (value >= 50) then html = sliceWithClass('pie50', 50, value, previous, bcolor, label) elseif (value >= 25) then html = sliceWithClass('pie25', 25, value, previous, bcolor, label) elseif (value >= 12.5) then html = sliceWithClass('pie12-5', 12.5, value, previous, bcolor, label) elseif (value >= 7) then html = sliceWithClass('pie7', 7, value, previous, bcolor, label) elseif (value >= 5) then html = sliceWithClass('pie5', 5, value, previous, bcolor, label) else -- 0-5% local cutIndex = round(value*10) if cutIndex < 1 then cutIndex = 1 end local cut = p.cuts[cutIndex] local transform = rotation(previous) html = sliceX(cut, transform, bcolor, label) end -- mw.log(html)

    return htmlend

    -- round to intfunction round(number) return math.floor(number + 0.5)end

    -- render full slice with specific classfunction sliceWithClass(sizeClass, sizeStep, value, previous, bcolor, label) local transform = rotation(previous) local html = "" html = html .. sliceBase(sizeClass, transform, bcolor, label) -- mw.logObject if (value > sizeStep) then local extra = value - sizeStep transform = rotation(previous + extra) -- mw.logObject html = html .. sliceBase(sizeClass, transform, bcolor, label) end return htmlend

    -- render single slicefunction sliceBase(sizeClass, transform, bcolor, label) local style = bcolor if transform ~= "" then style = style .. '; ' .. transform end return '\n\t

    'end

    -- small slice cut to fluid size.-- range in theory: 0 to 24.(9)% reaching 24.(9)% for cut = +inf-- range in practice: 0 to 5%function sliceX(cut, transform, bcolor, label) local path = 'clip-path: polygon(0% 0%, '..cut..'% 0%, 0 100%)' return '\n\t

    'end

    -- translate value to turn rotationfunction rotation(value) if (value > 0) then return string.format("transform: rotate(%.3fturn)", value/100) end return end

    -- Language sensitive float.function formatNum(value) local lang = mw.language.getContentLanguage -- doesn't do precision :(-- local v = lang:formatNum(value) local v = string.format("%.1f", value) if (lang:getCode

    'pl') then v = v:gsub("%.", ",") end return vend

    --function prepareLabel(tpl, entry) -- static tpl if tpl and not string.find(tpl, '$') then return tpl end

    -- format % value without % local p = formatNum(entry.value)

    -- default template if not tpl then tpl = "$v" end

    local label = "" if entry.raw then label = tpl:gsub("%$p", p .. "%%"):gsub("%$d", entry.raw):gsub("%$v", entry.raw .. " (" .. p .. "%%)") else label = tpl:gsub("%$v", p .. "%%") end return labelend

    -- default colorslocal colorPalette = local lastColor = '#cdf099'-- background color from entry or the default colorsfunction backColor(entry, no) if (type(entry.color)

    "string") then -- Remove unsafe characters from entry.color local sanitizedColor = entry.color:gsub("[^a-zA-Z0-9#%-]", "") return 'background-color: ' .. sanitizedColor else local color = defaultColor(no) return 'background-color: ' .. color endend-- color from the default colors-- last entry color for 0 or -1function defaultColor(no) local color = lastColor if (no > 0) then local cIndex = (no - 1) % #colorPalette + 1 color = colorPalette[cIndex] end return colorend

    --function trim(s) return (s:gsub("^%s+", ""):gsub("%s+$", ""))end

    return p