Template:Lua Template:Uses TemplateStyles
Implements {{climate chart}}.
Usage
{{#invoke:Climate chart|function_name}}
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(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 = '−' 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(' ')
:done()
:tag('div')
:addClass('climate-chart-column-precip-bar')
:wikitext(' ')
: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(' ')
:done()
:tag('div')
:addClass('climate-chart-column-temp-bar')
:wikitext(' ')
: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(args[n])
local maximum = tonumber(args[n+1])
local precipitation = tonumber(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