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)
"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
return trueend
-- render legend for pre-processed entriesfunction p.renderLegend(data, options) local html = "\n
-- 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
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
--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)
--function trim(s) return (s:gsub("^%s+", ""):gsub("%s+$", ""))end
return p