Module:Sandbox/Izno/Pie chart

From Wikipedia, the free encyclopedia
local p = {}
local compress_sparse_array = require('Module:TableTools').compressSparseArray

local function arg_or_default(arg, default)
	if arg and mw.text.trim(arg) ~= '' then
		return arg
	else
		return default
	end
end

local incoming_args = {
	['other-color'] = 'other_color_default()',
	style = nil,
	thumb = nil,
	labeln = nil,
	valuen = nil,
	colorn = 'get_graph_color(n)',
	caption = nil,
	other = nil,
	footer = nil
}

local graph_colors = {
	'#1F78B4',
	'#33A02C',
	'#FF7F00',
	'#6A3D9A',
	'#B15928',
	'#A6CEE3',
	'#B2DF8A',
	'#FB9A99',
	'#FDBF6F',
	'#CAB2D6',
	'#FFFF99',
	'#FEEBE2',
	'#A9A9A9'
}
	
local function get_graph_color(nth_item)
	return graph_colors[math.max(math.min(nth_item, #graph_colors), 1)]
end

local function get_css_for_slices(args)
	local slices = {}
	local degree_ratio = 360/100

	for k, v in pairs(args) do
		if k:match('value%d+') then
			local number = k:find('(%d+)')
			slices[number] = { value = tonumber(v) * degree_ratio }
		end
	end
	
	-- second loop because we need to guarantee that we've found all the potential slices
	-- before attempting to add their labels and colors
	for k, v in pairs(args) do
		if k:match('label%d+') then
			local number = k:find('(%d)')
			if slices[number] then -- if we don't have a value, then no label set
				slices[number]['label'] = v
			end
		end
		if k:match('color%d+') then
			local number = k:find('(%d)')
			if slices[number] then -- if we don't have a value, then no color set
				slices[number]['color'] = v
			end
		end
	end
	
	compress_sparse_array(slices)
	local conical_css_for_slices = {}
	local accumulated_value = 0
	
	for i, slice in ipairs(slices) do
		local value = slice.value
		local color = slice.color or get_graph_color(i)
		local next_accumulated_value = accumulated_value + value
		
		if i == 1 then
			-- we only need the end degree for the first
			table.insert(conical_css_for_slices, color .. ' ' .. value .. 'deg,')
		elseif i > 1 and i < #slices then
			-- need both start and end for the first, start is the previous end
			table.insert(conical_css_for_slices, color .. ' ' .. accumulated_value .. 'deg ' .. next_accumulated_value .. 'deg,')
		elseif i == #slices then
			-- we only need the start degree for the last
			table.insert(conical_css_for_slices, color .. ' ' .. accumulated_value .. 'deg')
		end
		
		accumulated_value = next_accumulated_value
	end
	return 'background: conic-gradient(' .. table.concat(conical_css_for_slices) .. ')'
end

function p._main(args)
	
	local root = mw.html.create('div')
	root:addClass('pie-chart thumb')
		:cssText(arg_or_default(args.style, nil))
	
	local thumb = arg_or_default(args.thumb, nil)
	local thumb_class = 'tright'
	if thumb and (thumb == 'center' or thumb == 'none') then
		thumb_class = 'tnone'
	elseif thumb and thumb == 'left' then
		thumb_class = 'tleft'
	end
	root:addClass(thumb_class)
	
	local radius = tonumber(arg_or_default(args.radius, 100))
	local diameter = radius * 2
	root:tag('div')
		:addClass('pie-chart-inner thumbinner')
		:css('width', (diameter + 2) .. 'px')
	
	local chart = mw.html.create('div')
	chart:addClass('pie-chart-chart mw-no-invert')
	chart:css('border-radius', '50%') -- can be in pie chart/styles.css if we have one
	chart:css('width', diameter .. 'px')
	chart:css('height', diameter .. 'px')
	
	local conic_css = get_css_for_slices(args)
	chart:cssText(conic_css)

	if thumb and thumb == 'center' then
		local root_container = mw.html.create('div')
		root_container:addClass('pie-chart-container center')
			:node(root)
		return root_container
	end

	return root
	
end

function p.main(frame)
	return p._main(frame:getParent().args)
end

return p