Modul:Vremenski okvir/Dijagram

S Wikipedije, slobodne enciklopedije

Dokumentaciju za ovaj modul možete napraviti na stranici Modul:Vremenski okvir/Dijagram/dok

--
-- VREMENSKI OKVIR - DIJAGRAM
--

w = {};
math_mod = require('Module:Math');

local months = {"jan", "feb", "mar", "apr", "maj", "jun",
		"jul", "aug", "sep", "okt", "nov", "dec"}
local max_height = 200
local d_args = {
	hide          = 'sakrij',
	collapsed     = 'sklopi',
	label         = 'Temperatura u °C • padavine u mm',
	diagram       = 'Dijagram:',
	temperature   = 'Temperatura',
	precipitation = 'Padavine'
}

-- Lets the user use ',' instead of '.' i.e. 290,9 (290.9 works too).
local function convertToNumbers(table_arg)
	local hi_val = 1
	local map = {}
	for i,val in pairs(table_arg) do
		if val == nil then
			map[i] = nil
		else
			local to_dot = mw.ustring.gsub(val, ",", ".")
			local to_no = math_mod._cleanNumber(to_dot)
			map[i] = to_no
		end
	end
	
	return map
end

-- Get min value if min value is lesser than 0.
local function getMinFromTable(table_arg)
	local min_val = 0 -- the min_val should at least be 0
	
	for i,val in pairs(table_arg) do
		if val ~= nil then
			if val < min_val then
				min_val = val
			end
		end
	end
	return min_val
end

-- Get max value if max is greater than 1.
local function getMaxFromTable(table_arg)
	local max_val = 1
	
	for i,val in pairs(table_arg) do
		if val ~= nil then
			if val > max_val then
				max_val = val
			end
		end
	end
	return max_val
end

-- Renders a bar for the chart
-- the bar_info parameter is a table with keys:
-- height, bottom, horizontal_axis, above_label and below_label.
local function renderBar(html_root, bar_info)
	if bar_info == nil then
		return
	end

	local div = html_root:tag('div')
		:css('position', 'relative')
		:css('z-index', '100')
		:css('margin', 'auto')
		:css('width', '17px')
		:css('height', '216px')
		:css('padding', '1px')

	-- add a horizontal axis
	if bar_info.horizontal_axis ~= nil then
		local zero_pos_px_str =  math_mod._round(bar_info.horizontal_axis, 1) .. 'px'
		div:tag('div')
			:css('position', 'absolute')
			:css('z-index', '2')
			:css('bottom', zero_pos_px_str)
			:css('width', '17px')
			:css('border-bottom', '1px dotted #A9A9A9')
	end
			
	-- add bar
	local height_str = math_mod._round(bar_info.height, 1) .. 'px'
	local bottom_str = math_mod._round(bar_info.bottom, 1) .. 'px'
	local bar = div:tag('div')
		:css('position', 'absolute')
		:css('z-index', '1')
		:css('left', '2px')
		:css('bottom', bottom_str)
		:css('width', '15px')
		:css('height', height_str)
		:css('overflow', 'hidden')
		:css('background', '#8AB0FF')
			
	-- Prepare bar label above, note: '+3' extra space for border-top.
	local label_map = {{
		bottom = math_mod._round(bar_info.height+bar_info.bottom+3, 1) .. 'px',
		label  = bar_info.above_label
	}}
	-- Prepare bar label below if necessary.
	if bar_info.below_label ~= nil then
		table.insert(label_map, { 
			bottom = math_mod._round(bar_info.bottom-16, 1) .. 'px',
			label = bar_info.below_label
		})

		-- Change css-style (-> we assume the bar is for temperature)
		-- these borders affects the height of the bar,
		-- if removed: change the height ('-4') in function 
		-- renderTemperatureDiagram and remove '+3' in label_map.bottom for 
		-- above label.
		bar:css('background', '#e8e8e8')
			:css('border-top', '2px solid red')
			:css('border-bottom', '2px solid blue')
	end

	-- Add bar labels (above and below).
	for i,v in ipairs(label_map) do
		div:tag('div')
			:css('position', 'absolute')
			:css('z-index', '4')
			:css('bottom', v.bottom)
			:css('width', '17px')
			:css('text-align', 'center')
			:css('font-size', '90%')
			:wikitext((mw.ustring.gsub( (mw.ustring.gsub( v.label, "%-", "−") ), "%.", ",") ))
	end
end

local function renderTemperatureDiagram(html_root, table_min, table_max)
	html_root:tag('tr')
		:tag('td')
			:attr('colspan', '13')
			:css('background', '#eee')
			:css('text-align', 'center')
			:css('font-weight', 'bold')
			:wikitext(d_args.temperature)
			
	local f_tr = html_root:tag('tr')
		:css('vertical-align', 'bottom')
	
	-- offset, border-left
	f_tr:tag('td')
		:css('border-left', '1px solid #A9A9A9')
		:css('border-bottom', '1px solid #A9A9A9')
		
	local min_map = convertToNumbers(table_min)
	local max_map = convertToNumbers(table_max)
	
	local min_val = getMinFromTable(min_map)
	local max_val = getMaxFromTable(max_map)
	
	local offset = 16
    local zero_pos = 0
	if min_val < 0 then
		zero_pos = min_val*(-1)
	end
	
	-- generate rows
	for i,v in ipairs(months) do
		-- check values
		if  min_map[tostring(v)] ~= nil and max_map[tostring(v)] ~= nil then
			-- check max and min value
			if min_map[tostring(v)] > max_map[tostring(v)] then
				-- this should not happen -> store nil everywhere
				min_map[tostring(v)] = nil
				max_map[tostring(v)] = nil
			end
		end
		
		local column = f_tr:tag('td')
			:css('border-bottom', '1px solid #A9A9A9')
		local bar_info = nil
		
		if  min_map[tostring(v)] ~= nil and max_map[tostring(v)] ~= nil then
			-- calculate height
			local bar_height = ((max_height-offset) / (max_val-min_val) 
				* (max_map[tostring(v)] - min_map[tostring(v)]))
			-- Remove some px from height 
			-- (from bar-style:border-top/-bottom in renderBar,
			-- see function renderBar for more info).
			if bar_height > 4 then
				bar_height = bar_height-4
			else
				bar_height = 0
			end

			bar_info = {
				height = bar_height,
				bottom = ((max_height-offset) / (max_val-min_val)
					* (zero_pos+min_map[tostring(v)])) + offset,
				horizontal_axis = (max_height-offset) / (max_val-min_val) 
					* zero_pos + offset,
				above_label = table_max[tostring(v)],
				below_label = table_min[tostring(v)]
			}
		end

		renderBar(column, bar_info)
	end
end

local function renderPrecipitationDiagram(html_root, table_arg)
	html_root:tag('tr')
		:tag('td')
			:attr('colspan', '13')
			:css('background', '#eee')
			:css('text-align', 'center')
			:css('font-weight', 'bold')
			:wikitext(d_args.precipitation)
			
	local f_tr = html_root:tag('tr')
		:css('vertical-align', 'bottom')
	
	-- offset, border-left
	f_tr:tag('td')
		:css('border-left', '1px solid #A9A9A9')
		:css('border-bottom', '1px solid #A9A9A9')
		
	-- convert to numbers and get the highest value
	local number_map = convertToNumbers(table_arg)
	local max_val = getMaxFromTable(number_map)
	
	-- generate rows
	for i,v in ipairs(months) do
		local column = f_tr:tag('td')
			:css('border-bottom', '1px solid #A9A9A9')
		local bar_info = nil
		
		if number_map[tostring(v)] ~= nil then
			bar_info = {
				height = max_height / max_val * number_map[tostring(v)],
				bottom = 0,
				above_label = number_map[tostring(v)]
			}
		end
		
		renderBar(column, bar_info)
	end
end

local function renderBarDiagram(html_root, width, colspan, args)
	local bd_table = html_root:tag('td')
		:css('width', width)
		:attr('colspan', colspan)
		:tag('table')
			:attr('border', '0')
			:attr('cellspacing', '0')
			:attr('cellpadding', '0')
			:attr('align', 'left')
			:css('width', '100%')
			:css('font-size', '75%')
			:css('background', '#f8f8f8')
			:css('text-align', 'center')
	
	local n = 0
  	for _ in pairs(args) do n = n + 1 end
	
	if n == 1 then
		renderPrecipitationDiagram(bd_table, args[1])
	elseif n == 2 then
		renderTemperatureDiagram(bd_table, args[1], args[2])
	end

	-- Render months along x-axis.
	local m_tr = bd_table:tag('tr')
		:css('background', '#FFFFFF')
	m_tr:tag('td') -- offset
	for i,v in ipairs(months) do
		m_tr:tag('td')
			:css('text-align', 'center')
			:wikitext(tostring(v))
	end
end

local function _diagram(table_args)
	-- (table) root
	local root = mw.html.create():tag('table')
		:addClass('mw-collapsible')
		:attr('cellspacing', '0')
		:css('background', 'transparent')
		:css('width', '100%')
	
	if table_args.state == d_args.hide or
		table_args.state == d_args.collapsed then
		root:addClass('mw-collapsed')
	end

	local header = root:tag('tr')
		:css('font-size', '85%')
	header:tag('td')
		:css('padding', '0')
		:wikitext(d_args.label)
	header:tag('th')
		:css('padding', '0')
		:css('font-weight', 'normal')
		:css('text-align', 'right')
		:wikitext(d_args.diagram)

	-- set diagram frame width
	local diagram_frame_width = '50%'
	local diagram_frame_colspan = '1'
	if tableIsEmpty(table_args.minTemperature) or tableIsEmpty(table_args.maxTemperature)
		or tableIsEmpty(table_args.precipitation) then
		diagram_frame_width = '100%'
		diagram_frame_colspan = '2'
	end
	
	local tr = root:tag('tr')
	local bd_args = nil
	if not tableIsEmpty(table_args.minTemperature) 
		and not tableIsEmpty(table_args.maxTemperature) then
		bd_args = { table_args.minTemperature, table_args.maxTemperature }
		renderBarDiagram(tr, diagram_frame_width, diagram_frame_colspan, bd_args)
	end
	
	if not tableIsEmpty(table_args.precipitation) then
		bd_args = { table_args.precipitation }
		renderBarDiagram(tr, diagram_frame_width, diagram_frame_colspan, bd_args)
	end
	
	return tostring(root)
end

function tableIsEmpty(t)
	if t == nil then
		return true
	end
    for _, _ in pairs(t) do
        return false
    end
    return true
end

function w.diagram(frame)
	if tableIsEmpty(frame) then
		return
	end
 
	return _diagram(frame)
end

return w