VOOZH about

URL: https://en.wikipedia.org/wiki/Module:Piechart

⇱ Module:Piechart - Wikipedia


Jump to content
From Wikipedia, the free encyclopedia
👁 Image
Module documentation
[view] [edit] [history] [purge]
👁 Image
This module is rated as ready for general use. It has reached a mature state, is considered relatively stable and bug-free, and may be used wherever appropriate. It can be mentioned on help pages and other Wikipedia resources as an option for new users. To minimise server load and avoid disruptive output, improvements should be developed through sandbox testing rather than repeated trial-and-error editing.
👁 Page template-protected
This module is currently protected from editing.
See the protection policy and protection log for more details. Please discuss any changes on the talk page; you may submit an edit request to ask an administrator to make an edit if it is uncontroversial or supported by consensus. You may also request that this page be unprotected.

Smooth pie chart module. Accessed via Template:Pie chart.

Usage

Draws charts in HTML with an accessible legend (optional). A list of all features is in the "TODO" section of the main `p.pie` function.

Most of the time you should use with a helper template that adds required CSS: {{Pie chart}}.

Examples

Minimalistic

Note that you don't need to provide the second value as it's calculated (assuming they sum up to 100).

{{Pie chart| [ {"value":33.3}, {} ] |thumb=none}}

Labels and Legend

Here we add some custom labels. Also note that we add a meta option to add legend on the side.

{{Pie chart| [
 {"label": "women: $v", "value": 33.3},
 {"label": "men: $v"}
]
|thumb=none
|meta = {"legend":true}
}}
  1. women: 33.3%
  2. men: 66.7%

Automatic Scaling

In cases where you don't have calculated percentages, you can use automatic scaling. Just provide both values in this case.

{{Pie chart| [
 {"label": "women: $v", "value": 750},
 {"label": "men: $v", "value": 250}
]
|thumb=none
|meta = {"legend":true}
}}
  1. women: 750 (75.0%)
  2. men: 250 (25.0%)

Multiple Values

The module allows displaying multiple values, not just 2.

{{Pie chart| [
 {"label": "sweets: $v", "value": 5, "color":"darkred"},
 {"label": "sandwiches: $v", "value": 3, "color":"wheat"},
 {"label": "cookies: $v", "value": 2, "color":"goldenrod"},
 {"label": "drinks: $v", "value": 1, "color":"#ccf"}
]
|thumb=none
|meta={"autoscale":true, "legend":true}
}}
  1. sweets: 5 (45.5%)
  2. sandwiches: 3 (27.3%)
  3. cookies: 2 (18.2%)
  4. drinks: 1 (9.09%)

Note that in this case, it was necessary to provide the additional option "autoscale":true. This is necessary when the sum is less than 100.

Links

  1. sweets: 5 (45.5%)
  2. sandwiches: 3 (27.3%)
  3. cookies: 2 (18.2%)
  4. drinks: 1 (9.09%)

Legend and Its Position

The legend is added using the meta property legend as shown. However, you can also change the order using direction. Possible values include:

  • row (default) – order is list, chart;
  • row-reverse – reverse order, i.e., chart, list;
  • column – column layout (vertical).
  • column-reverse – column layout, reversed (chart on top).
{{Pie chart| [
 {"label": "cookies: $v", "value": 2, "color":"goldenrod"},
 {"label": "drinks: $v", "value": 1, "color":"#ccf"},
 {"label": "sweets: $v", "value": 5, "color":"darkred"},
 {"label": "sandwiches: $v", "value": 3, "color":"wheat"}
]
|thumb=none
|meta={"autoscale":true, "legend":true, "direction":"row-reverse"}
}}

row (default direction)

  1. cookies: 2 (18.2%)
  2. drinks: 1 (9.09%)
  3. sweets: 5 (45.5%)
  4. sandwiches: 3 (27.3%)

row-reverse

  1. cookies: 2 (18.2%)
  2. drinks: 1 (9.09%)
  3. sweets: 5 (45.5%)
  4. sandwiches: 3 (27.3%)

column

  1. cookies: 2 (18.2%)
  2. drinks: 1 (9.09%)
  3. sweets: 5 (45.5%)
  4. sandwiches: 3 (27.3%)

column-reverse

  1. cookies: 2 (18.2%)
  2. drinks: 1 (9.09%)
  3. sweets: 5 (45.5%)
  4. sandwiches: 3 (27.3%)

Green frames added for clarity in examples. They are not normally added.

Direct functions

In case you want to use without the {{Pie chart}} template, you can use this main functions:

  • {{#invoke:Piechart|pie|json_data|meta=json_options}}
  • {{#invoke:Piechart|color|number}}

Note that direct calls to the pie function require adding CSS:

<templatestylessrc="Pie chart/style.css"/>
{{#invoke:Piechart|pie|[{"value":33.3},{}]}}

Example of json_data:

[
{"label":"pie: $v","color":"wheat","value":40},
{"label":"cheese pizza $v","color":"#fc0","value":20},
{"label":"mixed pizza: $v","color":"#f60","value":20},
{"label":"raw pizza $v","color":"#f30"}
]
  • Note that the last value is missing. The last value is optional as long as the values are intended to sum up to 100 (as in 100%).
  • Notice $v label, this is a formatted number (see `function prepareLabel`).
  • Colors are hex or names. Default palette is in shades of green.

Example of meta=json_options:

|meta={"size":200,"autoscale":false,"legend":true}

All meta options are optional (see `function p.setupOptions`).

Feature requests

For feature requests and bugs write to me, the author of the piechart module: Maciej Nux.

localp={}
localpriv={}-- private functions scope
-- expose private for easy testing/debugging
p.__priv=priv
-- require exact colors for printing
localforPrinting="-webkit-print-color-adjust: exact; print-color-adjust: exact;"
--[===[
	Smooth piechart module.
	Draws charts in HTML with an accessible legend (optional).
	A list of all features is in the "TODO" section of the main `p.pie` function.
	Module info:
	- Changelog and TODO: [[:en:User:Nux/pie_chart_-_todo]] (Piechart 1.0, 2.0 and beyond).
	- Author: [[:en:User:Nux|Maciej Nux]].
	Use with a helper template that adds required CSS.
	{{{1}}}:
	[
		{ "label": "pie: $v", "color": "wheat", "value": 40 },
		{ "label": "cheese pizza $v", "color": "#fc0", "value": 20 },
		{ "label": "mixed pizza: $v", "color": "#f60", "value": 20 },
		{ "label": "raw pizza $v", "color": "#f30" }
	]
	Where $v is a formatted number (see `function prepareLabel`).
	{{{meta}}}:
		{"size":200, "autoscale":false, "legend":true}
	All meta options are optional (see `function p.setupOptions`).
]===]
--[[
	Debug:
	-- labels and auto-value
	local json_data = '[{"label": "k: $v", "value": 33.1}, {"label": "m: $v", "value": -1}]'
	local html = p.renderPie(json_data)
	mw.logObject(html)
	-- autoscale values
	local json_data = '[{"value": 700}, {"value": 300}]'
	local html = p.renderPie(json_data, options)
	mw.logObject(html)	
	-- size option
	local json_data = '[{"label": "k: $v", "value": 33.1}, {"label": "m: $v", "value": -1}]'
	local options = '{"size":200}'
	local html = p.renderPie(json_data, options)
	mw.logObject(html)	
	-- custom colors
	local json_data = '[{"label": "k: $v", "value": 33.1, "color":"black"}, {"label": "m: $v", "value": -1, "color":"green"}]'
	local html = p.renderPie(json_data)
	mw.logObject(html)
	-- 4-cuts
	local entries = {
		'{"label": "ciastka: $v", "value": 2, "color":"goldenrod"}',
		'{"label": "słodycze: $v", "value": 4, "color":"darkred"}',
		'{"label": "napoje: $v", "value": 1, "color":"lightblue"}',
		'{"label": "kanapki: $v", "value": 3, "color":"wheat"}'
	}
	local json_data = '['..table.concat(entries, ',')..']'
	local html = p.renderPie(json_data, '{"autoscale":true}')
	mw.logObject(html)
	-- colors
	local fr = { args = { " 123 " } }
	local ret = p.color(fr)
]]
--[[
	Color for a slice (defaults).
	{{{1}}}: slice number
]]
functionp.color(frame)
localindex=tonumber(priv.trim(frame.args[1]))
return' '..priv.defaultColor(index)
end
--[===[
	Main pie chart function.
	TODO (maybe):
	[[:en:User:Nux/pie_chart_-_todo#Hopes_and_dreams]]
]===]
functionp.pie(frame)
localjson_data=priv.trim(frame.args[1])
localoptions={}
if(frame.args.meta)then
options.meta=priv.trim(frame.args.meta)
end
localhtml=p.renderPie(json_data,options)
returnpriv.trim(html)
end
-- Setup chart options.
functionp.setupOptions(user_options)
localoptions={
-- circle size in [px]
size=100,
-- autoscale values (otherwise assume they sum up to 100)
autoscale=false,
-- hide chart for screen readers (when you have a table, forced for legend)
ariahidechart=false,
-- show legend (defaults to the left side)
legend=false,
-- direction of legend-chart flexbox (flex-direction)
direction="",
-- width of the main container
-- when direction is used defaults to max-width, otherwise it's not added
width="",
-- caption above the labels
caption="",
-- footer below the labels
footer="",
-- formatting template for labels
labelformat="",
-- percentage number formatting precision (number of digits; -1 = automatic mode)
precision=-1,
}
-- internals
options.style=""
ifuser_optionsanduser_options.metathen
localdecodeSuccess,rawOptions=pcall(function()
returnmw.text.jsonDecode(user_options.meta,mw.text.JSON_TRY_FIXING)
end)
ifnotdecodeSuccessthen
rawOptions=false
mw.log('invalid meta parameters')
end
ifrawOptionsthen
iftype(rawOptions.size)=="number"then
options.size=math.floor(rawOptions.size)
end
options.autoscale=rawOptions.autoscaleorfalse
ifrawOptions.legendthen
options.legend=true
end
ifrawOptions.ariahidechartthen
options.ariahidechart=true
end
if(type(rawOptions.direction)=="string")then
-- Remove unsafe/invalid characters
localsanitized=rawOptions.direction:gsub("[^a-z0-9%-]","")
-- also adjust width so that row-reverse won't push things to the right
options.direction='flex-direction: '..sanitized..';'
options.width='width: max-content;'
end
if(type(rawOptions.width)=="string")then
-- note, this intentionaly overwrites what was set for direction
localsanitized=rawOptions.width:gsub("[^a-z0-9%-]","")
options.width='width: '..sanitized..';'
end
if(type(rawOptions.caption)=="string")then
options.caption=rawOptions.caption
end
if(type(rawOptions.footer)=="string")then
options.footer=rawOptions.footer
end
if(type(rawOptions.labelformat)=="string")then
options.labelformat=rawOptions.labelformat
end
iftype(rawOptions.precision)=="number"then
options.precision=math.floor(rawOptions.precision)
end
end
-- build style
ifoptions.width~=""then
options.style=options.style..options.width
end
ifoptions.direction~=""then
options.style=options.style..options.direction
end
end
if(options.legend)then
options.ariahidechart=true
end
returnoptions
end
-- internal for testing legend rendering
p.__priv.legendDebug=false
--[[
	Render piechart.
	@param json_data JSON string with pie data.
]]
functionp.renderPie(json_data,user_options)
iftype(json_data)~="string"then
error('invalid piechart data type: '..type(json_data))
end
if#json_data<2then
error('piechart data is empty')
end
localdecodeSuccess,data=pcall(function()
returnmw.text.jsonDecode(json_data,mw.text.JSON_TRY_FIXING)
end)
-- Handle decode error
ifnotdecodeSuccessthen
error('invalid piechart data: '..json_data)
end
localoptions=p.setupOptions(user_options)
-- prepare
localok,total=p.prepareEntries(data,options)
-- init render
localhtml="<div class='smooth-pie-container' style='"..options.style.."'>"
-- error info
ifnotokthen
html=html..priv.renderErrors(data)
end
-- render legend
ifoptions.legendthen
html=html..p.renderLegend(data,options)
end
ifp.__priv.legendDebugthen
returnhtml
end
-- render items
localheader,items,footer=p.renderEntries(ok,total,data,options)
html=html..header..items..footer
-- end .smooth-pie-container
html=html.."\n</div>"
returnhtml
end
functionpriv.boundaryFormatting(diff)
localvalue=0.0
ifdiff<=1.0then
value=math.ceil(diff/0.2)*0.2-- 0.2 step
else
value=math.ceil(diff/0.5)*0.5-- 0.5 step
end
returnstring.format("%.1f",value)
end
-- Check if sum will trigger autoscaling
functionpriv.willAutoscale(sum)
-- Compare with a number larger then 100% to avoid floating-point precision problems
--- ...and data precision problems https://en.wikipedia.org/wiki/Template_talk:Pie_chart#c-PrimeHunter-20250420202500-Allow_percentage_sum_slightly_above_100
localdiff=sum-100
localgrace=1
returndiff>grace
end
-- Tracking errors in data (note: somewhat expensive, similar to a red link)
-- In short: ±0.3 is a reasonable deviation; ±1 when the errors accumulate
-- https://en.wikipedia.org/wiki/Template_talk:Pie_chart#c-Nux-20250429152000-Nux-20250422224600
functionpriv.sumErrorTracking(sum,items)
localdiff=sum-100
ifdiff>=0.4anddiff<=10then
localfirstItem=items[1]
locallastItem=items[#items]
mw.addWarning("pie chart: Σ (value) = "..sum.."% ("..firstItem.value.." + .. .+ "..lastItem.value..")")
ifmw.title.getCurrentTitle().namespace==0then
localsuffix=priv.boundaryFormatting(diff)
_=mw.title.new("Module:Piechart/tracing/diff below "..suffix).id
end
end
end
-- Prepare data (slices etc)
functionp.prepareEntries(data,options)
localsum=priv.sumValues(data);
-- force autoscale when over 100
ifpriv.willAutoscale(sum)then
options.autoscale=true
end
-- pre-format entries
localok=true
localno=0
localtotal=#data
forindex,entryinipairs(data)do
no=no+1
ifnotpriv.prepareSlice(entry,no,sum,total,options)then
no=no-1
ok=false
end
end
total=no-- total valid
returnok,total
end
functionpriv.sumValues(data)
localsum=0;
for_,entryinipairs(data)do
localvalue=entry.value
ifnot(type(value)~="number"orvalue<0)then
sum=sum+value
end
end
returnsum
end
-- render error info
functionpriv.renderErrors(data)
localhtml="\n<ol class='chart-errors' style='display:none'>"
for_,entryinipairs(data)do
ifentry.errorthen
localentryJson=mw.text.jsonEncode(entry)
html=html.."\n<li>"..entryJson.."</li>"
end
end
returnhtml.."\n</ol>\n"
end
-- Prepare single slice data (modifies entry).
-- @param no = 1..total
functionpriv.prepareSlice(entry,no,sum,total,options)
localautoscale=options.autoscale
localvalue=entry.value
if(type(value)~="number"orvalue<0)then
ifautoscalethen
entry.error="cannot autoscale unknown value"
returnfalse
end
value=100-sum
end
-- entry.raw only when scaled
ifautoscalethen
entry.raw=value
value=(value/sum)*100
end
entry.value=value
-- prepare final label
entry.label=priv.prepareLabel(options.labelformat,entry,options.precision)
-- background, but also color for MW syntax linter
entry.bcolor=priv.backColor(entry,no,total)..";color:#000"
returntrue
end
-- render legend for pre-processed entries
functionp.renderLegend(data,options)
localhtml=""
ifoptions.caption~=""oroptions.footer~=""then
html="\n<div class='smooth-pie-legend-container'>"
end
ifoptions.caption~=""then
html=html.."<div class='smooth-pie-caption'>"..options.caption.."</div>"
end
html=html.."\n<ol class='smooth-pie-legend'>"
for_,entryinipairs(data)do
ifnotentry.errorthen
html=html..priv.renderLegendItem(entry,options)
end
end
html=html.."\n</ol>\n"
ifoptions.footer~=""then
html=html.."<div class='smooth-pie-footer'>"..options.footer.."</div>"
end
ifoptions.caption~=""oroptions.footer~=""then
html=html.."</div>\n"
end
returnhtml
end
-- render legend item
functionpriv.renderLegendItem(entry,options)
-- invisible value (for a11y reasons this should not be used for important values!)
ifentry.visible~=nilandentry.visible==falsethen
return""
end
locallabel=entry.label
localbcolor=entry.bcolor
localhtml="\n<li>"
ifp.__priv.legendDebugthen
forPrinting=""
end
html=html..'<span class="l-color" style="'..forPrinting..bcolor..'"></span>'
html=html..'<span class="l-label">'..label..'</span>'
returnhtml.."</li>"
end
-- Prepare data (slices etc)
functionp.renderEntries(ok,total,data,options)
-- cache for some items (small slices)
p.cuts=mw.loadJsonData('Module:Piechart/cuts.json')
localfirst=true
localprevious=0
localitems=""
forindex,entryinipairs(data)do
ifnotentry.errorthen
items=items..priv.renderItem(previous,entry,options)
previous=previous+entry.value
end
end
localheader=priv.renderHeader(options)
localfooter='\n<div class="smooth-pie-border"></div></div>'
returnheader,items,footer
end
-- header of pie-items (class="smooth-pie")
functionpriv.renderHeader(options)
localbcolor='background:#888;color:#000'
localsize=options.size
-- hide chart for readers, especially when legend is there
localaria=""
if(options.ariahidechart)then
aria='aria-hidden="true"'
end
-- slices container and last slice
localstyle='width:'..size..'px;height:'..size..'px;'..bcolor..';'..forPrinting
localhtml=[[
<div class="smooth-pie"
	style="]]..style..[["
	]]..aria..[[
>]]
returnhtml
end
-- Render pie-item
-- (previous is a sum of previous values)
functionpriv.renderItem(previous,entry,options)
localvalue=entry.value
locallabel=entry.label
localbcolor=entry.bcolor
-- value too small to see
if(value<0.03)then
mw.log('value too small',value,label)
mw.addWarning("pie chart: Value too small ↆ "..value.."% ("..label..")")
return""
end
-- minimize transformation defects
ifvalue<10then
ifprevious>1then
previous=previous-0.01
end
value=value+0.02
else
ifprevious>1then
previous=previous-0.1
end
value=value+0.2
end
-- force sum to be below 100% (needed due to value errors)
ifprevious+value>100then
ifprevious>=100then
mw.log('previous is already at 100',value,label)
return""
end
value=100-previous
end
localhtml=""
localsize=''
-- mw.logObject({'v,p,l', value, previous, label})
if(value>=50)then
html=priv.sliceWithClass('pie50',50,value,previous,bcolor,label)
elseif(value>=25)then
html=priv.sliceWithClass('pie25',25,value,previous,bcolor,label)
elseif(value>=12.5)then
html=priv.sliceWithClass('pie12-5',12.5,value,previous,bcolor,label)
elseif(value>=7)then
html=priv.sliceWithClass('pie7',7,value,previous,bcolor,label)
elseif(value>=5)then
html=priv.sliceWithClass('pie5',5,value,previous,bcolor,label)
else
-- 0-5%
localcutIndex=priv.round(value*10)
ifcutIndex<1then
cutIndex=1
end
localcut=p.cuts[cutIndex]
localtransform=priv.rotation(previous)
html=priv.sliceX(cut,transform,bcolor,label)
end
-- mw.log(html)
returnhtml
end
-- round to int
functionpriv.round(number)
returnmath.floor(number+0.5)
end
-- render full slice with specific class
functionpriv.sliceWithClass(sizeClass,sizeStep,value,previous,bcolor,label)
localtransform=priv.rotation(previous)
localhtml=""
html=html..priv.sliceBase(sizeClass,transform,bcolor,label)
-- mw.logObject({'sliceWithClass:', sizeClass, sizeStep, value, previous, bcolor, label})
if(value>sizeStep)then
localextra=value-sizeStep
transform=priv.rotation(previous+extra)
-- mw.logObject({'sliceWithClass; extra, transform', extra, transform})
html=html..priv.sliceBase(sizeClass,transform,bcolor,label)
end
returnhtml
end
-- render single slice
functionpriv.sliceBase(sizeClass,transform,bcolor,label)
localstyle=bcolor
iftransform~=""then
style=style..'; '..transform
end
return'\n\t<div class="'..sizeClass..'" style="'..style..'" title="'..p.extract_text(label)..'"></div>'
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%
functionpriv.sliceX(cut,transform,bcolor,label)
localpath='clip-path: polygon(0% 0%, '..cut..'% 0%, 0 100%)'
return'\n\t<div style="'..transform..'; '..bcolor..'; '..path..'" title="'..p.extract_text(label)..'"></div>'
end
-- translate value to turn rotation (v=100 => 1.0turn)
functionpriv.rotation(value)
if(value>0.001)then
localf=string.format("%.7f",value/100)
f=f:gsub("(%d)0+$","%1")-- remove trailing zeros
return"transform: rotate("..f.."turn)"
end
return''
end
-- Language sensitive float, small numbers.
functionpriv.formatNum(value,precision)
precision=precisionor-1
localv=""
ifprecision>=0then
-- custom format
v=string.format("%."..precision.."f",value)
else
-- automatic
if(value<10)then
v=string.format("%.2f",value)
else
v=string.format("%.1f",value)
end
end
locallang=mw.language.getContentLanguage()
if(lang:getCode()=='pl')then
v=v:gsub("%.",",")
end
returnv
end
-- Format large values.
functionpriv.formatLargeNum(value)
locallang=mw.language.getContentLanguage()
-- add thusands separators
localv=lang:formatNum(value)
returnv
end
-- Testing formatLargeNum.
-- p.__priv.test_formatLargeNum()
functionpriv.test_formatLargeNum()
mw.log("must not add fractional part")
mw.log(p.__priv.formatLargeNum(12))
mw.log(p.__priv.formatLargeNum(123))
mw.log("should preserve fractional part for small numbers")
mw.log(p.__priv.formatLargeNum(1.1))
mw.log(p.__priv.formatLargeNum(1.12))
mw.log(p.__priv.formatLargeNum(12.1))
mw.log("can preserve long fractional part")
mw.log(p.__priv.formatLargeNum(1.1234))
mw.log(p.__priv.formatLargeNum(1.12345))
mw.log("should add separators above 1k")
mw.log(p.__priv.formatLargeNum(999))
mw.log(p.__priv.formatLargeNum(1234))
mw.log(p.__priv.formatLargeNum(12345))
mw.log(p.__priv.formatLargeNum(123456))
mw.log(p.__priv.formatLargeNum(1234567))
mw.log("must handle large float, but might round values")
mw.log(p.__priv.formatLargeNum(1234.123))
mw.log(p.__priv.formatLargeNum(12345.123))
mw.log(p.__priv.formatLargeNum(123456.123))
mw.log(p.__priv.formatLargeNum(1234567.123))
end
--[[
	Prepare final label.
	Typical tpl:
		"$L: $v" (same as: "$label: $auto")
	will result in:
		"Abc: 23%" -- when values are percentages
		"Abc: 1234 (23%)" -- when values are autoscaled
	Advanced tpl:
		"$L: $d ($p)" e.g. "Abc: 1234 (23%)" for {"label":"Abc", "value":1234}
		"$L: $v" is the same as above when values are autoscaled
	Long vs short variable names:
		$label ($L)
		$auto ($v)
		$value ($d)
		$percent ($p)
]]
functionpriv.prepareLabel(tpl,entry,precision)
-- setup default tpl
ifnottplortpl==""then
-- simple if no label
ifnotentry.labelorentry.label==""then
tpl="$v"
else
ifentry.label:find("%$[a-z]")then
tpl=entry.label
else
tpl="$L: $v"
end
end
end
locallabelLabel=entry.labelandentry.labelorpriv.getLangOther()
-- format % value without %
localpRaw=priv.formatNum(entry.value,precision)
localpp=pRaw.."%%"
locallabel=tpl
-- aliases
label=label
:gsub("%$label","$L")
:gsub("%$auto","$v")
:gsub("%$value","$d")
:gsub("%$percent","$p")
-- replace variables
label=label:gsub("%$L",labelLabel)
locald=priv.formatLargeNum(entry.rawandentry.raworentry.value)
localv=entry.rawand(d.." ("..pp..")")orpp
label=label
:gsub("%$p",pp)
:gsub("%$d",d)
:gsub("%$v",v)
-- Report unknown variables
forvarinlabel:gmatch("%$[a-zA-Z]+")do
-- in preview
mw.addWarning("pie chart: Unknown variable (wrong format of your label): "..var)
-- tracing links
_=mw.title.new("Module:Piechart/tracing/unknown-variable").id
end
returnlabel
end
-- default colors
-- source: https://colorbrewer2.org/#type=diverging&scheme=PRGn&n=6
localcolorGroupSize=3-- must be at least 3
localcolorGroups=4
localcolorPalette={
-- green (from dark)
'#1b7837',
'#7fbf7b',
'#d9f0d3',
-- violet
'#762a83',
'#af8dc3',
'#e7d4e8',
-- red
'#d73027',
'#fc8d59',
'#fee090',
-- blue
'#4575b4',
'#91bfdb',
'#e0f3f8',
}
locallastColor='#fff'
-- background color from entry or the default colors
functionpriv.backColor(entry,no,total)
if(type(entry.color)=="string")then
-- Remove unsafe characters from entry.color
localsanitizedColor=entry.color
:gsub('&#35;','#')-- workaround Module:Political_party issue reported on talk
:gsub("[^a-zA-Z0-9#%-]","")
return'background:'..sanitizedColor
else
localcolor=priv.defaultColor(no,total)
return'background:'..color
end
end
-- color from the default colors
functionpriv.defaultColor(no,total)
localcolor=lastColor
ifno<=0then
returncolor
end
localsize=#colorPalette
ifnottotalortotal==0then
total=size+1
end
localcolorNo=priv.defaultColorNo(no,total,size)
ifcolorNo>0then
color=colorPalette[colorNo]
end
returncolor
end
-- gets color number from default colors
-- trys to return a light color as the last one
-- 0 means white-ish color should be used
functionpriv.defaultColorNo(no,total,size)
localcolor=0-- special, lastColor
iftotal==1then
color=1
elseiftotal<=colorGroupSize*(colorGroups-1)then
ifno<totalthen
color=no
else
localgroupIndex=((no-1)%colorGroupSize)
ifgroupIndex==0then-- dark
color=no+1
elseifgroupIndex==1then-- med
color=no+1
else
color=no
end
end
elseifno<totalthen
color=((no-1)%size)+1
end
returncolor
end
--[[
	Testing defaultColorNo:
	p.__priv.test_defaultColorNo(1, 12)
	p.__priv.test_defaultColorNo(2, 12)
	p.__priv.test_defaultColorNo(3, 12)
	p.__priv.test_defaultColorNo(4, 12)
	p.__priv.test_defaultColorNo(5, 12)
	p.__priv.test_defaultColorNo(6, 12)
]]
functionpriv.test_defaultColorNo(total,size)
forno=1,totaldo
localcolor=priv.defaultColorNo(no,total,size)
mw.logObject({no=no,color=color})
end
end
--[[
	trim string
	note:
	`(s:gsub(...))` returns only a string
	`s:gsub(...)` returns a string and a number
]]
functionpriv.trim(s)
return(s:gsub("^%s+",""):gsub("%s+$",""))
end
--[[
 Extract text from simple wikitext.
 For now only works with links.
]]
-- Tests:
-- mw.log(p.extract_text("[[candy|sweets]]: $v"))
-- mw.log(p.extract_text("[[sandwich]]es: $v"))
-- mw.log(p.extract_text("sandwich]]es: $v"))
-- mw.log(p.extract_text("sandwiches: $v"))
functionp.extract_text(label)
label=label
-- replace links with pipe (e.g., [[candy|sweets]])
:gsub("%[%[[^|%]]+|(.-)%]%]","%1")
-- replace simple links without pipe (e.g., [[sandwich]])
:gsub("%[%[(.-)%]%]","%1")
-- remove templates?
-- :gsub("{.-}", "")
-- remove tags
:gsub("<[^>]+>","")
-- escape special chars just in case
:gsub("<","&lt;"):gsub(">","&gt;")
:gsub("'","&#39;"):gsub("\"","&quot;")
returnlabel
end
--[[
 Parse classic template params into JSON.
From: 
|label1=cookies: $v |value1=11 |color1=goldenrod
|label2=sweets: $v |value2=20 |color2=darkred
To:
{"value":11,"color":"goldenrod","label":"cookies: $v"},
{"value":20,"color":"darkred","label":"sweets: $v"},
]]
functionp.parseEnumParams(frame)
localargs=frame:getParent().args
returnpriv.parseEnumParams(args)
end
functionpriv.parseEnumParams(args)
localresult={}
locali=1
localsum=0.0
localhasCustomColor=false-- has last custom color
whileargs["value"..i]do
-- value is required in this mode; it's also assumed to be 0..100
localentry={value=tonumber(args["value"..i])or0}
-- label and color is optional
locallabel=args["label"..i]
iflabelandlabel~=""then
entry.label=label
end
hasCustomColor=false
localcolor=args["color"..i]
ifcolorandcolor~=""then
entry.color=color
hasCustomColor=true
end
table.insert(result,entry)
sum=sum+entry.value
i=i+1
end
-- re-loop to set values in labels
localwillAutoscale=priv.willAutoscale(sum)
for_,entryinipairs(result)do
locallabel=entry.label
iflabelandnotlabel:find("%$[a-z]")then
-- autoscale will be forced, so use $v in labels
ifwillAutoscalethen
entry.label=label.." $v"
else
entry.label=label.." ($p)"
end
end
end
-- tracking data errors
priv.sumErrorTracking(sum,result)
-- support other value mapping
locallangOther=priv.getLangOther()
localcolorOther="#FEFDFD"-- white-ish for custom colors for best chance and contrast
localotherValue=100-sum
ifargs["other"]andargs["other"]~=""then
ifotherValue<0.001then
otherValue=0
end
localotherEntry={label=(args["other-label"]orlangOther).." ($p)"}
ifargs["other-color"]andargs["other-color"]~=""then
otherEntry.color=args["other-color"]
else
otherEntry.color=colorOther
end
table.insert(result,otherEntry)
elseifotherValue>0.01then
ifhasCustomColorthen
table.insert(result,{visible=false,label=langOther.." ($v)",color=colorOther})
else
table.insert(result,{visible=false,label=langOther.." ($v)"})
end
end
localjsonString=mw.text.jsonEncode(result)
returnjsonString
end
functionpriv.getLangOther()
-- support other value mapping
locallang=mw.language.getContentLanguage()
if(lang:getCode()=='pl')then
return"Inne"
end
return"Other"
end
-- Function to check if a value is true-ish
localtrueValues={["true"]=true,["1"]=true,["on"]=true,["yes"]=true}
functionpriv.isTrueishValue(value)
-- should return nil for empty args (i.e. undefined i.e. default)
ifnotvalueorvalue==""thenreturnnilend
value=priv.trim(value)
ifvalue==""thenreturnnilend
-- other non-empty are false
returntrueValues[value:lower()]orfalse
end
--[[
 Parse classic template params into JSON with chart meta data.
]]
functionp.parseMetaParams(frame)
localargs=frame:getParent().args
localmeta={}
-- default meta for value1..n parameters
-- ...and for thumb right/left
localthumb=args["thumb"]
ifargs["value1"]or(thumband(thumb=="right"orthumb=="left"))then
meta.size=200
meta.legend=true
end
-- explicit meta param
ifargs["meta"]then
localdecodeSuccess,tempMeta=pcall(function()
returnmw.text.jsonDecode(args["meta"],mw.text.JSON_TRY_FIXING)
end)
ifnotdecodeSuccessthen
mw.log('invalid meta parameter')
else
meta=tempMeta
end
end
ifargs["size"]thenmeta.size=tonumber(args["size"])end
ifargs["radius"]andtonumber(args["radius"])then
meta.size=2*tonumber(args["radius"])
end
ifargs["autoscale"]thenmeta.autoscale=priv.isTrueishValue(args["autoscale"])end
ifargs["legend"]thenmeta.legend=priv.isTrueishValue(args["legend"])end
ifargs["ariahidechart"]thenmeta.ariahidechart=priv.isTrueishValue(args["ariahidechart"])end
ifargs["direction"]andargs["direction"]~=""then
meta.direction=args["direction"]:gsub("[^a-z0-9%-]","")
end
ifargs["width"]andargs["width"]~=""then
meta.width=args["width"]:gsub("[^a-z0-9%-]","")
end
ifargs["caption"]andargs["caption"]~=""then
meta.caption=args["caption"]
end
ifargs["footer"]andargs["footer"]~=""then
meta.footer=args["footer"]
end
ifargs["labelformat"]andargs["labelformat"]~=""then
meta.labelformat=args["labelformat"]
end
ifargs["precision"]andargs["precision"]~=""then
meta.precision=tonumber(args["precision"])
end
returnmw.text.jsonEncode(meta)
end
returnp