模块:Pie chart

本页使用了标题或全文手工转换,现处于中国大陆简体模式
求闻百科,共笔求闻

此模块用于实现模板链接:{{Pie chart}}模板,该模板用于实现一个饼图。

此模板的函数module用于直接调用,template用于由模板调用。此外,_main可直接传入args表,这个args表中可以直接指定data字段,其值为表。例如,下面两段代码是等价的:

-- p 表示模块的返回值。
p._main {
    label1 = '第1项',
    value1 = '23',   -- 这个值也可以是直接的数字形式
    label2 = '第2项',
    value2 = 40
}

p._main {
    data = {
        {label = '第1项', value = '23'},
        {label = '第2项', value = '40'}
    }
}

另请参见Module:TableTools

上述文档内容嵌入自Module:Pie chart/doc编辑 | 历史
编者可以在本模块的沙盒创建 | 镜像和测试样例创建页面进行实验。
请将模块自身所属的分类添加在文档中。本模块的子页面
local p = {}
local tools = require 'Module:TableTools'
local yesno = require 'Module:Yesno'

local graphColor = {
	'#1F78B4',
	'#33A02C',
	'#E31A1C',
	'#FF7F00',
	'#6A3D9A',
	'#B15928',
	'#A6CEE3',
	'#B2DF8A',
	'#FB9A99',
	'#FDBF6F',
	'#CAB2D6',
	'#FFFF99',
	'#FEEBE2',
	'#A9A9A9',
}

function p._main(args, frame)
	local circleOnly = yesno(args.circleOnly)
	-- 图形部分
	local circle = mw.html.create 'div'
		:addClass 'PieChartCircle'
	
	local root = circleOnly and circle or mw.html.create 'div'
		:addClass 'PieChartTemplate thumb'
		:attr('aria-label', '饼图')
	
	local radius = args.radius  -- 这个值可以是字母或者数字,如果是数字,则不执行trim,以避免转换为string再转换为number的多余过程
	radius = type(radius) == 'string' and mw.text.trim(args.radius) or radius
	radius = radius ~= '' and radius or '100px'
	-- 参数radius应该是个css长度,如果不是,则自动加上单位
	if tonumber(radius) then
		radius = radius .. 'px'
	end
	root:css('--PieChartRadius', radius)
		
	local data = type(args.data) == 'table' and args.data or tools.numData(args, true)  -- 第二个参数表示 compress
	
	local from = args.from 
	from = type(from) == 'string' and mw.text.trim(from) or from
	from = from ~= '' and from or nil
	if tonumber(from) then
		from = 'calc(' .. from .. ' / var(--PieChartTotalValue) * 1turn)'
	end
	circle:css('--PieChartPiecesStart', from)
	
	local space = args.space and mw.text.trim(args.space)  -- 各项之间的间距,其类型为角度
	space = space ~= '' and space or nil
	if tonumber(space) then
		mw.addWarning(
			'调用[[Module:Pie chart]]吋的警告:参数space的值不能是数字(' .. tostring(space) .. '),而应该是一个CSS的角度值,例如10deg、0.05turn等')
		space = nil
	end
	circle:css('--PieChartSpace', space)
	
	local offset = args.offset and mw.text.trim(args.offset)
	offset = offset ~= '' and offset or nil
	if tonumber(offset) then
		mw.addWarning(
			'调用[[Module:Pie chart]]时的警告:参数offset的值不能是数字(' .. tostring(offset) .. '),而应该是一个CSS的长度值,例如10px、0.5em等')	
	end
	
	circle:css('--PieChartPieceOffset', offset)
	
	circle:tag 'div'
		:addClass 'PieChartHiddenText'
		:wikitext ('饼图,共' .. (#data) .. '个元素。')
	
	local stackedValue = 0
	local stackedSpaceNum = 0
	
	-- 计算total
	local total = tonumber(args.total)
	if args.total and not total then
		error ('调用[[Module:Pie chart]]时出现错误:参数total的值(' .. tostring(args.total) .. ')不是一个有效的数字')
	end
	if not total then
		total = 0
		for i, item in ipairs(data) do
			local value = tonumber(item.value) or 1
			if item.value and not value then
				error ('调用[[Module:PieChart]]时出现错误:' .. tostring(item.value) .. '不是一个有效的数字')
			end
			total = total + value
		end
	end
	for i, item in ipairs(data) do
		local value = tonumber(item.value) or 1
		local color = item.color or graphColor[i % #graphColor]
		
		local beginAngle = space and string.format(
			'calc((%s / var(--PieChartTotalValue)) * (1turn - var(--PieChartTotalSpace)) + %s * var(--PieChartSpace))',
			stackedValue, stackedSpaceNum) or string.format('calc((%s / var(--PieChartTotalValue)) * 1turn)', stackedValue)
		local endAngle = space and string.format(
			'calc((%s / var(--PieChartTotalValue)) * (1turn - var(--PieChartTotalSpace)) + %s * var(--PieChartSpace))',
			stackedValue + value, stackedSpaceNum) or string.format('calc((%s / var(--PieChartTotalValue)) * 1turn)', stackedValue + value)
		
		local piece = circle:tag 'div'
			:addClass 'PieChartPiece mw-no-invert'
			:css('--PieChartPieceBeginAngle', beginAngle)
			:css('--PieChartPieceEndAngle', endAngle)
			:css('--PieChartPieceColor', color)
			
		piece :tag 'div'
			:addClass 'PieChartPieceHiddenText'
			:wikitext('饼图元素,第' .. i .. '项,' .. 
				(item.label and ('标签内容:' .. mw.text.trim(item.label) .. ',') or '') ..
				('数值:' .. value) .. ',百分比:' .. (value / total * 100) .. '%。')
		
		if offset then
			piece:addClass 'PieChartPieceOffset'
		end
		
		piece:css('--PieChartRadius', item.radius)
		piece:css('--PieChartPieceOffset', item.offset)
		
		stackedValue = stackedValue + value
		stackedSpaceNum = stackedSpaceNum + 1
	end
	
	if yesno(args.other) then
		data[#data + 1] = {
			label = '其他',
			value = total - stackedValue
		}
	end
	
	circle:css{
		['--PieChartTotalValue'] = total,
		['--PieChartTotalSpace'] = space and 'calc(' .. stackedSpaceNum .. ' * var(--PieChartSpace))' or nil,
	}
	if circleOnly then
		-- 仅返回中间的圆形的部分
		return circle
	end
		
	local thumb = args.thumb and mw.text.trim(args.thumb)
	if not thumb or thum == '' then
		thumb = 'right'
	end
	if thumb == 'center' then
		root:addClass 'tnone'
	elseif thumb == 'none' or thumb == 'left' or thumb == 'right' then
		root:addClass('t' .. thumb)
	else
		error ("调用[[Module:Pie chart]]出现错误:未知的thumb参数:" .. tostring(thumb) .. ",可接受的参数包括:left、center、right")
	end
	
	root:cssText(args.style)
	
	local thumbinner = root:tag 'div'
		:addClass 'thumbinner'
	
	thumbinner:node(circle)
	
	local caption = args.caption and mw.text.trim(args.caption)
	caption = caption ~= '' and caption or nil
	local legends = yesno(args.legends, true) ~= false  -- 未指定时为 true
	local footer = args.footer and mw.text.trim(args.footer)
	footer = footer ~= '' and footer or nil
	
	if caption or legends or footer then
		local thumbCaption = thumbinner:tag 'div'
			:addClass 'thumbcaption'
		
		if caption then
			thumbCaption:wikitext(args.caption):newline()
		end
		
		if legends then
			local g_info = args.info and mw.text.trim(args.info) or 'percentage'
			for i, item in ipairs(data) do
				local value = tonumber(item.value) or 1
				local color = item.color or graphColor[i % #graphColor]
				local info = (item.info and mw.text.trim(item.info)) or g_info
				assert(frame, '未提供参数:frame')
				local infoContent
				if info == 'value' then
					infoContent = value
				elseif info == 'percentage' then
					infoContent = (value / total * 100) .. '%'
				end
				
				local label = item.label
				if label and infoContent then
					label = label .. '(' .. infoContent .. ')'
				elseif infoContent then
					label = infoContent
				end
				
				if label then
					local legend = frame:expandTemplate{
						title = 'Template:Legend',
						args = {
							color,
							label
						}
					}
					thumbCaption:wikitext(legend)
				end
			end
		end
		
		if footer then
			thumbCaption:tag 'div'
				:addClass 'PieChartFooter'
				:newline()
				:wikitext(footer)
				:newline()
		end
	end
	
	return root
end

function p.module(frame)
	return p._main(frame.args, frame)
end

function p.template(frame)
	return p.module(frame:getParent())
end

return p