https://en.wikipedia.org/w/index.php?action=history&feed=atom&title=Module%3AWeather%2FstableSandbox
Module:Weather/stableSandbox - Revision history
2025-06-07T02:43:34Z
Revision history for this page on the wiki
MediaWiki 1.45.0-wmf.4
https://en.wikipedia.org/w/index.php?title=Module:Weather/stableSandbox&diff=742821650&oldid=prev
Erutuon: creating a copy of Module:Weather/sandbox that can be used in articles until my work on the actual sandbox is complete and I have merged it with the main module
2016-10-06T00:51:52Z
<p>creating a copy of <a href="/wiki/Module:Weather/sandbox" title="Module:Weather/sandbox">Module:Weather/sandbox</a> that can be used in articles until my work on the actual sandbox is complete and I have merged it with the main module</p>
<p><b>New page</b></p><div>export = {}<br />
degree = "°" -- used by add_unit_names()<br />
minus = "−" -- used by makeRow() and makeTable()<br />
thinSpace = mw.ustring.char(0x2009) -- used by makeCell()<br />
<br />
-- Error message handling<br />
message = ""<br />
<br />
local function add_message(new_message)<br />
if show then<br />
if check_for_string(message) then<br />
message = message .. " " .. new_message<br />
else<br />
message = "Notices: " .. new_message<br />
end<br />
end<br />
end<br />
<br />
-- Input and output parameters<br />
local function get_format (frame)<br />
local input_parameter = frame.args.input<br />
local output_parameter = frame.args.output<br />
<br />
if input_parameter == nil then<br />
error("Please provide the number of values and a unit in the input parameter")<br />
else<br />
length = tonumber(string.match(input_parameter, "(%d+)")) -- Find digits in the input parameter.<br />
input_unit = string.match(input_parameter, "([CF])") -- C or F<br />
if string.find(input_parameter, "[^CF%d%s]") then<br />
add_message("There are extraneous characters in the <span style=\"background-color: #EEE; font-family: monospace;\">output</span> parameter.")<br />
end<br />
end<br />
<br />
if input_unit == "C" then<br />
output_unit = "F"<br />
elseif input_unit == "F" then<br />
output_unit = "C"<br />
else<br />
error ("Please provide an input unit in the input parameter: F for Fahrenheit or C for Celsius", 0)<br />
end<br />
<br />
if length == nil then<br />
error ("get_format has not found a length value in the input parameter")<br />
end<br />
<br />
if output_parameter == nil then<br />
add_message("No output format has been provided in the <span style=\"background-color: #EEE; font-family: monospace;\">output</span> parameter.")<br />
else<br />
cell_format = {}<br />
local n = 1<br />
for unit in output_parameter:gmatch("[CF]") do<br />
cell_format[n] = unit<br />
n = n + 1<br />
if n > 2 then<br />
break<br />
end<br />
end<br />
local function set_format(key, formatVariable, formatValue1, formatValue2)<br />
if string.find(output_parameter, key) then<br />
cell_format[formatVariable] = formatValue1<br />
else<br />
cell_format[formatVariable] = formatValue2<br />
end<br />
end<br />
if cell_format[1] then<br />
cell_format.first = cell_format[1]<br />
else<br />
error("C or F not found in output parameter")<br />
end<br />
if cell_format[2] == nil then<br />
cell_format["convert_units"] = "no"<br />
else<br />
if cell_format[2] == cell_format[1] then<br />
error("There should not be two of the same unit name in the output parameter.")<br />
else<br />
cell_format["convert_units"] = "yes"<br />
end<br />
end<br />
set_format("unit", "unit_names", "yes", "no")<br />
set_format("no ?color", "color", "no", "yes")<br />
set_format("sort", "sortable", "yes", "no")<br />
set_format("full ?size", "small_font", "no", "yes")<br />
set_format("no ?brackets", "brackets", "no", "yes")<br />
set_format("round", "decimals", "0", "")<br />
if string.find(output_parameter, "line break") then<br />
cell_format["line_break"] = "yes"<br />
elseif string.find(output_parameter, "one line") then<br />
cell_format["line_break"] = "no"<br />
else<br />
cell_format["line_break"] = "auto"<br />
end<br />
if string.find(output_parameter, "one line") and string.find(output_parameter, "line break") then<br />
error("Place either \"one line\" or \"line break\" in the output parameter, not both")<br />
end<br />
end<br />
if frame.args.palette == nil then<br />
palette = "cool2avg"<br />
else<br />
palette = frame.args.palette<br />
end<br />
<br />
if frame.args.messages == "show" then<br />
show = true<br />
else<br />
show = false<br />
end<br />
<br />
return length, input_unit, output_unit<br />
end<br />
<br />
-- Number and string-handling functions<br />
local function check_for_number(value)<br />
return type(tonumber(value)) == "number"<br />
end<br />
<br />
function check_for_string(string)<br />
string = tostring(string)<br />
return string ~= "" and string ~= nil<br />
end<br />
<br />
local function round(value, decimals)<br />
value = tonumber(value)<br />
if type(value) == "number" then<br />
local string = string.format("%." .. decimals .. "f", value)<br />
return string<br />
elseif value == nil then<br />
value = "nil"<br />
add_message("Format was asked to operate on " .. value .. ", which cannot be converted to a number.", 2)<br />
return ""<br />
end<br />
end<br />
<br />
local function convert(value, decimals, unit) -- Unit is the unit being converted from. It defaults to input_unit.<br />
if not unit then<br />
unit = input_unit<br />
end<br />
if check_for_number(value) then<br />
local value = tonumber(value)<br />
if unit == "C" then<br />
add_message(value .. " " .. degree .. unit .. " was converted.")<br />
return round(value * 9/5 + 32, decimals)<br />
elseif unit == "F" then<br />
add_message(value .. " " .. degree .. unit .. " was converted.")<br />
return round((value - 32) * 5/9, decimals)<br />
else<br />
error("Input unit not recognized", 2)<br />
end<br />
else<br />
return "" -- Setting result to empty string if value is not a number avoids concatenation errors.<br />
end<br />
end<br />
<br />
-- Input parsing<br />
function make_array(parameter, array, frame)<br />
local array = {}<br />
local n = 1<br />
for number in parameter:gmatch("%-?%d+%.?%d?") do<br />
local number = number<br />
if n == 1 then<br />
local decimals = number:match("%.(%d+)")<br />
if decimals == nil then<br />
precision = "0"<br />
else<br />
precision = #decimals<br />
end<br />
end<br />
table.insert(array, n, number)<br />
n = n + 1<br />
if n > length then<br />
break<br />
end<br />
end<br />
if not array[length] then<br />
add_message("There are not " .. length .. " values in the " .. parameter .. " parameter.")<br />
end<br />
return array, precision<br />
end<br />
<br />
function make_arrays(frame)<br />
get_format(frame)<br />
local parameter_a = frame.args.a<br />
local parameter_b = frame.args.b<br />
local parameter_c = frame.args.c<br />
if parameter_a then<br />
a = make_array(parameter_a, a, frame)<br />
else<br />
error("Please provide a set of numbers in parameter a")<br />
end<br />
if parameter_b then<br />
b = make_array(parameter_b, b, frame)<br />
else<br />
add_message("There is no content in parameter <span style=\"background-color: #EEE; font-family: monospace;\">b</span>.")<br />
end<br />
if parameter_c then<br />
c = make_array(parameter_c, c, frame)<br />
else<br />
add_message("There is no content in parameter <span style=\"background-color: #EEE; font-family: monospace;\">c</span>.")<br />
end<br />
return a, b, c<br />
end<br />
<br />
-- Color generation<br />
<br />
palettes = {<br />
-- The first three arrays in each palette defines background color using a table of four numbers,<br />
-- say { 11, 22, 33, 44 } (values in °C).<br />
-- That means the color is 0 below 11 and above 44, and is 255 from 22 to 33.<br />
-- The color rises from 0 to 255 between 11 and 22, and falls between 33 and 44.<br />
cool = {<br />
{ -42.75, 4.47, 41.5, 60 },<br />
{ -42.75, 4.47, 4.5, 41.5 },<br />
{ -90 , -42.78, 4.5, 23 },<br />
white = { -23.3, 37.8 },<br />
},<br />
cool2 = {<br />
{ -42.75, 4.5 , 41.5, 56 },<br />
{ -42.75, 4.5 , 4.5, 41.5 },<br />
{ -90 , -42.78, 4.5, 23 },<br />
white = { -23.3, 35 },<br />
},<br />
cool2avg = {<br />
{ -38, 4.5, 25 , 45 },<br />
{ -38, 4.5, 4.5, 30 },<br />
{ -70, -38 , 4.5, 23 },<br />
white = { -23.3, 25 },<br />
},<br />
}<br />
<br />
local function temperature_color(palette, value, out_rgb)<br />
--[[ Return style for a table cell based on the given value which<br />
should be a temperature in °C. ]]<br />
local background_color, text_color<br />
value = tonumber(value)<br />
if value == nil then<br />
background_color, text_color = 'FFF', '000'<br />
add_message("Value supplied to <span style=\"background-color: #EEE; font-family: monospace;\">temperature_color</span> is not recognized.")<br />
else<br />
local min, max = unpack(palette.white or { -23, 35 })<br />
if value < min or value >= max then<br />
text_color = 'FFF'<br />
else<br />
text_color = '' -- This assumes that black text color is the default for most readers.<br />
end<br />
<br />
local background_rgb = out_rgb or {}<br />
for i, v in ipairs(palette) do<br />
local a, b, c, d = unpack(v)<br />
if value <= a then<br />
background_rgb[i] = 0<br />
elseif value < b then<br />
background_rgb[i] = (value - a) * 255 / (b - a)<br />
elseif value <= c then<br />
background_rgb[i] = 255<br />
elseif value < d then<br />
background_rgb[i] = 255 - ( (value - c) * 255 / (d - c) )<br />
else<br />
background_rgb[i] = 0<br />
end<br />
end<br />
background_color = string.format('%02X%02X%02X', background_rgb[1], background_rgb[2], background_rgb[3])<br />
end<br />
if text_color == "" then<br />
return background_color<br />
else<br />
return background_color, text_color<br />
end<br />
end<br />
<br />
local function color_CSS(background_color, text_color)<br />
if background_color and text_color then<br />
return 'background: #' .. background_color .. '; color: #' .. text_color .. ';'<br />
elseif background_color then<br />
return 'background: #' .. background_color .. ';'<br />
else<br />
return ''<br />
end<br />
end<br />
<br />
local function temperature_color_CSS(palette, value, out_rgb)<br />
return color_CSS(temperature_color(palette, value, out_rgb))<br />
end<br />
<br />
function temperature_CSS(value, unit, palette)<br />
local palette = palettes[palette] or palettes.cool<br />
local value = tonumber(value)<br />
if value == nil then<br />
error("The function <span style=\"background-color: #EEE; font-family: monospace;\">temperature_CSS</span> is receiving a nil value")<br />
else<br />
if unit == 'C' then<br />
return color_CSS(temperature_color(palette, value))<br />
elseif unit == 'F' then<br />
return color_CSS(temperature_color(palette, convert(value, decimals, 'F')))<br />
else<br />
unit_error(unit or "nil")<br />
end<br />
end<br />
end<br />
<br />
local function style_attribute(palette, value, out_rgb)<br />
local font_size = "font-size: 85%;"<br />
local color = temperature_color_CSS(palette, value, out_rgb)<br />
return 'style=\"' .. color .. ' ' .. font_size .. '\"'<br />
end<br />
<br />
function export.temperature_style(frame) -- used by Template:Average temperature table/color<br />
local palette = palettes[frame.args.palette] or palettes.cool<br />
local unit = frame.args.unit or 'C'<br />
local value = tonumber(frame.args[1])<br />
if unit == 'C' then<br />
return style_attribute(palette, value)<br />
elseif unit == 'F' then<br />
return style_attribute(palette, convert(value, 1, 'F'))<br />
else<br />
unit_error(unit)<br />
end<br />
end<br />
<br />
--[[ ==== Cell, row, table generation ==== ]]<br />
local output_formats = {<br />
high_low_average_F =<br />
{ first = "F",<br />
convert_units = "yes",<br />
unit_names = "no",<br />
color = "yes",<br />
small_font = "yes",<br />
sortable = "yes",<br />
decimals = "0",<br />
brackets = "yes",<br />
line_break = "auto", },<br />
high_low_average_C =<br />
{ first = "C",<br />
convert_units = "yes",<br />
unit_names = "no",<br />
color = "yes",<br />
small_font = "yes",<br />
sortable = "yes",<br />
decimals = "0",<br />
brackets = "yes",<br />
line_break = "auto", },<br />
high_low_F =<br />
{ first = "F",<br />
convert_units = "yes",<br />
unit_names = "no",<br />
color = "no",<br />
small_font = "yes",<br />
sortable = "no",<br />
decimals = "",<br />
brackets = "yes",<br />
line_break = "auto", },<br />
high_low_C =<br />
{ first = "C",<br />
convert_units = "yes",<br />
unit_names = "no",<br />
color = "no",<br />
small_font = "yes",<br />
sortable = "no",<br />
decimals = "0",<br />
brackets = "yes",<br />
line_break = "auto", },<br />
average_F =<br />
{ first = "F",<br />
convert_units = "yes",<br />
unit_names = "no",<br />
color = "yes",<br />
small_font = "yes",<br />
sortable = "no",<br />
decimals = "0",<br />
brackets = "yes",<br />
line_break = "auto", },<br />
average_C =<br />
{ first = "C",<br />
convert_units = "yes",<br />
unit_names = "no",<br />
color = "yes",<br />
small_font = "yes",<br />
sortable = "no",<br />
decimals = "0",<br />
brackets = "yes",<br />
line_break = "auto", },<br />
}<br />
<br />
local function add_unit_names(value, unit)<br />
if not unit then unit = input_unit end<br />
if output_format.unit_names == "yes" then<br />
if check_for_string(value) then<br />
return value .. "&nbsp;" .. degree .. unit<br />
else<br />
return value -- Don't add a unit name to an empty string<br />
end<br />
else<br />
return value<br />
end<br />
end<br />
<br />
local function if_yes(parameter, realization1, realization2)<br />
if realization1 then<br />
if realization2 then<br />
if parameter == "yes" then<br />
parameter = { realization1, realization2 }<br />
else<br />
parameter = { "", "" }<br />
end<br />
else<br />
if parameter == "yes" then<br />
parameter = realization1<br />
else<br />
parameter = ""<br />
end<br />
end<br />
else<br />
parameter = ""<br />
add_message("<span style=\"background-color: #EEE; font-family: monospace;\">if_yes</span> needs at least one realization")<br />
end<br />
return parameter<br />
end<br />
<br />
function makeCell(output_format, a, b, c)<br />
local cell, cell_content = "", ""<br />
local color_CSS, other_CSS, title_attribute, sortkey, attribute_separator, converted_units_separator = "", "", "", "", "", "", ""<br />
local style_attribute, high_low_separator, brackets, values, converted_units = {"", ""}, {"", ""}, {"", ""}, {"", ""}, {"", ""}<br />
<br />
if check_for_number(output_format.decimals) then<br />
decimals = output_format.decimals<br />
--[[ Precision is the number of decimals in the first number of the last array.<br />
This may be a problem for data from Weatherbase,<br />
which seems to inappropriately remove .0 from numbers that have it. ]]<br />
else<br />
decimals = precision<br />
end<br />
<br />
if check_for_number(b) and check_for_number(a) then<br />
values, high_low_separator = { round(a, decimals), round(b, decimals) }, { thinSpace .. "/" .. thinSpace, if_yes(output_format.convert_units, thinSpace .. "/" .. thinSpace) }<br />
elseif check_for_number(a) then<br />
values = { round(a, decimals), "" }<br />
elseif check_for_number(c) then<br />
values = { round(c, decimals), "" }<br />
end<br />
if output_format.first == input_unit then<br />
if output_format.convert_units == "yes" then<br />
converted_units = { add_unit_names(convert(values[1], decimals), output_unit), add_unit_names(convert(values[2], decimals), output_unit) }<br />
end<br />
values = { add_unit_names(values[1]), add_unit_names(values[2]) }<br />
elseif output_format.first == "C" or output_format.first == "F" then<br />
if output_format.convert_units == "yes" then<br />
converted_units = { add_unit_names(values[1]), add_unit_names(values[2]) }<br />
end<br />
values = { add_unit_names(convert(values[1], decimals), output_unit), add_unit_names(convert(values[2], decimals), output_unit) }<br />
else<br />
if output_format.first == nil then<br />
output_format.first = "nil"<br />
end<br />
add_message("<span style=\"background-color: #EEE; font-family: monospace;\">" .. output_format.first .. "</span>, the value for <span style=\"background-color: #EEE; font-family: monospace;\">first</span> in <span style=\"background-color: #EEE; font-family: monospace;\">output_format</span> is not recognized.")<br />
end<br />
--[[<br />
Regarding line breaks:<br />
If there are two values, there will be at least three characters: 9/1.<br />
If there is one decimal, numbers will be three to five characters long<br />
and there will be 3 to 10 characters total even without unit conversion:<br />
1.1, 116.5/88.0.<br />
If there are units, that adds three characters per number: 25 °C/20 °C.<br />
In each of these cases, a line break is needed so that table cells are not too wide;<br />
even more so when more than one of these things are true.<br />
]]<br />
if output_format.convert_units == "yes" then<br />
brackets = if_yes(output_format.brackets, "(", ")" )<br />
if output_format.line_break == "auto" then<br />
if check_for_string(values[2]) or decimals ~= "0" or output_format.show_units == "yes" then<br />
converted_units_separator = "<br>"<br />
else<br />
converted_units_separator = "&nbsp;"<br />
end<br />
elseif output_format.line_break == "yes" then<br />
converted_units_separator = "<br>"<br />
elseif output_format.line_break == "no" then<br />
converted_units_separator = "&nbsp;"<br />
else<br />
error("Value for line_break not recognized")<br />
end<br />
end<br />
<br />
cell_content = values[1] .. high_low_separator[1] .. values[2] .. converted_units_separator .. brackets[1] .. converted_units[1] .. high_low_separator[2] .. converted_units[2] .. brackets[2]<br />
<br />
if check_for_number(c) then<br />
color_CSS = if_yes(output_format.color, temperature_CSS(c, input_unit, palette))<br />
if check_for_number(b) and check_for_number(a) then<br />
local attribute_value<br />
if output_format.first == input_unit then<br />
attribute_value = c<br />
else<br />
attribute_value = convert(c, decimals)<br />
end<br />
sortkey = if_yes(output_format.sortable, " data-sort-value=\"" .. attribute_value .. "\"")<br />
title_attribute = " title=\"Average temperature: " .. attribute_value .. " " .. degree .. output_format.first .. "\""<br />
end<br />
elseif check_for_number(b) then<br />
color_css = ""<br />
elseif check_for_number(a) then<br />
color_CSS = if_yes(output_format.color, temperature_CSS(a, input_unit, palette))<br />
else<br />
add_message("Neither a nor b nor c are strings.")<br />
end<br />
other_CSS = if_yes(output_format.small_font, "font-size: 85%;")<br />
if check_for_string(color_CSS) or check_for_string(other_CSS) then<br />
style_attribute = { "style=\"", "\"" }<br />
end<br />
<br />
if check_for_string(other_CSS) or check_for_string(color_CSS) or check_for_string(title_attribute) or check_for_string(sortkey) then<br />
attribute_separator = " | "<br />
end<br />
cell = "\n| " .. style_attribute[1] .. color_CSS .. other_CSS .. style_attribute[2] .. title_attribute .. sortkey .. attribute_separator .. cell_content<br />
return cell<br />
end<br />
<br />
function export.makeRow(frame)<br />
make_arrays(frame)<br />
local output = ""<br />
if frame.args[1] then<br />
output = "\n|-"<br />
output = output .. "\n! " .. frame.args[1]<br />
if frame.args[2] then<br />
output = output .. " !! " .. frame.args[2]<br />
end<br />
end<br />
if cell_format then<br />
output_format = cell_format<br />
end<br />
if a and b and c then<br />
for i = 1, length do<br />
if not output_format then<br />
output_format = output_formats.high_low_average_F<br />
end<br />
output = output .. makeCell(output_format, a[i], b[i], c[i])<br />
end<br />
elseif a and b then<br />
for i = 1, length do<br />
if not output_format then<br />
output_format = output_formats.high_low_F<br />
end<br />
output = output .. makeCell(output_format, a[i], b[i])<br />
end<br />
elseif a then<br />
for i = 1, length do<br />
if not output_format then<br />
output_format = output_formats.average_F<br />
end<br />
output = output .. makeCell(output_format, a[i])<br />
end<br />
end<br />
output = mw.ustring.gsub(output, "([%p%s])-(%d)", "%1" .. minus .. "%2")<br />
return output<br />
end<br />
<br />
function export.makeTable(frame)<br />
make_arrays(frame)<br />
local output = "{| class=\"wikitable center nowrap\""<br />
if cell_format then<br />
output_format = cell_format<br />
end<br />
if a and b and c then<br />
for i = 1, length do<br />
if not output_format then<br />
output_format = output_formats.high_low_average_F<br />
end<br />
output = output .. makeCell(output_format, a[i], b[i], c[i])<br />
end<br />
elseif a and b then<br />
for i = 1, length do<br />
if not output_format then<br />
output_format = output_formats.high_low_F<br />
end<br />
output = output .. makeCell(output_format, a[i], b[i])<br />
end<br />
elseif a then<br />
for i = 1, length do<br />
if not output_format then<br />
output_format = output_formats.average_F<br />
end<br />
output = output .. makeCell(output_format, a[i])<br />
end<br />
end<br />
output = mw.ustring.gsub(output, "([%p%s])-(%d)", "%1" .. minus .. "%2")<br />
--[[ Replaces hyphens that have a punctuation or space character before them and a number after them,<br />
making sure that hyphens in "data-sort-type" are not replaced with minuses.<br />
If Lua had (?<=), a capture would not be necessary. ]]<br />
output = output .. "\n|}"<br />
if show then<br />
output = output .. "\n\n<span style=\"color: red; font-size: 80%; line-height: 100%;\">" .. message .. "</span>"<br />
end<br />
return output<br />
end<br />
<br />
<br />
<br />
local chart = [[<br />
{{Graph:Chart<br />
|width=600<br />
|height=180<br />
|xAxisTitle=Celsius<br />
|yAxisTitle=__COLOR<br />
|type=line<br />
|x=__XVALUES<br />
|y=__YVALUES<br />
|colors=__COLOR<br />
}}<br />
]]<br />
<br />
function export.show(frame)<br />
-- For testing, return wikitext to show graphs of how the red/green/blue colors<br />
-- vary with temperature, and a table of the resulting colors.<br />
local function collection()<br />
-- Return a table to hold items.<br />
return {<br />
n = 0,<br />
add = function (self, item)<br />
self.n = self.n + 1<br />
self[self.n] = item<br />
end,<br />
join = function (self, sep)<br />
return table.concat(self, sep)<br />
end,<br />
}<br />
end<br />
local function make_chart(result, color, xvalues, yvalues)<br />
result:add('\n')<br />
result:add(frame:preprocess((chart:gsub('__[A-Z]+', {<br />
__COLOR = color,<br />
__XVALUES = xvalues:join(','),<br />
__YVALUES = yvalues:join(','),<br />
}))))<br />
end<br />
local function with_minus(value)<br />
if value < 0 then<br />
return minus .. tostring(-value)<br />
end<br />
return tostring(value)<br />
end<br />
local args = frame.args<br />
local first = args[1] or -90<br />
local last = args[2] or 59<br />
local palette = palettes[args.palette] or palettes.cool<br />
local xvals, reds, greens, blues = collection(), collection(), collection(), collection()<br />
local wikitext = collection()<br />
wikitext:add('{| class="wikitable"\n|-\n')<br />
local columns = 0<br />
for celsius = first, last do<br />
local background_rgb = {}<br />
local style = style_attribute(palette, celsius, background_rgb)<br />
local R = math.floor(background_rgb[1])<br />
local G = math.floor(background_rgb[2])<br />
local B = math.floor(background_rgb[3])<br />
xvals:add(celsius)<br />
reds:add(R)<br />
greens:add(G)<br />
blues:add(B)<br />
wikitext:add('| ' .. style .. ' | ' .. with_minus(celsius) .. '\n')<br />
columns = columns + 1<br />
if columns >= 10 then<br />
columns = 0<br />
wikitext:add('|-\n')<br />
end<br />
end<br />
wikitext:add('|}\n')<br />
make_chart(wikitext, 'Red', xvals, reds)<br />
make_chart(wikitext, 'Green', xvals, greens)<br />
make_chart(wikitext, 'Blue', xvals, blues)<br />
return wikitext:join()<br />
end<br />
<br />
return export</div>
Erutuon