<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://prdmc.ca/w/index.php?action=history&amp;feed=atom&amp;title=Module%3APiechart</id>
	<title>Module:Piechart - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://prdmc.ca/w/index.php?action=history&amp;feed=atom&amp;title=Module%3APiechart"/>
	<link rel="alternate" type="text/html" href="https://prdmc.ca/w/index.php?title=Module:Piechart&amp;action=history"/>
	<updated>2026-04-15T06:40:12Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.1</generator>
	<entry>
		<id>https://prdmc.ca/w/index.php?title=Module:Piechart&amp;diff=1056&amp;oldid=prev</id>
		<title>RandomUser34: 1 revision imported</title>
		<link rel="alternate" type="text/html" href="https://prdmc.ca/w/index.php?title=Module:Piechart&amp;diff=1056&amp;oldid=prev"/>
		<updated>2026-02-15T05:40:24Z</updated>

		<summary type="html">&lt;p&gt;1 revision imported&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 05:40, 15 February 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;4&quot; class=&quot;diff-notice&quot; lang=&quot;en&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(No difference)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;!-- diff cache key prd_wiki:diff:1.41:old-1055:rev-1056 --&gt;
&lt;/table&gt;</summary>
		<author><name>RandomUser34</name></author>
	</entry>
	<entry>
		<id>https://prdmc.ca/w/index.php?title=Module:Piechart&amp;diff=1055&amp;oldid=prev</id>
		<title>wp&gt;Nux: typo • Wikiploy</title>
		<link rel="alternate" type="text/html" href="https://prdmc.ca/w/index.php?title=Module:Piechart&amp;diff=1055&amp;oldid=prev"/>
		<updated>2026-01-25T19:44:18Z</updated>

		<summary type="html">&lt;p&gt;typo • &lt;a href=&quot;/w/index.php?title=En:WP:Wikiploy&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;En:WP:Wikiploy (page does not exist)&quot;&gt;Wikiploy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local p = {}&lt;br /&gt;
local priv = {} -- private functions scope&lt;br /&gt;
-- expose private for easy testing/debugging&lt;br /&gt;
p.__priv = priv&lt;br /&gt;
&lt;br /&gt;
-- require exact colors for printing&lt;br /&gt;
local forPrinting = &amp;quot;-webkit-print-color-adjust: exact; print-color-adjust: exact;&amp;quot;&lt;br /&gt;
--[===[&lt;br /&gt;
	Smooth piechart module.&lt;br /&gt;
&lt;br /&gt;
	Draws charts in HTML with an accessible legend (optional).&lt;br /&gt;
	A list of all features is in the &amp;quot;TODO&amp;quot; section of the main `p.pie` function.&lt;br /&gt;
&lt;br /&gt;
	Module info:&lt;br /&gt;
	- Changelog and TODO: [[:en:User:Nux/pie_chart_-_todo]] (Piechart 1.0, 2.0 and beyond).&lt;br /&gt;
	- Author: [[:en:User:Nux|Maciej Nux]].&lt;br /&gt;
&lt;br /&gt;
	Use with a helper template that adds required CSS.&lt;br /&gt;
&lt;br /&gt;
	{{{1}}}:&lt;br /&gt;
	[&lt;br /&gt;
		{ &amp;quot;label&amp;quot;: &amp;quot;pie: $v&amp;quot;, &amp;quot;color&amp;quot;: &amp;quot;wheat&amp;quot;, &amp;quot;value&amp;quot;: 40 },&lt;br /&gt;
		{ &amp;quot;label&amp;quot;: &amp;quot;cheese pizza $v&amp;quot;, &amp;quot;color&amp;quot;: &amp;quot;#fc0&amp;quot;, &amp;quot;value&amp;quot;: 20 },&lt;br /&gt;
		{ &amp;quot;label&amp;quot;: &amp;quot;mixed pizza: $v&amp;quot;, &amp;quot;color&amp;quot;: &amp;quot;#f60&amp;quot;, &amp;quot;value&amp;quot;: 20 },&lt;br /&gt;
		{ &amp;quot;label&amp;quot;: &amp;quot;raw pizza $v&amp;quot;, &amp;quot;color&amp;quot;: &amp;quot;#f30&amp;quot; }&lt;br /&gt;
	]&lt;br /&gt;
	Where $v is a formatted number (see `function prepareLabel`).&lt;br /&gt;
&lt;br /&gt;
	{{{meta}}}:&lt;br /&gt;
		{&amp;quot;size&amp;quot;:200, &amp;quot;autoscale&amp;quot;:false, &amp;quot;legend&amp;quot;:true}&lt;br /&gt;
&lt;br /&gt;
	All meta options are optional (see `function p.setupOptions`).&lt;br /&gt;
]===]&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
	Debug:&lt;br /&gt;
	&lt;br /&gt;
	-- labels and auto-value&lt;br /&gt;
	local json_data = &amp;#039;[{&amp;quot;label&amp;quot;: &amp;quot;k: $v&amp;quot;, &amp;quot;value&amp;quot;: 33.1}, {&amp;quot;label&amp;quot;: &amp;quot;m: $v&amp;quot;, &amp;quot;value&amp;quot;: -1}]&amp;#039;&lt;br /&gt;
	local html = p.renderPie(json_data)&lt;br /&gt;
	mw.logObject(html)&lt;br /&gt;
	&lt;br /&gt;
	-- autoscale values&lt;br /&gt;
	local json_data = &amp;#039;[{&amp;quot;value&amp;quot;: 700}, {&amp;quot;value&amp;quot;: 300}]&amp;#039;&lt;br /&gt;
	local html = p.renderPie(json_data, options)&lt;br /&gt;
	mw.logObject(html)	&lt;br /&gt;
	&lt;br /&gt;
	-- size option&lt;br /&gt;
	local json_data = &amp;#039;[{&amp;quot;label&amp;quot;: &amp;quot;k: $v&amp;quot;, &amp;quot;value&amp;quot;: 33.1}, {&amp;quot;label&amp;quot;: &amp;quot;m: $v&amp;quot;, &amp;quot;value&amp;quot;: -1}]&amp;#039;&lt;br /&gt;
	local options = &amp;#039;{&amp;quot;size&amp;quot;:200}&amp;#039;&lt;br /&gt;
	local html = p.renderPie(json_data, options)&lt;br /&gt;
	mw.logObject(html)	&lt;br /&gt;
&lt;br /&gt;
	-- custom colors&lt;br /&gt;
	local json_data = &amp;#039;[{&amp;quot;label&amp;quot;: &amp;quot;k: $v&amp;quot;, &amp;quot;value&amp;quot;: 33.1, &amp;quot;color&amp;quot;:&amp;quot;black&amp;quot;}, {&amp;quot;label&amp;quot;: &amp;quot;m: $v&amp;quot;, &amp;quot;value&amp;quot;: -1, &amp;quot;color&amp;quot;:&amp;quot;green&amp;quot;}]&amp;#039;&lt;br /&gt;
	local html = p.renderPie(json_data)&lt;br /&gt;
	mw.logObject(html)&lt;br /&gt;
	&lt;br /&gt;
	-- 4-cuts&lt;br /&gt;
	local entries = {&lt;br /&gt;
		&amp;#039;{&amp;quot;label&amp;quot;: &amp;quot;ciastka: $v&amp;quot;, &amp;quot;value&amp;quot;: 2, &amp;quot;color&amp;quot;:&amp;quot;goldenrod&amp;quot;}&amp;#039;,&lt;br /&gt;
		&amp;#039;{&amp;quot;label&amp;quot;: &amp;quot;słodycze: $v&amp;quot;, &amp;quot;value&amp;quot;: 4, &amp;quot;color&amp;quot;:&amp;quot;darkred&amp;quot;}&amp;#039;,&lt;br /&gt;
		&amp;#039;{&amp;quot;label&amp;quot;: &amp;quot;napoje: $v&amp;quot;, &amp;quot;value&amp;quot;: 1, &amp;quot;color&amp;quot;:&amp;quot;lightblue&amp;quot;}&amp;#039;,&lt;br /&gt;
		&amp;#039;{&amp;quot;label&amp;quot;: &amp;quot;kanapki: $v&amp;quot;, &amp;quot;value&amp;quot;: 3, &amp;quot;color&amp;quot;:&amp;quot;wheat&amp;quot;}&amp;#039;&lt;br /&gt;
	}&lt;br /&gt;
	local json_data = &amp;#039;[&amp;#039;..table.concat(entries, &amp;#039;,&amp;#039;)..&amp;#039;]&amp;#039;&lt;br /&gt;
	local html = p.renderPie(json_data, &amp;#039;{&amp;quot;autoscale&amp;quot;:true}&amp;#039;)&lt;br /&gt;
	mw.logObject(html)&lt;br /&gt;
&lt;br /&gt;
	-- colors&lt;br /&gt;
	local fr = { args = { &amp;quot; 123 &amp;quot; } }&lt;br /&gt;
	local ret = p.color(fr)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
	Color for a slice (defaults).&lt;br /&gt;
&lt;br /&gt;
	{{{1}}}: slice number&lt;br /&gt;
]]&lt;br /&gt;
function p.color(frame)&lt;br /&gt;
	local index = tonumber(priv.trim(frame.args[1]))&lt;br /&gt;
	return &amp;#039; &amp;#039; .. priv.defaultColor(index)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[===[&lt;br /&gt;
	Main pie chart function.&lt;br /&gt;
	&lt;br /&gt;
	TODO (maybe):&lt;br /&gt;
	[[:en:User:Nux/pie_chart_-_todo#Hopes_and_dreams]]&lt;br /&gt;
]===]&lt;br /&gt;
function p.pie(frame)&lt;br /&gt;
	local json_data = priv.trim(frame.args[1])&lt;br /&gt;
	local options = {}&lt;br /&gt;
	if (frame.args.meta) then&lt;br /&gt;
		options.meta = priv.trim(frame.args.meta)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local html = p.renderPie(json_data, options)&lt;br /&gt;
	return priv.trim(html)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Setup chart options.&lt;br /&gt;
function p.setupOptions(user_options)&lt;br /&gt;
	local options = {&lt;br /&gt;
		-- circle size in [px]&lt;br /&gt;
		size = 100,&lt;br /&gt;
		-- autoscale values (otherwise assume they sum up to 100)&lt;br /&gt;
		autoscale = false,&lt;br /&gt;
		-- hide chart for screen readers (when you have a table, forced for legend)&lt;br /&gt;
		ariahidechart = false,&lt;br /&gt;
		-- show legend (defaults to the left side)&lt;br /&gt;
		legend = false,&lt;br /&gt;
		-- direction of legend-chart flexbox (flex-direction)&lt;br /&gt;
		direction = &amp;quot;&amp;quot;,&lt;br /&gt;
		-- width of the main container&lt;br /&gt;
		-- when direction is used defaults to max-width, otherwise it&amp;#039;s not added&lt;br /&gt;
		width = &amp;quot;&amp;quot;,&lt;br /&gt;
		-- caption above the labels&lt;br /&gt;
		caption = &amp;quot;&amp;quot;,&lt;br /&gt;
		-- footer below the labels&lt;br /&gt;
		footer = &amp;quot;&amp;quot;,&lt;br /&gt;
		-- formatting template for labels&lt;br /&gt;
		labelformat = &amp;quot;&amp;quot;,&lt;br /&gt;
		-- percentage number formatting precision (number of digits; -1 = automatic mode)&lt;br /&gt;
		precision = -1,&lt;br /&gt;
	}&lt;br /&gt;
	-- internals&lt;br /&gt;
	options.style = &amp;quot;&amp;quot;&lt;br /&gt;
	if user_options and user_options.meta then&lt;br /&gt;
		local decodeSuccess, rawOptions = pcall(function()&lt;br /&gt;
			return mw.text.jsonDecode(user_options.meta, mw.text.JSON_TRY_FIXING)&lt;br /&gt;
		end)&lt;br /&gt;
		if not decodeSuccess then&lt;br /&gt;
			rawOptions = false&lt;br /&gt;
			mw.log(&amp;#039;invalid meta parameters&amp;#039;)&lt;br /&gt;
		end&lt;br /&gt;
		if rawOptions then&lt;br /&gt;
			if type(rawOptions.size) == &amp;quot;number&amp;quot; then&lt;br /&gt;
				options.size = math.floor(rawOptions.size)&lt;br /&gt;
			end&lt;br /&gt;
			options.autoscale = rawOptions.autoscale or false &lt;br /&gt;
			if rawOptions.legend then&lt;br /&gt;
				options.legend = true&lt;br /&gt;
			end&lt;br /&gt;
			if rawOptions.ariahidechart then&lt;br /&gt;
				options.ariahidechart = true&lt;br /&gt;
			end&lt;br /&gt;
			if (type(rawOptions.direction) == &amp;quot;string&amp;quot;) then&lt;br /&gt;
				-- Remove unsafe/invalid characters&lt;br /&gt;
				local sanitized = rawOptions.direction:gsub(&amp;quot;[^a-z0-9%-]&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
				-- also adjust width so that row-reverse won&amp;#039;t push things to the right&lt;br /&gt;
				options.direction = &amp;#039;flex-direction: &amp;#039; .. sanitized .. &amp;#039;;&amp;#039;&lt;br /&gt;
				options.width = &amp;#039;width: max-content;&amp;#039;&lt;br /&gt;
			end&lt;br /&gt;
			if (type(rawOptions.width) == &amp;quot;string&amp;quot;) then&lt;br /&gt;
				-- note, this intentionaly overwrites what was set for direction&lt;br /&gt;
				local sanitized = rawOptions.width:gsub(&amp;quot;[^a-z0-9%-]&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
				options.width = &amp;#039;width: &amp;#039; .. sanitized .. &amp;#039;;&amp;#039;&lt;br /&gt;
			end&lt;br /&gt;
			if (type(rawOptions.caption) == &amp;quot;string&amp;quot;) then&lt;br /&gt;
				options.caption = rawOptions.caption&lt;br /&gt;
			end&lt;br /&gt;
			if (type(rawOptions.footer) == &amp;quot;string&amp;quot;) then&lt;br /&gt;
				options.footer = rawOptions.footer&lt;br /&gt;
			end&lt;br /&gt;
			if (type(rawOptions.labelformat) == &amp;quot;string&amp;quot;) then&lt;br /&gt;
				options.labelformat = rawOptions.labelformat&lt;br /&gt;
			end&lt;br /&gt;
			if type(rawOptions.precision) == &amp;quot;number&amp;quot; then&lt;br /&gt;
				options.precision = math.floor(rawOptions.precision)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- build style&lt;br /&gt;
		if options.width ~= &amp;quot;&amp;quot; then&lt;br /&gt;
			options.style = options.style .. options.width&lt;br /&gt;
		end&lt;br /&gt;
		if options.direction ~= &amp;quot;&amp;quot; then&lt;br /&gt;
			options.style = options.style .. options.direction&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if (options.legend) then&lt;br /&gt;
		options.ariahidechart = true&lt;br /&gt;
	end&lt;br /&gt;
	return options&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- internal for testing legend rendering&lt;br /&gt;
p.__priv.legendDebug = false&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
	Render piechart.&lt;br /&gt;
	&lt;br /&gt;
	@param json_data JSON string with pie data.&lt;br /&gt;
]]&lt;br /&gt;
function p.renderPie(json_data, user_options)&lt;br /&gt;
	if type(json_data) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
		error(&amp;#039;invalid piechart data type: &amp;#039;..type(json_data))&lt;br /&gt;
	end&lt;br /&gt;
	if #json_data &amp;lt; 2 then&lt;br /&gt;
		error(&amp;#039;piechart data is empty&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	local decodeSuccess, data = pcall(function()&lt;br /&gt;
		return mw.text.jsonDecode(json_data, mw.text.JSON_TRY_FIXING)&lt;br /&gt;
	end)&lt;br /&gt;
	-- Handle decode error&lt;br /&gt;
	if not decodeSuccess then&lt;br /&gt;
		error(&amp;#039;invalid piechart data: &amp;#039;..json_data)&lt;br /&gt;
	end&lt;br /&gt;
	local options = p.setupOptions(user_options)&lt;br /&gt;
&lt;br /&gt;
	-- prepare&lt;br /&gt;
	local ok, total = p.prepareEntries(data, options)&lt;br /&gt;
&lt;br /&gt;
	-- init render&lt;br /&gt;
	local html = &amp;quot;&amp;lt;div class=&amp;#039;smooth-pie-container&amp;#039; style=&amp;#039;&amp;quot;..options.style..&amp;quot;&amp;#039;&amp;gt;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	-- error info&lt;br /&gt;
	if not ok then&lt;br /&gt;
		html = html .. priv.renderErrors(data)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- render legend&lt;br /&gt;
	if options.legend then&lt;br /&gt;
		html = html .. p.renderLegend(data, options)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if p.__priv.legendDebug then&lt;br /&gt;
		return html&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- render items&lt;br /&gt;
	local header, items, footer = p.renderEntries(ok, total, data, options)&lt;br /&gt;
	html = html .. header .. items .. footer&lt;br /&gt;
&lt;br /&gt;
	-- end .smooth-pie-container&lt;br /&gt;
	html = html .. &amp;quot;\n&amp;lt;/div&amp;gt;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	return html&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function priv.boundaryFormatting(diff)&lt;br /&gt;
	local value = 0.0&lt;br /&gt;
	if diff &amp;lt;= 1.0 then&lt;br /&gt;
		value = math.ceil(diff / 0.2) * 0.2 -- 0.2 step&lt;br /&gt;
	else&lt;br /&gt;
		value = math.ceil(diff / 0.5) * 0.5 -- 0.5 step&lt;br /&gt;
	end&lt;br /&gt;
	return string.format(&amp;quot;%.1f&amp;quot;, value)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Check if sum will trigger autoscaling&lt;br /&gt;
function priv.willAutoscale(sum)&lt;br /&gt;
	-- Compare with a number larger then 100% to avoid floating-point precision problems&lt;br /&gt;
	--- ...and data precision problems https://en.wikipedia.org/wiki/Template_talk:Pie_chart#c-PrimeHunter-20250420202500-Allow_percentage_sum_slightly_above_100&lt;br /&gt;
	local diff = sum - 100&lt;br /&gt;
	local grace = 1&lt;br /&gt;
	return diff &amp;gt; grace&lt;br /&gt;
end&lt;br /&gt;
-- Tracking errors in data (note: somewhat expensive, similar to a red link)&lt;br /&gt;
-- In short: ±0.3 is a reasonable deviation; ±1 when the errors accumulate&lt;br /&gt;
-- https://en.wikipedia.org/wiki/Template_talk:Pie_chart#c-Nux-20250429152000-Nux-20250422224600&lt;br /&gt;
function priv.sumErrorTracking(sum, items)&lt;br /&gt;
	local diff = sum - 100&lt;br /&gt;
	if diff &amp;gt;= 0.4 and diff &amp;lt;= 10 then&lt;br /&gt;
		local firstItem = items[1]&lt;br /&gt;
		local lastItem = items[#items]&lt;br /&gt;
		mw.addWarning(&amp;quot;pie chart: Σ (value) = &amp;quot;..sum..&amp;quot;% (&amp;quot;..firstItem.value..&amp;quot; + .. .+ &amp;quot;..lastItem.value..&amp;quot;)&amp;quot;)&lt;br /&gt;
		if mw.title.getCurrentTitle().namespace == 0 then&lt;br /&gt;
			local suffix = priv.boundaryFormatting(diff) &lt;br /&gt;
			_ = mw.title.new(&amp;quot;Module:Piechart/tracing/diff below &amp;quot;..suffix).id&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Prepare data (slices etc)&lt;br /&gt;
function p.prepareEntries(data, options)&lt;br /&gt;
	local sum = priv.sumValues(data);&lt;br /&gt;
	-- force autoscale when over 100&lt;br /&gt;
	if priv.willAutoscale(sum) then&lt;br /&gt;
		options.autoscale = true&lt;br /&gt;
	end&lt;br /&gt;
	-- pre-format entries&lt;br /&gt;
	local ok = true&lt;br /&gt;
	local no = 0&lt;br /&gt;
	local total = #data&lt;br /&gt;
	for index, entry in ipairs(data) do&lt;br /&gt;
		no = no + 1&lt;br /&gt;
		if not priv.prepareSlice(entry, no, sum, total, options) then&lt;br /&gt;
			no = no - 1&lt;br /&gt;
			ok = false&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	total = no -- total valid&lt;br /&gt;
&lt;br /&gt;
	return ok, total&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function priv.sumValues(data)&lt;br /&gt;
	local sum = 0;&lt;br /&gt;
	for _, entry in ipairs(data) do&lt;br /&gt;
		local value = entry.value&lt;br /&gt;
		if not (type(value) ~= &amp;quot;number&amp;quot; or value &amp;lt; 0) then&lt;br /&gt;
			sum = sum + value&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return sum&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- render error info&lt;br /&gt;
function priv.renderErrors(data)&lt;br /&gt;
	local html = &amp;quot;\n&amp;lt;ol class=&amp;#039;chart-errors&amp;#039; style=&amp;#039;display:none&amp;#039;&amp;gt;&amp;quot;&lt;br /&gt;
	for _, entry in ipairs(data) do&lt;br /&gt;
		if entry.error then&lt;br /&gt;
			local entryJson = mw.text.jsonEncode(entry)&lt;br /&gt;
			html = html .. &amp;quot;\n&amp;lt;li&amp;gt;&amp;quot;.. entryJson ..&amp;quot;&amp;lt;/li&amp;gt;&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return html .. &amp;quot;\n&amp;lt;/ol&amp;gt;\n&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Prepare single slice data (modifies entry).&lt;br /&gt;
-- @param no = 1..total&lt;br /&gt;
function priv.prepareSlice(entry, no, sum, total, options)&lt;br /&gt;
	local autoscale = options.autoscale&lt;br /&gt;
	local value = entry.value&lt;br /&gt;
	if (type(value) ~= &amp;quot;number&amp;quot; or value &amp;lt; 0) then&lt;br /&gt;
		if autoscale then&lt;br /&gt;
			entry.error = &amp;quot;cannot autoscale unknown value&amp;quot;&lt;br /&gt;
			return false&lt;br /&gt;
		end&lt;br /&gt;
		value = 100 - sum&lt;br /&gt;
	end&lt;br /&gt;
	-- entry.raw only when scaled&lt;br /&gt;
	if autoscale then&lt;br /&gt;
		entry.raw = value&lt;br /&gt;
		value = (value / sum) * 100&lt;br /&gt;
	end&lt;br /&gt;
	entry.value = value&lt;br /&gt;
&lt;br /&gt;
	-- prepare final label&lt;br /&gt;
	entry.label = priv.prepareLabel(options.labelformat, entry, options.precision)&lt;br /&gt;
	-- background, but also color for MW syntax linter&lt;br /&gt;
	entry.bcolor = priv.backColor(entry, no, total) .. &amp;quot;;color:#000&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- render legend for pre-processed entries&lt;br /&gt;
function p.renderLegend(data, options)&lt;br /&gt;
	local html = &amp;quot;&amp;quot;&lt;br /&gt;
	if options.caption ~= &amp;quot;&amp;quot; or options.footer ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		html = &amp;quot;\n&amp;lt;div class=&amp;#039;smooth-pie-legend-container&amp;#039;&amp;gt;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
	if options.caption ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		html = html .. &amp;quot;&amp;lt;div class=&amp;#039;smooth-pie-caption&amp;#039;&amp;gt;&amp;quot; .. options.caption .. &amp;quot;&amp;lt;/div&amp;gt;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
	html = html .. &amp;quot;\n&amp;lt;ol class=&amp;#039;smooth-pie-legend&amp;#039;&amp;gt;&amp;quot;&lt;br /&gt;
	for _, entry in ipairs(data) do&lt;br /&gt;
		if not entry.error then&lt;br /&gt;
			html = html .. priv.renderLegendItem(entry, options)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	html = html .. &amp;quot;\n&amp;lt;/ol&amp;gt;\n&amp;quot;&lt;br /&gt;
	if options.footer ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		html = html .. &amp;quot;&amp;lt;div class=&amp;#039;smooth-pie-footer&amp;#039;&amp;gt;&amp;quot; .. options.footer .. &amp;quot;&amp;lt;/div&amp;gt;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
	if options.caption ~= &amp;quot;&amp;quot; or options.footer ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		html = html .. &amp;quot;&amp;lt;/div&amp;gt;\n&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
	return html&lt;br /&gt;
end&lt;br /&gt;
-- render legend item&lt;br /&gt;
function priv.renderLegendItem(entry, options)&lt;br /&gt;
	-- invisible value (for a11y reasons this should not be used for important values!)&lt;br /&gt;
	if entry.visible ~= nil and entry.visible == false then&lt;br /&gt;
		return &amp;quot;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local label = entry.label&lt;br /&gt;
	local bcolor = entry.bcolor&lt;br /&gt;
	local html = &amp;quot;\n&amp;lt;li&amp;gt;&amp;quot;&lt;br /&gt;
	if p.__priv.legendDebug then&lt;br /&gt;
		forPrinting = &amp;quot;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
	html = html .. &amp;#039;&amp;lt;span class=&amp;quot;l-color&amp;quot; style=&amp;quot;&amp;#039;..forPrinting..bcolor..&amp;#039;&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;#039;&lt;br /&gt;
	html = html .. &amp;#039;&amp;lt;span class=&amp;quot;l-label&amp;quot;&amp;gt;&amp;#039;..label..&amp;#039;&amp;lt;/span&amp;gt;&amp;#039;&lt;br /&gt;
	return html .. &amp;quot;&amp;lt;/li&amp;gt;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Prepare data (slices etc)&lt;br /&gt;
function p.renderEntries(ok, total, data, options)&lt;br /&gt;
	-- cache for some items (small slices)&lt;br /&gt;
	p.cuts = mw.loadJsonData(&amp;#039;Module:Piechart/cuts.json&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
	local first = true&lt;br /&gt;
	local previous = 0&lt;br /&gt;
	local items = &amp;quot;&amp;quot;&lt;br /&gt;
	for index, entry in ipairs(data) do&lt;br /&gt;
		if not entry.error then&lt;br /&gt;
			items = items .. priv.renderItem(previous, entry, options)&lt;br /&gt;
			previous = previous + entry.value&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local header = priv.renderHeader(options)&lt;br /&gt;
	local footer = &amp;#039;\n&amp;lt;div class=&amp;quot;smooth-pie-border&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
	return header, items, footer&lt;br /&gt;
end&lt;br /&gt;
-- header of pie-items (class=&amp;quot;smooth-pie&amp;quot;)&lt;br /&gt;
function priv.renderHeader(options)&lt;br /&gt;
	local bcolor = &amp;#039;background:#888;color:#000&amp;#039;&lt;br /&gt;
	local size = options.size&lt;br /&gt;
&lt;br /&gt;
	-- hide chart for readers, especially when legend is there&lt;br /&gt;
	local aria = &amp;quot;&amp;quot;&lt;br /&gt;
	if (options.ariahidechart) then&lt;br /&gt;
		aria = &amp;#039;aria-hidden=&amp;quot;true&amp;quot;&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- slices container and last slice&lt;br /&gt;
	local style = &amp;#039;width:&amp;#039;..size..&amp;#039;px;height:&amp;#039;..size..&amp;#039;px;&amp;#039;..bcolor..&amp;#039;;&amp;#039;..forPrinting&lt;br /&gt;
	local html = [[&lt;br /&gt;
&amp;lt;div class=&amp;quot;smooth-pie&amp;quot;&lt;br /&gt;
	style=&amp;quot;]]..style..[[&amp;quot;&lt;br /&gt;
	]]..aria..[[&lt;br /&gt;
&amp;gt;]]&lt;br /&gt;
	return html&lt;br /&gt;
end&lt;br /&gt;
-- Render pie-item&lt;br /&gt;
-- (previous is a sum of previous values)&lt;br /&gt;
function priv.renderItem(previous, entry, options)&lt;br /&gt;
	local value = entry.value&lt;br /&gt;
	local label = entry.label&lt;br /&gt;
	local bcolor = entry.bcolor&lt;br /&gt;
&lt;br /&gt;
	-- value too small to see&lt;br /&gt;
	if (value &amp;lt; 0.03) then&lt;br /&gt;
		mw.log(&amp;#039;value too small&amp;#039;, value, label)&lt;br /&gt;
		mw.addWarning(&amp;quot;pie chart: Value too small ↆ &amp;quot;..value..&amp;quot;% (&amp;quot;..label..&amp;quot;)&amp;quot;)&lt;br /&gt;
		return &amp;quot;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- minimize transformation defects&lt;br /&gt;
	if value &amp;lt; 10 then&lt;br /&gt;
		if previous &amp;gt; 1 then&lt;br /&gt;
			previous = previous - 0.01&lt;br /&gt;
		end&lt;br /&gt;
		value = value + 0.02&lt;br /&gt;
	else&lt;br /&gt;
		if previous &amp;gt; 1 then&lt;br /&gt;
			previous = previous - 0.1&lt;br /&gt;
		end&lt;br /&gt;
		value = value + 0.2&lt;br /&gt;
	end&lt;br /&gt;
	-- force sum to be below 100% (needed due to value errors)&lt;br /&gt;
	if previous + value &amp;gt; 100 then&lt;br /&gt;
		if previous &amp;gt;= 100 then&lt;br /&gt;
			mw.log(&amp;#039;previous is already at 100&amp;#039;, value, label)&lt;br /&gt;
			return &amp;quot;&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		value = 100 - previous&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local html =  &amp;quot;&amp;quot;&lt;br /&gt;
	&lt;br /&gt;
	local size = &amp;#039;&amp;#039;&lt;br /&gt;
	-- mw.logObject({&amp;#039;v,p,l&amp;#039;, value, previous, label})&lt;br /&gt;
	if (value &amp;gt;= 50) then&lt;br /&gt;
		html = priv.sliceWithClass(&amp;#039;pie50&amp;#039;, 50, value, previous, bcolor, label)&lt;br /&gt;
	elseif (value &amp;gt;= 25) then&lt;br /&gt;
		html = priv.sliceWithClass(&amp;#039;pie25&amp;#039;, 25, value, previous, bcolor, label)&lt;br /&gt;
	elseif (value &amp;gt;= 12.5) then&lt;br /&gt;
		html = priv.sliceWithClass(&amp;#039;pie12-5&amp;#039;, 12.5, value, previous, bcolor, label)&lt;br /&gt;
	elseif (value &amp;gt;= 7) then&lt;br /&gt;
		html = priv.sliceWithClass(&amp;#039;pie7&amp;#039;, 7, value, previous, bcolor, label)&lt;br /&gt;
	elseif (value &amp;gt;= 5) then&lt;br /&gt;
		html = priv.sliceWithClass(&amp;#039;pie5&amp;#039;, 5, value, previous, bcolor, label)&lt;br /&gt;
	else&lt;br /&gt;
		-- 0-5%&lt;br /&gt;
		local cutIndex = priv.round(value*10)&lt;br /&gt;
		if cutIndex &amp;lt; 1 then&lt;br /&gt;
			cutIndex = 1&lt;br /&gt;
		end&lt;br /&gt;
		local cut = p.cuts[cutIndex]&lt;br /&gt;
		local transform = priv.rotation(previous)&lt;br /&gt;
		html = priv.sliceX(cut, transform, bcolor, label)&lt;br /&gt;
	end	&lt;br /&gt;
	-- mw.log(html)&lt;br /&gt;
&lt;br /&gt;
	return html&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- round to int&lt;br /&gt;
function priv.round(number)&lt;br /&gt;
	return math.floor(number + 0.5)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- render full slice with specific class&lt;br /&gt;
function priv.sliceWithClass(sizeClass, sizeStep, value, previous, bcolor, label)&lt;br /&gt;
	local transform = priv.rotation(previous)&lt;br /&gt;
	local html =  &amp;quot;&amp;quot;&lt;br /&gt;
	html = html .. priv.sliceBase(sizeClass, transform, bcolor, label)&lt;br /&gt;
	-- mw.logObject({&amp;#039;sliceWithClass:&amp;#039;, sizeClass, sizeStep, value, previous, bcolor, label})&lt;br /&gt;
	if (value &amp;gt; sizeStep) then&lt;br /&gt;
		local extra = value - sizeStep&lt;br /&gt;
		transform = priv.rotation(previous + extra)&lt;br /&gt;
		-- mw.logObject({&amp;#039;sliceWithClass; extra, transform&amp;#039;, extra, transform})&lt;br /&gt;
		html = html .. priv.sliceBase(sizeClass, transform, bcolor, label)&lt;br /&gt;
	end&lt;br /&gt;
	return html&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- render single slice&lt;br /&gt;
function priv.sliceBase(sizeClass, transform, bcolor, label)&lt;br /&gt;
	local style = bcolor&lt;br /&gt;
	if transform ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		style = style .. &amp;#039;; &amp;#039; .. transform&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;#039;\n\t&amp;lt;div class=&amp;quot;&amp;#039;..sizeClass..&amp;#039;&amp;quot; style=&amp;quot;&amp;#039;..style..&amp;#039;&amp;quot; title=&amp;quot;&amp;#039;..p.extract_text(label)..&amp;#039;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- small slice cut to fluid size.&lt;br /&gt;
-- range in theory: 0 to 24.(9)% reaching 24.(9)% for cut = +inf&lt;br /&gt;
-- range in practice: 0 to 5%&lt;br /&gt;
function priv.sliceX(cut, transform, bcolor, label)&lt;br /&gt;
	local path = &amp;#039;clip-path: polygon(0% 0%, &amp;#039;..cut..&amp;#039;% 0%, 0 100%)&amp;#039;&lt;br /&gt;
	return &amp;#039;\n\t&amp;lt;div style=&amp;quot;&amp;#039;..transform..&amp;#039;; &amp;#039;..bcolor..&amp;#039;; &amp;#039;..path..&amp;#039;&amp;quot; title=&amp;quot;&amp;#039;..p.extract_text(label)..&amp;#039;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- translate value to turn rotation (v=100 =&amp;gt; 1.0turn)&lt;br /&gt;
function priv.rotation(value)&lt;br /&gt;
	if (value &amp;gt; 0.001) then&lt;br /&gt;
		local f = string.format(&amp;quot;%.7f&amp;quot;, value / 100)&lt;br /&gt;
		f = f:gsub(&amp;quot;(%d)0+$&amp;quot;, &amp;quot;%1&amp;quot;) -- remove trailing zeros&lt;br /&gt;
		return &amp;quot;transform: rotate(&amp;quot;..f..&amp;quot;turn)&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;#039;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Language sensitive float, small numbers.&lt;br /&gt;
function priv.formatNum(value, precision)&lt;br /&gt;
	precision = precision or -1&lt;br /&gt;
&lt;br /&gt;
	local v = &amp;quot;&amp;quot;&lt;br /&gt;
	if precision &amp;gt;= 0 then&lt;br /&gt;
		-- custom format&lt;br /&gt;
		v = string.format(&amp;quot;%.&amp;quot; .. precision .. &amp;quot;f&amp;quot;, value)&lt;br /&gt;
	else&lt;br /&gt;
		-- automatic&lt;br /&gt;
		if (value &amp;lt; 10) then&lt;br /&gt;
			v = string.format(&amp;quot;%.2f&amp;quot;, value)&lt;br /&gt;
		else&lt;br /&gt;
			v = string.format(&amp;quot;%.1f&amp;quot;, value)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local lang = mw.language.getContentLanguage()&lt;br /&gt;
	if (lang:getCode() == &amp;#039;pl&amp;#039;) then&lt;br /&gt;
		v = v:gsub(&amp;quot;%.&amp;quot;, &amp;quot;,&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Format large values.&lt;br /&gt;
function priv.formatLargeNum(value)&lt;br /&gt;
	local lang = mw.language.getContentLanguage()&lt;br /&gt;
	-- add thusands separators&lt;br /&gt;
	local v = lang:formatNum(value)&lt;br /&gt;
	return v&lt;br /&gt;
end&lt;br /&gt;
-- Testing formatLargeNum.&lt;br /&gt;
-- p.__priv.test_formatLargeNum()&lt;br /&gt;
function priv.test_formatLargeNum()&lt;br /&gt;
	mw.log(&amp;quot;must not add fractional part&amp;quot;)&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(12) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(123) )&lt;br /&gt;
&lt;br /&gt;
	mw.log(&amp;quot;should preserve fractional part for small numbers&amp;quot;)&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(1.1) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(1.12) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(12.1) )&lt;br /&gt;
	mw.log(&amp;quot;can preserve long fractional part&amp;quot;)&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(1.1234) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(1.12345) )&lt;br /&gt;
	&lt;br /&gt;
	mw.log(&amp;quot;should add separators above 1k&amp;quot;)&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(999) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(1234) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(12345) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(123456) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(1234567) )&lt;br /&gt;
&lt;br /&gt;
	mw.log(&amp;quot;must handle large float, but might round values&amp;quot;)&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(1234.123) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(12345.123) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(123456.123) )&lt;br /&gt;
	mw.log( p.__priv.formatLargeNum(1234567.123) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
	Prepare final label.&lt;br /&gt;
&lt;br /&gt;
	Typical tpl:&lt;br /&gt;
		&amp;quot;$L: $v&amp;quot; (same as: &amp;quot;$label: $auto&amp;quot;)&lt;br /&gt;
	will result in:&lt;br /&gt;
		&amp;quot;Abc: 23%&amp;quot; -- when values are percentages&lt;br /&gt;
		&amp;quot;Abc: 1234 (23%)&amp;quot; -- when values are autoscaled&lt;br /&gt;
	&lt;br /&gt;
	Advanced tpl:&lt;br /&gt;
		&amp;quot;$L: $d ($p)&amp;quot; e.g. &amp;quot;Abc: 1234 (23%)&amp;quot; for {&amp;quot;label&amp;quot;:&amp;quot;Abc&amp;quot;, &amp;quot;value&amp;quot;:1234}&lt;br /&gt;
		&amp;quot;$L: $v&amp;quot; is the same as above when values are autoscaled&lt;br /&gt;
	&lt;br /&gt;
	Long vs short variable names:&lt;br /&gt;
		$label ($L)&lt;br /&gt;
		$auto ($v)&lt;br /&gt;
		$value ($d)&lt;br /&gt;
		$percent ($p)&lt;br /&gt;
]]&lt;br /&gt;
function priv.prepareLabel(tpl, entry, precision)&lt;br /&gt;
	-- setup default tpl&lt;br /&gt;
	if not tpl or tpl == &amp;quot;&amp;quot; then&lt;br /&gt;
		-- simple if no label&lt;br /&gt;
		if not entry.label or entry.label == &amp;quot;&amp;quot; then&lt;br /&gt;
			tpl = &amp;quot;$v&amp;quot;&lt;br /&gt;
		else&lt;br /&gt;
			if entry.label:find(&amp;quot;%$[a-z]&amp;quot;) then&lt;br /&gt;
				tpl = entry.label&lt;br /&gt;
			else&lt;br /&gt;
				tpl = &amp;quot;$L: $v&amp;quot;&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local labelLabel = entry.label and entry.label or priv.getLangOther()&lt;br /&gt;
&lt;br /&gt;
	-- format % value without %&lt;br /&gt;
	local pRaw = priv.formatNum(entry.value, precision)&lt;br /&gt;
	local pp = pRaw .. &amp;quot;%%&amp;quot;&lt;br /&gt;
	&lt;br /&gt;
	local label = tpl&lt;br /&gt;
	-- aliases&lt;br /&gt;
	label = label&lt;br /&gt;
		:gsub(&amp;quot;%$label&amp;quot;, &amp;quot;$L&amp;quot;)&lt;br /&gt;
		:gsub(&amp;quot;%$auto&amp;quot;, &amp;quot;$v&amp;quot;)&lt;br /&gt;
		:gsub(&amp;quot;%$value&amp;quot;, &amp;quot;$d&amp;quot;)&lt;br /&gt;
		:gsub(&amp;quot;%$percent&amp;quot;, &amp;quot;$p&amp;quot;)&lt;br /&gt;
	-- replace variables&lt;br /&gt;
	label = label:gsub(&amp;quot;%$L&amp;quot;, labelLabel)&lt;br /&gt;
	local d = priv.formatLargeNum(entry.raw and entry.raw or entry.value)&lt;br /&gt;
	local v = entry.raw and (d .. &amp;quot; (&amp;quot; .. pp .. &amp;quot;)&amp;quot;) or pp&lt;br /&gt;
	label = label&lt;br /&gt;
		:gsub(&amp;quot;%$p&amp;quot;, pp)&lt;br /&gt;
		:gsub(&amp;quot;%$d&amp;quot;, d)&lt;br /&gt;
		:gsub(&amp;quot;%$v&amp;quot;, v)&lt;br /&gt;
&lt;br /&gt;
	-- Report unknown variables&lt;br /&gt;
	for var in label:gmatch(&amp;quot;%$[a-zA-Z]+&amp;quot;) do&lt;br /&gt;
		-- in preview&lt;br /&gt;
		mw.addWarning(&amp;quot;pie chart: Unknown variable (wrong format of your label): &amp;quot; .. var)&lt;br /&gt;
		-- tracing links&lt;br /&gt;
		_ = mw.title.new(&amp;quot;Module:Piechart/tracing/unknown-variable&amp;quot;).id&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return label&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- default colors&lt;br /&gt;
-- source: https://colorbrewer2.org/#type=diverging&amp;amp;scheme=PRGn&amp;amp;n=6&lt;br /&gt;
local colorGroupSize = 3 -- must be at least 3&lt;br /&gt;
local colorGroups = 4&lt;br /&gt;
local colorPalette = {&lt;br /&gt;
-- green (from dark)&lt;br /&gt;
&amp;#039;#1b7837&amp;#039;,&lt;br /&gt;
&amp;#039;#7fbf7b&amp;#039;,&lt;br /&gt;
&amp;#039;#d9f0d3&amp;#039;,&lt;br /&gt;
-- violet&lt;br /&gt;
&amp;#039;#762a83&amp;#039;,&lt;br /&gt;
&amp;#039;#af8dc3&amp;#039;,&lt;br /&gt;
&amp;#039;#e7d4e8&amp;#039;,&lt;br /&gt;
-- red&lt;br /&gt;
&amp;#039;#d73027&amp;#039;,&lt;br /&gt;
&amp;#039;#fc8d59&amp;#039;,&lt;br /&gt;
&amp;#039;#fee090&amp;#039;,&lt;br /&gt;
-- blue&lt;br /&gt;
&amp;#039;#4575b4&amp;#039;,&lt;br /&gt;
&amp;#039;#91bfdb&amp;#039;,&lt;br /&gt;
&amp;#039;#e0f3f8&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
local lastColor = &amp;#039;#fff&amp;#039;&lt;br /&gt;
-- background color from entry or the default colors&lt;br /&gt;
function priv.backColor(entry, no, total)&lt;br /&gt;
	if (type(entry.color) == &amp;quot;string&amp;quot;) then&lt;br /&gt;
		-- Remove unsafe characters from entry.color&lt;br /&gt;
		local sanitizedColor = entry.color&lt;br /&gt;
			:gsub(&amp;#039;&amp;amp;#35;&amp;#039;, &amp;#039;#&amp;#039;)  -- workaround Module:Political_party issue reported on talk&lt;br /&gt;
			:gsub(&amp;quot;[^a-zA-Z0-9#%-]&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
		return &amp;#039;background:&amp;#039; .. sanitizedColor&lt;br /&gt;
	else&lt;br /&gt;
		local color = priv.defaultColor(no, total)&lt;br /&gt;
		return &amp;#039;background:&amp;#039; .. color&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
-- color from the default colors&lt;br /&gt;
function priv.defaultColor(no, total)&lt;br /&gt;
	local color = lastColor&lt;br /&gt;
	if no &amp;lt;= 0 then&lt;br /&gt;
		return color&lt;br /&gt;
	end&lt;br /&gt;
	local size = #colorPalette&lt;br /&gt;
	if not total or total == 0 then&lt;br /&gt;
		total = size + 1&lt;br /&gt;
	end&lt;br /&gt;
	local colorNo = priv.defaultColorNo(no, total, size)&lt;br /&gt;
	if colorNo &amp;gt; 0 then&lt;br /&gt;
		color = colorPalette[colorNo]&lt;br /&gt;
	end&lt;br /&gt;
	return color&lt;br /&gt;
end&lt;br /&gt;
-- gets color number from default colors&lt;br /&gt;
-- trys to return a light color as the last one&lt;br /&gt;
-- 0 means white-ish color should be used&lt;br /&gt;
function priv.defaultColorNo(no, total, size)&lt;br /&gt;
	local color = 0 -- special, lastColor&lt;br /&gt;
	if total == 1 then&lt;br /&gt;
		color = 1&lt;br /&gt;
	elseif total &amp;lt;= colorGroupSize * (colorGroups - 1) then&lt;br /&gt;
		if no &amp;lt; total then&lt;br /&gt;
			color = no&lt;br /&gt;
		else&lt;br /&gt;
			local groupIndex = ((no - 1) % colorGroupSize)&lt;br /&gt;
			if groupIndex == 0 then -- dark&lt;br /&gt;
				color = no+1&lt;br /&gt;
			elseif groupIndex == 1 then -- med&lt;br /&gt;
				color = no+1&lt;br /&gt;
			else&lt;br /&gt;
				color = no&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	elseif no &amp;lt; total then&lt;br /&gt;
		color = ((no - 1) % size) + 1&lt;br /&gt;
	end&lt;br /&gt;
	return color&lt;br /&gt;
end&lt;br /&gt;
--[[&lt;br /&gt;
	Testing defaultColorNo:&lt;br /&gt;
	p.__priv.test_defaultColorNo(1, 12)&lt;br /&gt;
	p.__priv.test_defaultColorNo(2, 12)&lt;br /&gt;
	p.__priv.test_defaultColorNo(3, 12)&lt;br /&gt;
	p.__priv.test_defaultColorNo(4, 12)&lt;br /&gt;
	p.__priv.test_defaultColorNo(5, 12)&lt;br /&gt;
	p.__priv.test_defaultColorNo(6, 12)&lt;br /&gt;
]]&lt;br /&gt;
function priv.test_defaultColorNo(total, size)&lt;br /&gt;
	for no=1,total do&lt;br /&gt;
		local color = priv.defaultColorNo(no, total, size)&lt;br /&gt;
		mw.logObject({no=no, color=color})&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
	trim string&lt;br /&gt;
	&lt;br /&gt;
	note:&lt;br /&gt;
	`(s:gsub(...))` returns only a string&lt;br /&gt;
	`s:gsub(...)` returns a string and a number&lt;br /&gt;
]]&lt;br /&gt;
function priv.trim(s)&lt;br /&gt;
	return (s:gsub(&amp;quot;^%s+&amp;quot;, &amp;quot;&amp;quot;):gsub(&amp;quot;%s+$&amp;quot;, &amp;quot;&amp;quot;))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
  Extract text from simple wikitext.&lt;br /&gt;
  &lt;br /&gt;
  For now only works with links.&lt;br /&gt;
]]&lt;br /&gt;
-- Tests:&lt;br /&gt;
-- mw.log(p.extract_text(&amp;quot;[[candy|sweets]]: $v&amp;quot;))&lt;br /&gt;
-- mw.log(p.extract_text(&amp;quot;[[sandwich]]es: $v&amp;quot;))&lt;br /&gt;
-- mw.log(p.extract_text(&amp;quot;sandwich]]es: $v&amp;quot;))&lt;br /&gt;
-- mw.log(p.extract_text(&amp;quot;sandwiches: $v&amp;quot;))&lt;br /&gt;
function p.extract_text(label)&lt;br /&gt;
	label = label&lt;br /&gt;
		-- replace links with pipe (e.g., [[candy|sweets]])&lt;br /&gt;
		:gsub(&amp;quot;%[%[[^|%]]+|(.-)%]%]&amp;quot;, &amp;quot;%1&amp;quot;)&lt;br /&gt;
		-- replace simple links without pipe (e.g., [[sandwich]])&lt;br /&gt;
		:gsub(&amp;quot;%[%[(.-)%]%]&amp;quot;, &amp;quot;%1&amp;quot;)&lt;br /&gt;
		-- remove templates?&lt;br /&gt;
		-- :gsub(&amp;quot;{.-}&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
		-- remove tags&lt;br /&gt;
		:gsub(&amp;quot;&amp;lt;[^&amp;gt;]+&amp;gt;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
		-- escape special chars just in case&lt;br /&gt;
		:gsub(&amp;quot;&amp;lt;&amp;quot;, &amp;quot;&amp;amp;lt;&amp;quot;):gsub(&amp;quot;&amp;gt;&amp;quot;, &amp;quot;&amp;amp;gt;&amp;quot;)&lt;br /&gt;
		:gsub(&amp;quot;&amp;#039;&amp;quot;, &amp;quot;&amp;amp;#39;&amp;quot;):gsub(&amp;quot;\&amp;quot;&amp;quot;, &amp;quot;&amp;amp;quot;&amp;quot;)&lt;br /&gt;
	return label&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
  Parse classic template params into JSON.&lt;br /&gt;
&lt;br /&gt;
From:  &lt;br /&gt;
|label1=cookies: $v |value1=11 |color1=goldenrod&lt;br /&gt;
|label2=sweets: $v |value2=20 |color2=darkred&lt;br /&gt;
&lt;br /&gt;
To:&lt;br /&gt;
{&amp;quot;value&amp;quot;:11,&amp;quot;color&amp;quot;:&amp;quot;goldenrod&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;cookies: $v&amp;quot;},&lt;br /&gt;
{&amp;quot;value&amp;quot;:20,&amp;quot;color&amp;quot;:&amp;quot;darkred&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;sweets: $v&amp;quot;},&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
function p.parseEnumParams(frame)&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	return priv.parseEnumParams(args)&lt;br /&gt;
end&lt;br /&gt;
function priv.parseEnumParams(args)&lt;br /&gt;
	local result = {}&lt;br /&gt;
	&lt;br /&gt;
	local i = 1&lt;br /&gt;
	local sum = 0.0&lt;br /&gt;
	local hasCustomColor = false -- has last custom color&lt;br /&gt;
	while args[&amp;quot;value&amp;quot; .. i] do&lt;br /&gt;
		-- value is required in this mode; it&amp;#039;s also assumed to be 0..100&lt;br /&gt;
		local entry = { value = tonumber(args[&amp;quot;value&amp;quot; .. i]) or 0 }&lt;br /&gt;
		-- label and color is optional&lt;br /&gt;
		local label = args[&amp;quot;label&amp;quot; .. i]&lt;br /&gt;
		if label and label ~= &amp;quot;&amp;quot; then&lt;br /&gt;
			entry.label = label&lt;br /&gt;
		end&lt;br /&gt;
		hasCustomColor = false&lt;br /&gt;
		local color = args[&amp;quot;color&amp;quot; .. i]&lt;br /&gt;
		if color and color ~= &amp;quot;&amp;quot; then&lt;br /&gt;
			entry.color = color&lt;br /&gt;
			hasCustomColor = true&lt;br /&gt;
		end&lt;br /&gt;
		table.insert(result, entry)&lt;br /&gt;
		sum = sum + entry.value&lt;br /&gt;
		i = i + 1&lt;br /&gt;
	end&lt;br /&gt;
	-- re-loop to set values in labels&lt;br /&gt;
	local willAutoscale = priv.willAutoscale(sum)&lt;br /&gt;
	for _, entry in ipairs(result) do&lt;br /&gt;
		local label = entry.label&lt;br /&gt;
		if label and not label:find(&amp;quot;%$[a-z]&amp;quot;) then&lt;br /&gt;
			-- autoscale will be forced, so use $v in labels&lt;br /&gt;
			if willAutoscale then&lt;br /&gt;
				entry.label = label .. &amp;quot; $v&amp;quot;&lt;br /&gt;
			else&lt;br /&gt;
				entry.label = label .. &amp;quot; ($p)&amp;quot;&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- tracking data errors&lt;br /&gt;
	priv.sumErrorTracking(sum, result)&lt;br /&gt;
&lt;br /&gt;
	-- support other value mapping&lt;br /&gt;
	local langOther = priv.getLangOther()&lt;br /&gt;
	local colorOther = &amp;quot;#FEFDFD&amp;quot; -- white-ish for custom colors for best chance and contrast&lt;br /&gt;
	&lt;br /&gt;
	local otherValue = 100 - sum&lt;br /&gt;
	if args[&amp;quot;other&amp;quot;] and args[&amp;quot;other&amp;quot;] ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		if otherValue &amp;lt; 0.001 then&lt;br /&gt;
			otherValue = 0&lt;br /&gt;
		end&lt;br /&gt;
		local otherEntry = { label = (args[&amp;quot;other-label&amp;quot;] or langOther) .. &amp;quot; ($p)&amp;quot; }&lt;br /&gt;
		if args[&amp;quot;other-color&amp;quot;] and args[&amp;quot;other-color&amp;quot;] ~= &amp;quot;&amp;quot; then&lt;br /&gt;
			otherEntry.color = args[&amp;quot;other-color&amp;quot;]&lt;br /&gt;
		else&lt;br /&gt;
			otherEntry.color = colorOther&lt;br /&gt;
		end&lt;br /&gt;
		table.insert(result, otherEntry)&lt;br /&gt;
	elseif otherValue &amp;gt; 0.01 then&lt;br /&gt;
		if hasCustomColor then&lt;br /&gt;
			table.insert(result, {visible = false, label = langOther .. &amp;quot; ($v)&amp;quot;, color = colorOther})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(result, {visible = false, label = langOther .. &amp;quot; ($v)&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local jsonString = mw.text.jsonEncode(result)&lt;br /&gt;
	return jsonString&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function priv.getLangOther()&lt;br /&gt;
	-- support other value mapping&lt;br /&gt;
	local lang = mw.language.getContentLanguage()&lt;br /&gt;
	if (lang:getCode() == &amp;#039;pl&amp;#039;) then&lt;br /&gt;
		return &amp;quot;Inne&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Other&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Function to check if a value is true-ish&lt;br /&gt;
local trueValues = { [&amp;quot;true&amp;quot;] = true, [&amp;quot;1&amp;quot;] = true, [&amp;quot;on&amp;quot;] = true, [&amp;quot;yes&amp;quot;] = true }&lt;br /&gt;
function priv.isTrueishValue(value)&lt;br /&gt;
	-- should return nil for empty args (i.e. undefined i.e. default)&lt;br /&gt;
	if not value or value == &amp;quot;&amp;quot; then return nil end&lt;br /&gt;
	value = priv.trim(value)&lt;br /&gt;
	if value == &amp;quot;&amp;quot; then return nil end&lt;br /&gt;
	-- other non-empty are false&lt;br /&gt;
	return trueValues[value:lower()] or false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
  Parse classic template params into JSON with chart meta data.&lt;br /&gt;
]]&lt;br /&gt;
function p.parseMetaParams(frame)&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	local meta = {}&lt;br /&gt;
&lt;br /&gt;
	-- default meta for value1..n parameters&lt;br /&gt;
	-- ...and for thumb right/left&lt;br /&gt;
	local thumb = args[&amp;quot;thumb&amp;quot;]&lt;br /&gt;
	if args[&amp;quot;value1&amp;quot;] or (thumb and (thumb == &amp;quot;right&amp;quot; or thumb == &amp;quot;left&amp;quot;)) then&lt;br /&gt;
		meta.size = 200&lt;br /&gt;
		meta.legend = true&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- explicit meta param&lt;br /&gt;
	if args[&amp;quot;meta&amp;quot;] then&lt;br /&gt;
		local decodeSuccess, tempMeta = pcall(function()&lt;br /&gt;
			return mw.text.jsonDecode(args[&amp;quot;meta&amp;quot;], mw.text.JSON_TRY_FIXING)&lt;br /&gt;
		end)&lt;br /&gt;
		if not decodeSuccess then&lt;br /&gt;
			mw.log(&amp;#039;invalid meta parameter&amp;#039;)&lt;br /&gt;
		else&lt;br /&gt;
			meta = tempMeta&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	if args[&amp;quot;size&amp;quot;] then meta.size = tonumber(args[&amp;quot;size&amp;quot;]) end&lt;br /&gt;
	if args[&amp;quot;radius&amp;quot;] and tonumber(args[&amp;quot;radius&amp;quot;]) then&lt;br /&gt;
		meta.size = 2 * tonumber(args[&amp;quot;radius&amp;quot;])&lt;br /&gt;
	end&lt;br /&gt;
	if args[&amp;quot;autoscale&amp;quot;] then meta.autoscale = priv.isTrueishValue(args[&amp;quot;autoscale&amp;quot;]) end&lt;br /&gt;
	if args[&amp;quot;legend&amp;quot;] then meta.legend = priv.isTrueishValue(args[&amp;quot;legend&amp;quot;]) end&lt;br /&gt;
	if args[&amp;quot;ariahidechart&amp;quot;] then meta.ariahidechart = priv.isTrueishValue(args[&amp;quot;ariahidechart&amp;quot;]) end&lt;br /&gt;
	if args[&amp;quot;direction&amp;quot;] and args[&amp;quot;direction&amp;quot;] ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		meta.direction = args[&amp;quot;direction&amp;quot;]:gsub(&amp;quot;[^a-z0-9%-]&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	if args[&amp;quot;width&amp;quot;] and args[&amp;quot;width&amp;quot;] ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		meta.width = args[&amp;quot;width&amp;quot;]:gsub(&amp;quot;[^a-z0-9%-]&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	if args[&amp;quot;caption&amp;quot;] and args[&amp;quot;caption&amp;quot;] ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		meta.caption = args[&amp;quot;caption&amp;quot;]&lt;br /&gt;
	end&lt;br /&gt;
	if args[&amp;quot;footer&amp;quot;] and args[&amp;quot;footer&amp;quot;] ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		meta.footer = args[&amp;quot;footer&amp;quot;]&lt;br /&gt;
	end&lt;br /&gt;
	if args[&amp;quot;labelformat&amp;quot;] and args[&amp;quot;labelformat&amp;quot;] ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		meta.labelformat = args[&amp;quot;labelformat&amp;quot;]&lt;br /&gt;
	end&lt;br /&gt;
	if args[&amp;quot;precision&amp;quot;] and args[&amp;quot;precision&amp;quot;] ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		meta.precision = tonumber(args[&amp;quot;precision&amp;quot;])&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return mw.text.jsonEncode(meta)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>wp&gt;Nux</name></author>
	</entry>
</feed>