Permanently protected module
From Wikipedia, the free encyclopedia


local p = {}

local cfg = mw.loadData('Module:Climate chart/configuration')



-- from https://lua-users.org/wiki/SimpleRound

local function round(num, decimal_places)

	local mult = 10^(decimal_places or 0)

	return math.floor(num * mult + 0.5) / mult

end



local function arg_or_default(args, from_arg, default)

	local arg = mw.text.trim(argsfrom_arg or '')

	if arg ~= '' then

		return arg

	else

		return default

	end

end



-- we only draw using the metric numbers

local function compute_column_draw_data(metric_year, max_precipitation)

	local column_draw_data = {}

	-- so many magic constants

	local precipitation_scale = math.max(1, max_precipitation / 750) -- 750 mm is the maximum for height

	

	for _, month in ipairs(metric_year) do

		local precipitation_bar_height = month.precipitation / 50 / precipitation_scale -- 50 is a magic constant

		local temperature_bar_displacement = month.minimum / 5 + 8

		local temperature_bar_height = (month.maximum - month.minimum) / 5

		local temperature_high_displacement = month.maximum / 5 + 8

		local temperature_low_displacement = month.minimum / 5 + 6.5

		

		table.insert(column_draw_data, {

			precipitation_height = precipitation_bar_height,

			temperature_height = temperature_bar_height,

			temperature_displacement = temperature_bar_displacement,

			temperature_high_displacement = temperature_high_displacement,

			temperature_low_displacement = temperature_low_displacement

		})

	end

	

	return column_draw_data

end



local function present_monthly_temperature(temperature)

	local rounded_temp = round(temperature, 0)

	local temperature_sign = ''

	if rounded_temp < 0 then temperature_sign = '&minus;' end

	local abs_temp = math.abs(rounded_temp)

	

	return temperature_sign .. abs_temp

end



local function draw_column(month_draw_data, month_data)

	

	local precipitation = month_data.precipitation

	local precipitation_decimal_places = precipitation < 10 and 1 or 0

	local rounded_precipitation = round(precipitation, precipitation_decimal_places)

	

	local high_temp = present_monthly_temperature(month_data.maximum)

	local low_temp = present_monthly_temperature(month_data.minimum)

	

	local column = mw.html.create('div')

	column:addClass('climate-chart-column')

	:tag('div')

		:addClass('climate-chart-column-spacer')

		:wikitext('&nbsp;')

		:done()

	:tag('div')

		:addClass('climate-chart-column-precip-bar')

		:wikitext('&nbsp;')

		:css('height', month_draw_data.precipitation_height .. 'em')

		:css('print-color-adjust', 'exact') -- css sanitizer doesn't accept yet

		:done()

	:tag('div')

		:addClass('climate-chart-column-value climate-chart-column-precip')

		:tag('span')

			:wikitext(rounded_precipitation)

			:done()

		:done()

	:tag('div')

		:addClass('climate-chart-column-spacer2')

		:wikitext('&nbsp;')

		:done()

	:tag('div')

		:addClass('climate-chart-column-temp-bar')

		:wikitext('&nbsp;')

		:css('bottom', month_draw_data.temperature_displacement .. 'em' )

		:css('height', month_draw_data.temperature_height .. 'em')

		:css('print-color-adjust', 'exact') -- css sanitizer doesn't accept yet

		:done()

	:tag('div')

		:addClass('climate-chart-column-value climate-chart-column-high-temp')

		:css('bottom', month_draw_data.temperature_high_displacement .. 'em')

		:tag('span')

			:wikitext(high_temp)

			:done()

		:done()

	:tag('div')

		:addClass('climate-chart-column-value climate-chart-column-low-temp')

		:css('bottom', month_draw_data.temperature_low_displacement .. 'em')

		:tag('span')

			:wikitext(low_temp)

			:done()

		:done()

	:done()

	return column

end



local function header_row()

	local month_row = mw.html.create('tr')

	for _, month in ipairs(cfg.i18n.months) do

		month_row:tag('th')

			:attr('scope', 'col')

			:wikitext(month)

			:done()

	end

	

	return month_row:allDone()

end



local function fill_nice_tables(args)

	local primary_table = {}

	local secondary_table = {}

	for n = 2, 37, 3 do

		local minimum = tonumber(argsn])

		local maximum = tonumber(argsn+1])

		local precipitation = tonumber(argsn+2])

		 -- we use the fact that `tonumber` returns nil if it gets not_a_string

		 -- _OR_ the empty string later, since the defaults are unit-specific

		table.insert(primary_table, {

			minimum = minimum,

			maximum = maximum,

			precipitation = precipitation

		})

		table.insert(secondary_table, {

			minimum = minimum,

			maximum = maximum,

			precipitation = precipitation

		})

	end

	return primary_table, secondary_table

end



local function c_to_f(temperature_in_c)

	return temperature_in_c * 1.8 + 32

end



local function f_to_c(temperature_in_f)

	return (temperature_in_f - 32) * 5/9

end



local function mm_to_in(precipitation_in_mm)

	return precipitation_in_mm / 25.4	

end



local function in_to_mm(precipitation_in_in)

	return precipitation_in_in * 25.4

end



local function convert_inplace(t, convert_temperature, convert_precipitation)

	for _, month in ipairs(t) do

		month.minimum = convert_temperature(month.minimum)

		month.maximum = convert_temperature(month.maximum)

		month.precipitation = convert_precipitation(month.precipitation)

	end

end



local function fill_in_nils(year_t, default_t)

	for _, month in ipairs(year_t) do

		if not month.precipitation then month.precipitation = default_t.precipitation end

		if not month.maximum then month.maximum = default_t.temperature_high end

		if not month.minimum then month.minimum = default_t.temperature_low end

	end

end



local function chart_rows(args, imperial)

	local metric_t

	local imperial_t

	local maximum_precipitation = tonumber(args.maxprecip)

	local imperial_max_precipitation

	local metric_max_precipitation

	local default_max_precipitation = 1

	

	if imperial then

		imperial_t, metric_t = fill_nice_tables(args)

		fill_in_nils(metric_t, cfg.metric_default)

		fill_in_nils(imperial_t, cfg.imperial_default)

		convert_inplace(metric_t, f_to_c, in_to_mm, imperial)

		if maximum_precipitation then

			imperial_max_precipitation = maximum_precipitation

			metric_max_precipitation = in_to_mm(maximum_precipitation)

		else

			imperial_max_precipitation = default_max_precipitation

			metric_max_precipitation = default_max_precipitation

		end

	else

		metric_t, imperial_t = fill_nice_tables(args)

		fill_in_nils(metric_t, cfg.metric_default)

		fill_in_nils(imperial_t, cfg.imperial_default)

		convert_inplace(imperial_t, c_to_f, mm_to_in, imperial)

		if maximum_precipitation then

			metric_max_precipitation = maximum_precipitation

			imperial_max_precipitation = mm_to_in(maximum_precipitation)

		else

			metric_max_precipitation = default_max_precipitation

			imperial_max_precipitation = default_max_precipitation

		end

	end

	

	local column_draw_data = compute_column_draw_data(metric_t, metric_max_precipitation)

	

	local metric_row = mw.html.create('tr')

	local imperial_row = mw.html.create('tr')

	local function add_columns(row, year)

		for i = 1, 12 do

			row:tag('td')

				:node(draw_column(column_draw_datai], yeari]))

				:done()

		end

	end

	

	add_columns(metric_row, metric_t)

	add_columns(imperial_row, imperial_t)

	

	return metric_row, imperial_row

	

end



local function present_chart_content(args, imperial)

	

	local primary_row, secondary_row

	if imperial then

		secondary_row, primary_row = chart_rows(args, imperial)

	else

		primary_row, secondary_row = chart_rows(args, imperial)

	end

	

	local primary = mw.html.create('table')

		:addClass('climate-chart-primary climate-chart-internal')

		:node(header_row())

		:node(primary_row)

		:done()

		

	local secondary_chart = mw.html.create('table')

		:addClass('climate-chart-secondary climate-chart-internal')

		:node(header_row())

		:node(secondary_row)

		:done()

	

	local secondary_title = imperial and cfg.i18n.secondary_title_metric or cfg.i18n.secondary_title_imperial

	-- primary has html_chart

	

	return primary, {

		title = secondary_title,

		chart = secondary_chart

	}

end



local function wrap_secondary_content(chart_content, temp_explanation, precip_explanation)

	local ret = mw.html.create('div')

	ret:addClass('climate-chart-secondary mw-collapsible mw-collapsed')

		:tag('div')

			:addClass('climate-chart-secondary-title')

			:wikitext(chart_content.title)

			:done()

		:tag('div')

			:addClass('mw-collapsible-content')

			:node(chart_content.chart)

			:node(temp_explanation)

			:node(precip_explanation)

			:done()

	return ret

end



local function explain_bar(bar_type, text)

	local ret = mw.html.create('p')

	ret:addClass('climate-change-explain-bar-' .. bar_type)

		:tag('span')

			:wikitext(cfg.i18n.explainer_key)

			:done()

		:wikitext(text)

		:done()

	return ret

end



local function explain(imperial, bar_type, imperial_explanation, metric_explanation)

	if imperial then

		return explain_bar(bar_type, imperial_explanation),

			explain_bar(bar_type, metric_explanation)

	else

		return explain_bar(bar_type, metric_explanation),

			explain_bar(bar_type, imperial_explanation)

	end

end



local function add_source(source)

	if not source then return end

	return mw.html.create('p'):wikitext(string.format(cfg.i18n.source, source))

end



local function add_title_content(title)

	local ret = mw.html.create()

	ret:tag('div')

		:addClass('climate-chart-title')

		:wikitext(title)

		:done()

	:tag('div')

		:addClass('climate-chart-explainer')

		:wikitext(cfg.i18n.explainer)

		:done()

	return ret

end



function p._main(args)

	

	local float = arg_or_default(args, cfg.arg.float, nil)

	local float_class = nil

	if float then

		if float == 'right' then

			float_class = 'climate-chart-right'

		elseif float == 'left' then

			float_class = 'climate-chart-left'

		end

	end

	

	local clear = arg_or_default(args, cfg.arg.clear, nil) or float

	local units = string.lower(arg_or_default(args, cfg.arg.units, ''))

	local is_imperial_primary = units == cfg.keyword.imperial

	local title = add_title_content(arg_or_default(args, cfg.arg.title, ''))

	local primary_chart, secondary_chart_content = present_chart_content(

		args,

		is_imperial_primary

	)

	local primary_temp_explanation, secondary_temp_explanation = explain(

		is_imperial_primary,

		'temp',

		cfg.i18n.explainer_fahrenheit,

		cfg.i18n.explainer_celsius

	)

	local primary_precip_explanation, secondary_precip_explanation = explain(

		is_imperial_primary,

		'precip',

		cfg.i18n.explainer_in,

		cfg.i18n.explainer_mm

	)

	

	local source = add_source(arg_or_default(args, 'source', nil))

	

	local secondary_content = wrap_secondary_content(

		secondary_chart_content,

		secondary_temp_explanation,

		secondary_precip_explanation

	)

	

	local climate_chart = mw.html.create('div')

	climate_chart:addClass('climate-chart')

		:addClass(float_class)

		:css('clear', clear)

	

	climate_chart:node(title)

		:node(primary_chart)

		:node(primary_temp_explanation)

		:node(primary_precip_explanation)

		:node(source)

		:node(secondary_content)

		:allDone()

	return mw.getCurrentFrame():extensionTag{

		name = 'templatestyles', args = { src = 'Module:Climate chart/styles.css' }

	} .. tostring(climate_chart)

end



function p.main(frame)

	return p._main(frame:getParent().args)

end



return p