Module:Climate chart

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

-- we import this function to strip whitespace and normalize minus signs
-- in arguments, which can be done hyphen-minus (-), unicode minus (−), or html
-- reference minus − or similar. this is behavior preserving from the
-- wikitext version of the template
local pfexpr = mw.ext.ParserFunctions.expr

-- 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(args[from_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(pfexpr(args[n]))
		local maximum = tonumber(pfexpr(args[n+1]))
		local precipitation = tonumber(pfexpr(args[n+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_data[i], year[i]))
				: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

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.