Module:Piechart
![]() | This module is rated as ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
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}
}}
- women: 33.3%
- 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}
}}
- women: 750 (75.0%)
- 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}
}}
- sweets: 5 (45.5%)
- sandwiches: 3 (27.3%)
- cookies: 2 (18.2%)
- 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
- sweets: 5 (45.5%)
- sandwiches: 3 (27.3%)
- cookies: 2 (18.2%)
- 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)
- cookies: 2 (18.2%)
- drinks: 1 (9.09%)
- sweets: 5 (45.5%)
- sandwiches: 3 (27.3%)
row-reverse
- cookies: 2 (18.2%)
- drinks: 1 (9.09%)
- sweets: 5 (45.5%)
- sandwiches: 3 (27.3%)
column
- cookies: 2 (18.2%)
- drinks: 1 (9.09%)
- sweets: 5 (45.5%)
- sandwiches: 3 (27.3%)
column-reverse
- cookies: 2 (18.2%)
- drinks: 1 (9.09%)
- sweets: 5 (45.5%)
- 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:
<templatestyles src="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.
See also
- {{Pie chart}}, also pie, but with classic template parameters (enumerated); that chart is shown on the right (like a thumbnail image).
local p = {}
local priv = {} -- private functions scope
-- expose private for easy testing/debugging
p.__priv = priv
-- require exact colors for printing
local forPrinting = "-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.
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`).
]]
-- Author: [[User:Nux|Maciej Nux]] (pl.wikipedia.org).
--[[
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
]]
function p.color(frame)
local index = tonumber(priv.trim(frame.args[1]))
return ' ' .. priv.defaultColor(index)
end
--[[
Piechart.
TODO:
- [x] basic 2-element pie chart
- read json
- calculate value with -1
- generate html
- new css + tests
- provide dumb labels (just v%)
- [x] colors in json
- [x] 1st value >= 50%
- [x] custom labels support
- [x] pie size from 'meta' param (options json)
- [x] pl formatting for numbers?
- [x] support undefined value (instead of -1)
- [x] undefined in any order
- [x] scale values to 100% (autoscale)
- [x] order values clockwise (not left/right)
- [x] multi-cut pie
- [x] sanitize user values
- [x] auto colors
- [x] function to get color by number (for custom legend)
- [x] remember and show autoscaled data
- [x] generate a legend
- [x] simple legend positioning by (flex-)direction
- legend2: customization
- (?) itemTpl support
- replace default item with tpl
- can I / should I sanitize it?
- support for $v, $d, $p
- (?) custom head
- (?) validation of input
- check if required values are present
- message showing whole entry, when entry is invalid
- pre-sanitize values?
- sane info when JSON fails? Maybe dump JSON and show example with quotes-n-all...
- (?) option to sort entries by value
]]
function p.pie(frame)
local json_data = priv.trim(frame.args[1])
local options = {}
if (frame.args.meta) then
options.meta = priv.trim(frame.args.meta)
end
local html = p.renderPie(json_data, options)
return priv.trim(html)
end
-- Setup chart options.
function p.setupOptions(user_options)
local options = {
-- 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 = "",
}
-- internals
options.style = ""
if user_options.meta then
local rawOptions = mw.text.jsonDecode(user_options.meta, mw.text.JSON_TRY_FIXING)
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 = 'flex-direction: ' .. sanitized .. ';'
options.width = 'width: max-content;'
end
if (type(rawOptions.width) == "string") then
-- note, this intentionaly overwrites what was set for direction
local sanitized = 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
end
-- build style
if options.width ~= "" then
options.style = options.style .. options.width
end
if options.direction ~= "" then
options.style = options.style .. options.direction
end
end
if (options.legend) then
options.ariahidechart = true
end
return options
end
--[[
Render piechart.
@param json_data JSON string with pie data.
]]
function p.renderPie(json_data, user_options)
local data = mw.text.jsonDecode(json_data, mw.text.JSON_TRY_FIXING)
local options = p.setupOptions(user_options)
-- prepare
local ok, total = p.prepareEntries(data, options)
-- init render
local html = "<div class='smooth-pie-container' style='"..options.style.."'>"
-- error info
if not ok then
html = html .. priv.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</div>"
return html
end
-- Prepare data (slices etc)
function p.prepareEntries(data, options)
local sum = priv.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 priv.prepareSlice(entry, no, sum, total, options) then
no = no - 1
ok = false
end
end
total = no -- total valid
return ok, total
end
function priv.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 sum
end
-- render error info
function priv.renderErrors(data)
local html = "\n<ol class='chart-errors' style='display:none'>"
for _, entry in ipairs(data) do
if entry.error then
local entryJson = mw.text.jsonEncode(entry)
html = html .. "\n<li>".. entryJson .."</li>"
end
end
return html .. "\n</ol>\n"
end
-- Prepare single slice data (modifies entry).
-- @param no = 1..total
function priv.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 = priv.prepareLabel(entry.label, entry)
-- background, but also color for MW syntax linter
entry.bcolor = priv.backColor(entry, no, total) .. ";color:#000"
return true
end
-- render legend for pre-processed entries
function p.renderLegend(data, options)
local html = ""
if options.caption ~= "" or options.footer ~= "" then
html = "\n<div class='smooth-pie-legend-container'>"
end
if options.caption ~= "" then
html = html .. "<div class='smooth-pie-caption'>" .. options.caption .. "</div>"
end
html = html .. "\n<ol class='smooth-pie-legend'>"
for _, entry in ipairs(data) do
if not entry.error then
html = html .. priv.renderLegendItem(entry, options)
end
end
html = html .. "\n</ol>\n"
if options.footer ~= "" then
html = html .. "<div class='smooth-pie-footer'>" .. options.footer .. "</div>"
end
if options.caption ~= "" or options.footer ~= "" then
html = html .. "</div>\n"
end
return html
end
-- render legend item
function priv.renderLegendItem(entry, options)
-- invisible value (for a11y reasons this should not be used for important values!)
if entry.visible ~= nil and entry.visible == false then
return ""
end
local label = entry.label
local bcolor = entry.bcolor
local html = "\n<li>"
html = html .. '<span class="l-color" style="'..forPrinting..bcolor..'"></span>'
html = html .. '<span class="l-label">'..label..'</span>'
return html .. "</li>"
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 = priv.renderFinal(entry, options)
else
items = items .. priv.renderOther(previous, entry, options)
end
previous = previous + entry.value
end
end
local footer = '\n</div>'
return header, items, footer
end
-- final, but header...
function priv.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..';'..forPrinting
local html = [[
<div class="smooth-pie"
style="]]..style..[["
title="]]..p.extract_text(label)..[["
]]..aria..[[
>]]
return html
end
-- any other then final
function priv.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({'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%
local cutIndex = priv.round(value*10)
if cutIndex < 1 then
cutIndex = 1
end
local cut = p.cuts[cutIndex]
local transform = priv.rotation(previous)
html = priv.sliceX(cut, transform, bcolor, label)
end
-- mw.log(html)
return html
end
-- round to int
function priv.round(number)
return math.floor(number + 0.5)
end
-- render full slice with specific class
function priv.sliceWithClass(sizeClass, sizeStep, value, previous, bcolor, label)
local transform = priv.rotation(previous)
local html = ""
html = html .. priv.sliceBase(sizeClass, transform, bcolor, label)
-- mw.logObject({'sliceWithClass:', sizeClass, sizeStep, value, previous, bcolor, label})
if (value > sizeStep) then
local extra = value - sizeStep
transform = priv.rotation(previous + extra)
-- mw.logObject({'sliceWithClass; extra, transform', extra, transform})
html = html .. priv.sliceBase(sizeClass, transform, bcolor, label)
end
return html
end
-- render single slice
function priv.sliceBase(sizeClass, transform, bcolor, label)
local style = bcolor
if transform ~= "" 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%
function priv.sliceX(cut, transform, bcolor, label)
local path = '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
function priv.rotation(value)
if (value > 0) then
return string.format("transform: rotate(%.3fturn)", value/100)
end
return ''
end
-- Language sensitive float.
function priv.formatNum(value)
local lang = mw.language.getContentLanguage()
-- doesn't do precision :(
-- local v = lang:formatNum(value)
local v = ""
if (value < 10) then
v = string.format("%.2f", value)
else
v = string.format("%.1f", value)
end
if (lang:getCode() == 'pl') then
v = v:gsub("%.", ",")
end
return v
end
--[[
Prepare final label.
Typical tpl:
"Abc: $v"
will result in:
"Abc: 23%" -- when values are percentages
"Abc: 1234 (23%)" -- when values are autoscaled
Advanced tpl:
"Abc: $d ($p)" -- only works with autoscale
]]
function priv.prepareLabel(tpl, entry)
-- static tpl
if tpl and not string.find(tpl, '$') then
return tpl
end
-- format % value without %
local p = priv.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 label
end
-- default colors
-- source: https://colorbrewer2.org/#type=diverging&scheme=PRGn&n=6
local colorGroupSize = 3 -- must be at least 3
local colorGroups = 4
local colorPalette = {
-- green (from dark)
'#1b7837',
'#7fbf7b',
'#d9f0d3',
-- violet
'#762a83',
'#af8dc3',
'#e7d4e8',
-- red
'#d73027',
'#fc8d59',
'#fee090',
-- blue
'#4575b4',
'#91bfdb',
'#e0f3f8',
}
local lastColor = '#fff'
-- background color from entry or the default colors
function priv.backColor(entry, no, total)
if (type(entry.color) == "string") then
-- Remove unsafe characters from entry.color
local sanitizedColor = entry.color:gsub("[^a-zA-Z0-9#%-]", "")
return 'background:' .. sanitizedColor
else
local color = priv.defaultColor(no, total)
return 'background:' .. color
end
end
-- color from the default colors
function priv.defaultColor(no, total)
local color = lastColor
if no <= 0 then
return color
end
local size = #colorPalette
if not total or total == 0 then
total = size + 1
end
local colorNo = priv.defaultColorNo(no, total, size)
if colorNo > 0 then
color = colorPalette[colorNo]
end
return color
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
function priv.defaultColorNo(no, total, size)
local color = 0 -- special, lastColor
if total == 1 then
color = 1
elseif total <= colorGroupSize * (colorGroups - 1) then
if no < total then
color = no
else
local groupIndex = ((no - 1) % colorGroupSize)
if groupIndex == 0 then -- dark
color = no+1
elseif groupIndex == 1 then -- med
color = no+1
else
color = no
end
end
elseif no < total then
color = ((no - 1) % size) + 1
end
return color
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)
]]
function priv.test_defaultColorNo(total, size)
for no=1,total do
local color = 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
]]
function priv.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"))
function p.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("<", "<"):gsub(">", ">")
:gsub("'", "'"):gsub("\"", """)
return label
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"},
]]
function p.parseEnumParams(frame)
local args = frame:getParent().args
local result = {}
local i = 1
local sum = 0.0
local hasCustomColor = false -- has last custom color
while args["value" .. i] do
-- value is required in this mode; it's also assumed to be 0..100
local entry = { value = tonumber(args["value" .. i]) or 0 }
-- label and color is optional
local label = args["label" .. i]
if label and label ~= "" then
entry.label = label
end
hasCustomColor = false
local color = args["color" .. i]
if color and color ~= "" 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
for _, entry in ipairs(result) do
local label = entry.label
if label and not label:find("%$v") then
-- autoscale will be forced, so use $v in labels
if sum > 100 then
entry.label = label .. " $v"
else
entry.label = label .. " (" .. entry.value .. "%)"
end
end
end
-- support other value mapping
local lang = mw.language.getContentLanguage()
local langOther = "Other"
if (lang:getCode() == 'pl') then
langOther = "Inne"
end
local colorOther = "#FEFDFD" -- white-ish for custom colors for best chance and contrast
if args["other"] and args["other"] ~= "" then
local value = 100 - sum
if value < 0 then
value = 0
end
local otherEntry = { label = (args["other-label"] or langOther) .. " ("..priv.formatNum(value).."%)" }
if args["other-color"] and args["other-color"] ~= "" then
otherEntry.color = args["other-color"]
else
otherEntry.color = colorOther
end
table.insert(result, otherEntry)
elseif sum < 100 then
if hasCustomColor then
table.insert(result, {visible = false, label = langOther .. " ($v)", color = colorOther})
else
table.insert(result, {visible = false, label = langOther .. " ($v)"})
end
end
local jsonString = mw.text.jsonEncode(result)
return jsonString
end
-- Function to check if a value is true-ish
local trueValues = { ["true"] = true, ["1"] = true, ["on"] = true, ["yes"] = true }
function priv.isTrueishValue(value)
-- should return nil for empty args (i.e. undefined i.e. default)
if not value or value == "" then return nil end
value = priv.trim(value)
if value == "" then return nil end
-- other non-empty are false
return trueValues[value:lower()] or false
end
--[[
Parse classic template params into JSON with chart meta data.
]]
function p.parseMetaParams(frame)
local args = frame:getParent().args
local meta = {}
-- default meta for value1..n parameters
-- ...and for thumb right/left
local thumb = args["thumb"]
if args["value1"] or (thumb and (thumb == "right" or thumb == "left")) then
meta.direction = "column-reverse"
meta.width = "min-content"
meta.size = 200
meta.legend = true
end
-- explicit meta param
if args["meta"] then
meta = mw.text.jsonDecode(args["meta"], mw.text.JSON_TRY_FIXING)
end
if args["size"] then meta.size = tonumber(args["size"]) end
if args["radius"] and tonumber(args["radius"]) then
meta.size = 2 * tonumber(args["radius"])
end
if args["autoscale"] then meta.autoscale = priv.isTrueishValue(args["autoscale"]) end
if args["legend"] then meta.legend = priv.isTrueishValue(args["legend"]) end
if args["ariahidechart"] then meta.ariahidechart = priv.isTrueishValue(args["ariahidechart"]) end
if args["direction"] and args["direction"] ~= "" then
meta.direction = args["direction"]:gsub("[^a-z0-9%-]", "")
end
if args["width"] and args["width"] ~= "" then
meta.width = args["width"]:gsub("[^a-z0-9%-]", "")
end
if args["caption"] and args["caption"] ~= "" then
meta.caption = args["caption"]
end
if args["footer"] and args["footer"] ~= "" then
meta.footer = args["footer"]
end
return mw.text.jsonEncode(meta)
end
return p