Modul:Piechart: Perbedaan antara revisi
Tampilan
Konten dihapus Konten ditambahkan
RaFaDa20631 (bicara | kontrib) Tidak ada ringkasan suntingan Tag: Dikembalikan |
RaFaDa20631 (bicara | kontrib) Membalikkan revisi 25325248 oleh RaFaDa20631 (bicara) Tag: Pembatalan |
||
Baris 1: | Baris 1: | ||
local p = {} |
local p = {} |
||
--Kode ini berasal dari plwiki Moduł:Piechart dengan atribusi oleh User:Nux |
--Kode ini berasal dari plwiki Moduł:Piechart dengan atribusi oleh User:Nux |
||
--[[ |
|||
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.wiki-indonesia.club). |
|||
--[[ |
--[[ |
||
Debug: |
Debug: |
||
Baris 58: | Baris 35: | ||
local html = p.renderPie(json_data, '{"autoscale":true}') |
local html = p.renderPie(json_data, '{"autoscale":true}') |
||
mw.logObject(html) |
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(trim(frame.args[1])) |
|||
return ' ' .. defaultColor(index) |
|||
end |
|||
--[[ |
--[[ |
||
Piechart. |
Piechart. |
||
{{{1}}}: |
|||
[ |
|||
{ "label": "k: $v", "value": 33.1 }, |
|||
{ "label": "m: $v", "value": -1 }, |
|||
] |
|||
where $v is a formatted label |
|||
TODO: |
TODO: |
||
- [x] basic 2-element pie chart |
- [x] basic 2-element pie chart |
||
Baris 96: | Baris 66: | ||
- [x] sanitize user values |
- [x] sanitize user values |
||
- [x] auto colors |
- [x] auto colors |
||
- |
- function to get color by number (for custom legend) |
||
- generate a legend |
|||
- [x] remember and show autoscaled data |
|||
- (?) $info: $values.join(separator) |
|||
- [x] generate a legend |
|||
- (?) or a list with css formatting (that could be overriden) |
|||
- [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 |
- (?) option to sort entries by value |
||
]] |
]] |
||
Baris 122: | Baris 81: | ||
local html = p.renderPie(json_data, options) |
local html = p.renderPie(json_data, options) |
||
return trim(html) |
return trim(html) |
||
end |
|||
-- Setup chart options. |
|||
function p.setupOptions(json_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 = "", |
|||
} |
|||
if json_options then |
|||
local rawOptions = mw.text.jsonDecode(json_options) |
|||
if rawOptions then |
|||
if type(rawOptions.size) == "number" then |
|||
options.size = math.floor(rawOptions.size) |
|||
end |
|||
options.autoscale = rawOptions.autoscale or false |
|||
if rawOptions.legend then |
|||
options.legend = true |
|||
end |
|||
if rawOptions.ariahidechart then |
|||
options.ariahidechart = true |
|||
end |
|||
if (type(rawOptions.direction) == "string") then |
|||
-- Remove unsafe/invalid characters |
|||
local sanitized = rawOptions.direction:gsub("[^a-z0-9%-]", "") |
|||
-- also adjust width so that row-reverse won't push things to the right |
|||
options.direction = 'width: max-content; flex-direction: ' .. sanitized |
|||
end |
|||
end |
|||
end |
|||
if (options.legend) then |
|||
options.ariahidechart = true |
|||
end |
|||
return options |
|||
end |
end |
||
Baris 172: | Baris 90: | ||
function p.renderPie(json_data, json_options) |
function p.renderPie(json_data, json_options) |
||
local data = mw.text.jsonDecode(json_data) |
local data = mw.text.jsonDecode(json_data) |
||
local options = |
local options = nil |
||
if json_options then |
|||
options = mw.text.jsonDecode(json_options) |
|||
-- prepare |
|||
local ok, total = p.prepareEntries(data, options) |
|||
-- init render |
|||
local html = "<div class='smooth-pie-container' style='"..options.direction.."'>" |
|||
-- error info |
|||
if not ok then |
|||
html = html .. renderErrors(data) |
|||
end |
end |
||
local size = options and type(options.size) == "number" and math.floor(options.size) or 100 -- circle size in [px] |
|||
local autoscale = options and options.autoscale or false -- autoscale values |
|||
-- Move the last element to the first position |
|||
-- render legend |
|||
local lastEntry = table.remove(data) |
|||
if options.legend then |
|||
table.insert(data, 1, lastEntry) |
|||
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 |
|||
local html = "" |
|||
-- Prepare data (slices etc) |
|||
function p.prepareEntries(data, options) |
|||
local sum = sumValues(data); |
local sum = sumValues(data); |
||
-- force autoscale when over 100 |
-- force autoscale when over 100 |
||
if (sum > 100) then |
if (sum > 100) then |
||
autoscale = true |
|||
end |
end |
||
local first = true |
|||
-- pre-format entries |
|||
local |
local previous = 0 |
||
local |
local totalCount = #data |
||
local total = #data |
|||
for index, entry in ipairs(data) do |
for index, entry in ipairs(data) do |
||
local html_slice, value = renderSlice(entry, previous, sum, size, index, autoscale, totalCount) |
|||
no = no + 1 |
|||
html = html .. html_slice |
|||
if not prepareSlice(entry, no, sum, total, options) then |
|||
if not first then |
|||
no = no - 1 |
|||
previous = previous + value |
|||
ok = false |
|||
end |
|||
first = false |
|||
end |
end |
||
html = html .. '\n</div>' |
|||
return |
return html |
||
end |
end |
||
Baris 234: | Baris 134: | ||
end |
end |
||
--[[ |
|||
-- render error info |
|||
Render a single slice. |
|||
function renderErrors(data) |
|||
local html = "\n<ol class='chart-errors' style='display:none'>" |
|||
@param entry Current entry. |
|||
@param sum Sum of all entries. |
|||
if entry.error then |
|||
]] |
|||
entryJson = mw.text.jsonEncode(entry) |
|||
function renderSlice(entry, previous, sum, size, index, autoscale, totalCount) |
|||
html = html .. "\n<li>".. entryJson .."</li>" |
|||
local value, label, bcolor = genSlice(entry, sum, index, autoscale, totalCount) |
|||
end |
|||
local html = "" |
|||
if (index==1) then |
|||
html = renderFinal(label, bcolor, size) |
|||
else |
|||
html = renderOther(value, previous, label, bcolor, size) |
|||
end |
end |
||
return html |
return html, value |
||
end |
end |
||
-- Prepare single slice data. |
|||
function genSlice(entry, sum, index, autoscale, totalCount) |
|||
-- Prepare single slice data (modifies entry). |
|||
function prepareSlice(entry, no, sum, total, options) |
|||
local autoscale = options.autoscale |
|||
local value = entry.value |
local value = entry.value |
||
if (type(value) ~= "number" or value < 0) then |
if (type(value) ~= "number" or value < 0) then |
||
if autoscale then |
if autoscale then |
||
return "<!-- cannot autoscale unknown value -->" |
|||
return false |
|||
end |
end |
||
value = 100 - sum |
|||
end |
end |
||
-- entry.raw only when scaled |
|||
if autoscale then |
if autoscale then |
||
value = (value / sum) * 100 |
|||
value = (value / sum) * 100 |
|||
end |
end |
||
entry.value = value |
|||
local label = formatValue(entry.label, value) |
|||
-- prepare final label |
|||
local bcolor = backColor(entry, index, totalCount) |
|||
-- prepare final slice bg color |
|||
return value, label, bcolor |
|||
local index = no |
|||
if no == total then |
|||
index = -1 |
|||
end |
|||
entry.bcolor = backColor(entry, index) |
|||
return true |
|||
end |
|||
-- render legend for pre-processed entries |
|||
function p.renderLegend(data, options) |
|||
local html = "\n<ol class='smooth-pie-legend'>" |
|||
for _, entry in ipairs(data) do |
|||
if not entry.error then |
|||
html = html .. renderLegendItem(entry, options) |
|||
end |
|||
end |
|||
return html .. "\n</ol>\n" |
|||
end |
|||
-- render legend item |
|||
function renderLegendItem(entry, options) |
|||
local label = entry.label |
|||
local bcolor = entry.bcolor |
|||
local html = "\n<li>" |
|||
html = html .. '<span class="l-color" style="'..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 = renderFinal(entry, options) |
|||
else |
|||
items = items .. renderOther(previous, entry, options) |
|||
end |
|||
previous = previous + entry.value |
|||
end |
|||
end |
|||
local footer = '\n</div>' |
|||
return header, items, footer |
|||
end |
end |
||
-- final, but header... |
-- final, but header... |
||
function renderFinal( |
function renderFinal(label, bcolor, size) |
||
local |
local html = "" |
||
local bcolor = entry.bcolor |
|||
local size = options.size |
|||
-- hide chart for readers, especially when legend is there |
|||
local aria = "" |
|||
if (options.ariahidechart) then |
|||
aria = 'aria-hidden="true"' |
|||
end |
|||
-- slices container and last slice |
|||
local style = 'width:'..size..'px; height:'..size..'px;'..bcolor |
local style = 'width:'..size..'px; height:'..size..'px;'..bcolor |
||
html = [[ |
|||
<div class="smooth-pie" |
<div class="smooth-pie" |
||
style="]]..style..[[" |
|||
title="]]..label..[[" |
|||
]]..aria..[[ |
|||
>]] |
>]] |
||
return html |
return html |
||
end |
end |
||
-- any other then final |
-- any other then final |
||
function renderOther(previous, |
function renderOther(value, previous, label, bcolor, size) |
||
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 html = "" |
||
local trans = string.format("translatex(%.0fpx)", size/2) |
|||
local size = '' |
|||
local maskStyle = getMaskStyle(previous) |
|||
-- mw.logObject({'v,p,l', value, previous, label}) |
|||
if (value |
if (value < 50) then |
||
local rotate = string.format("rotate(-%.3fturn)", value/100) |
|||
html = sliceWithClass('pie50', 50, value, previous, bcolor, label) |
|||
local transform = 'transform: scale(-1, 1) ' .. rotate .. ' ' .. trans ..';' |
|||
elseif (value >= 25) then |
|||
html = html .. '\n\t<div class="piemask" '..maskStyle..'><div class="slice" style="'..transform..' '..bcolor..'" title="'..label..'"></div></div>' |
|||
html = sliceWithClass('pie25', 25, value, previous, bcolor, label) |
|||
elseif (value >= 12.5) then |
|||
html = sliceWithClass('pie12-5', 12.5, value, previous, bcolor, label) |
|||
elseif (value >= 7) then |
|||
html = sliceWithClass('pie7', 7, value, previous, bcolor, label) |
|||
elseif (value >= 5) then |
|||
html = sliceWithClass('pie5', 5, value, previous, bcolor, label) |
|||
else |
else |
||
-- |
-- 50% |
||
html = html .. '\n\t<div class="piemask" '..maskStyle..'><div class="slice" style="'..bcolor..'" title="'..label..'"></div></div>' |
|||
local cutIndex = round(value*10) |
|||
-- value overflowing 50% (extra slice) |
|||
if cutIndex < 1 then |
|||
if (value > 50) then |
|||
cutIndex = 1 |
|||
maskStyle = getMaskStyle(previous + 50) |
|||
local rotate = string.format("rotate(-%.3fturn)", (value-50)/100) |
|||
local transform = 'transform: scale(-1, 1) ' .. rotate .. ' ' .. trans ..';' |
|||
html = html .. '\n\t<div class="piemask" '..maskStyle..'><div class="slice" style="'..transform..' '..bcolor..'" title="'..label..'"></div></div>' |
|||
end |
end |
||
local cut = p.cuts[cutIndex] |
|||
local transform = rotation(previous) |
|||
html = sliceX(cut, transform, bcolor, label) |
|||
end |
|||
-- mw.log(html) |
|||
return html |
|||
end |
|||
-- round to int |
|||
function round(number) |
|||
return math.floor(number + 0.5) |
|||
end |
|||
-- render full slice with specific class |
|||
function sliceWithClass(sizeClass, sizeStep, value, previous, bcolor, label) |
|||
local transform = rotation(previous) |
|||
local html = "" |
|||
html = html .. sliceBase(sizeClass, transform, bcolor, label) |
|||
-- mw.logObject({'sliceWithClass:', sizeClass, sizeStep, value, previous, bcolor, label}) |
|||
if (value > sizeStep) then |
|||
local extra = value - sizeStep |
|||
transform = rotation(previous + extra) |
|||
-- mw.logObject({'sliceWithClass; extra, transform', extra, transform}) |
|||
html = html .. sliceBase(sizeClass, transform, bcolor, label) |
|||
end |
end |
||
return html |
return html |
||
end |
end |
||
-- style of a mask (rotate into place) |
|||
function getMaskStyle(previous) |
|||
-- render single slice |
|||
if (previous>0) then |
|||
function sliceBase(sizeClass, transform, bcolor, label) |
|||
local maskRotate = string.format("rotate(%.3fturn)", previous/100) |
|||
local style = bcolor |
|||
local maskStyle = 'style="transform: '..maskRotate..';"' |
|||
if transform ~= "" then |
|||
return maskStyle |
|||
style = style .. '; ' .. transform |
|||
end |
|||
return '\n\t<div class="'..sizeClass..'" style="'..style..'" title="'..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 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="'..label..'"></div>' |
|||
end |
|||
-- translate value to turn rotation |
|||
function rotation(value) |
|||
if (value > 0) then |
|||
return string.format("transform: rotate(%.3fturn)", value/100) |
|||
end |
end |
||
return '' |
return '' |
||
end |
end |
||
-- Language sensitive float. |
|||
function formatNum(value) |
function formatNum(value) |
||
local lang = mw.language.getContentLanguage() |
local lang = mw.language.getContentLanguage() |
||
Baris 443: | Baris 226: | ||
end |
end |
||
function formatValue(label, value) |
|||
--[[ |
|||
local v = formatNum(value) |
|||
Prepare final label. |
|||
local l = "" |
|||
if label then |
|||
Typical tpl: |
|||
l = label:gsub("%$v", v..'%%') |
|||
"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 prepareLabel(tpl, entry) |
|||
-- static tpl |
|||
if tpl and not string.find(tpl, '$') then |
|||
return tpl |
|||
end |
|||
-- format % value without % |
|||
local p = formatNum(entry.value) |
|||
-- default template |
|||
if not tpl then |
|||
tpl = "$v" |
|||
end |
|||
local label = "" |
|||
if entry.raw then |
|||
label = tpl:gsub("%$p", p .. "%%"):gsub("%$d", entry.raw):gsub("%$v", entry.raw .. " (" .. p .. "%%)") |
|||
else |
else |
||
l = v .. "%" |
|||
end |
end |
||
return |
return l |
||
end |
end |
||
Baris 490: | Baris 249: | ||
} |
} |
||
local lastColor = '#cdf099' |
local lastColor = '#cdf099' |
||
function backColor(entry, no, totalCount) |
|||
-- background color from entry or the default colors |
|||
function backColor(entry, no) |
|||
if (type(entry.color) == "string") then |
if (type(entry.color) == "string") then |
||
-- Remove unsafe characters from entry.color |
-- Remove unsafe characters from entry.color |
||
Baris 497: | Baris 255: | ||
return 'background-color: ' .. sanitizedColor |
return 'background-color: ' .. sanitizedColor |
||
else |
else |
||
local color = |
local color = lastColor |
||
if (no > 1) then |
|||
local cIndex = (no - 1) % #colorPalette + 1 |
|||
color = colorPalette[cIndex] |
|||
end |
|||
mw.log(no, color) |
|||
return 'background-color: ' .. color |
return 'background-color: ' .. color |
||
end |
end |
||
end |
|||
-- color from the default colors |
|||
-- last entry color for 0 or -1 |
|||
function defaultColor(no) |
|||
local color = lastColor |
|||
if (no > 0) then |
|||
local cIndex = (no - 1) % #colorPalette + 1 |
|||
color = colorPalette[cIndex] |
|||
end |
|||
return color |
|||
end |
end |
||
Revisi terkini sejak 18 Februari 2024 08.46
local p = {}
--Kode ini berasal dari plwiki Moduł:Piechart dengan atribusi oleh User:Nux
--[[
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)
]]
--[[
Piechart.
{{{1}}}:
[
{ "label": "k: $v", "value": 33.1 },
{ "label": "m: $v", "value": -1 },
]
where $v is a formatted label
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
- function to get color by number (for custom legend)
- generate a legend
- (?) $info: $values.join(separator)
- (?) or a list with css formatting (that could be overriden)
- (?) option to sort entries by value
]]
function p.pie(frame)
local json_data = trim(frame.args[1])
local options = nil
if (frame.args.meta) then
options = trim(frame.args.meta)
end
local html = p.renderPie(json_data, options)
return trim(html)
end
--[[
Render piechart.
@param json_data JSON string with pie data.
]]
function p.renderPie(json_data, json_options)
local data = mw.text.jsonDecode(json_data)
local options = nil
if json_options then
options = mw.text.jsonDecode(json_options)
end
local size = options and type(options.size) == "number" and math.floor(options.size) or 100 -- circle size in [px]
local autoscale = options and options.autoscale or false -- autoscale values
-- Move the last element to the first position
local lastEntry = table.remove(data)
table.insert(data, 1, lastEntry)
local html = ""
local sum = sumValues(data);
-- force autoscale when over 100
if (sum > 100) then
autoscale = true
end
local first = true
local previous = 0
local totalCount = #data
for index, entry in ipairs(data) do
local html_slice, value = renderSlice(entry, previous, sum, size, index, autoscale, totalCount)
html = html .. html_slice
if not first then
previous = previous + value
end
first = false
end
html = html .. '\n</div>'
return html
end
function sumValues(data)
local sum = 0;
for _, entry in ipairs(data) do
local value = entry.value
if not (type(value) ~= "number" or value < 0) then
sum = sum + value
end
end
return sum
end
--[[
Render a single slice.
@param entry Current entry.
@param sum Sum of all entries.
]]
function renderSlice(entry, previous, sum, size, index, autoscale, totalCount)
local value, label, bcolor = genSlice(entry, sum, index, autoscale, totalCount)
local html = ""
if (index==1) then
html = renderFinal(label, bcolor, size)
else
html = renderOther(value, previous, label, bcolor, size)
end
return html, value
end
-- Prepare single slice data.
function genSlice(entry, sum, index, autoscale, totalCount)
local value = entry.value
if (type(value) ~= "number" or value < 0) then
if autoscale then
return "<!-- cannot autoscale unknown value -->"
end
value = 100 - sum
end
if autoscale then
value = (value / sum) * 100
end
local label = formatValue(entry.label, value)
local bcolor = backColor(entry, index, totalCount)
return value, label, bcolor
end
-- final, but header...
function renderFinal(label, bcolor, size)
local html = ""
local style = 'width:'..size..'px; height:'..size..'px;'..bcolor
html = [[
<div class="smooth-pie"
style="]]..style..[["
title="]]..label..[["
>]]
return html
end
-- any other then final
function renderOther(value, previous, label, bcolor, size)
local html = ""
local trans = string.format("translatex(%.0fpx)", size/2)
local maskStyle = getMaskStyle(previous)
if (value < 50) then
local rotate = string.format("rotate(-%.3fturn)", value/100)
local transform = 'transform: scale(-1, 1) ' .. rotate .. ' ' .. trans ..';'
html = html .. '\n\t<div class="piemask" '..maskStyle..'><div class="slice" style="'..transform..' '..bcolor..'" title="'..label..'"></div></div>'
else
-- 50%
html = html .. '\n\t<div class="piemask" '..maskStyle..'><div class="slice" style="'..bcolor..'" title="'..label..'"></div></div>'
-- value overflowing 50% (extra slice)
if (value > 50) then
maskStyle = getMaskStyle(previous + 50)
local rotate = string.format("rotate(-%.3fturn)", (value-50)/100)
local transform = 'transform: scale(-1, 1) ' .. rotate .. ' ' .. trans ..';'
html = html .. '\n\t<div class="piemask" '..maskStyle..'><div class="slice" style="'..transform..' '..bcolor..'" title="'..label..'"></div></div>'
end
end
return html
end
-- style of a mask (rotate into place)
function getMaskStyle(previous)
if (previous>0) then
local maskRotate = string.format("rotate(%.3fturn)", previous/100)
local maskStyle = 'style="transform: '..maskRotate..';"'
return maskStyle
end
return ''
end
function formatNum(value)
local lang = mw.language.getContentLanguage()
-- doesn't do precision :(
-- local v = lang:formatNum(value)
local v = string.format("%.1f", value)
if (lang:getCode() == 'pl') then
v = v:gsub("%.", ",")
end
return v
end
function formatValue(label, value)
local v = formatNum(value)
local l = ""
if label then
l = label:gsub("%$v", v..'%%')
else
l = v .. "%"
end
return l
end
-- default colors
local colorPalette = {
'#005744',
'#006c52',
'#00814e',
'#009649',
'#00ab45',
'#00c140',
'#00d93b',
'#00f038',
}
local lastColor = '#cdf099'
function backColor(entry, no, totalCount)
if (type(entry.color) == "string") then
-- Remove unsafe characters from entry.color
local sanitizedColor = entry.color:gsub("[^a-zA-Z0-9#%-]", "")
return 'background-color: ' .. sanitizedColor
else
local color = lastColor
if (no > 1) then
local cIndex = (no - 1) % #colorPalette + 1
color = colorPalette[cIndex]
end
mw.log(no, color)
return 'background-color: ' .. color
end
end
--[[
trim string
note:
`(s:gsub(...))` returns only a string
`s:gsub(...)` returns a string and a number
]]
function trim(s)
return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end
return p